chore(deps): update docker/build-push-action action to v6.11.0
[ArchiSteamFarm.git] / ArchiSteamFarm.CustomPlugins.SignInWithSteam / SignInWithSteamController.cs
blobbb89aa6c29688f62c2603e27f5ae185331818bc7
1 // ----------------------------------------------------------------------------------------------
2 // _ _ _ ____ _ _____
3 // / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
4 // / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
5 // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
6 // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
7 // ----------------------------------------------------------------------------------------------
8 // |
9 // Copyright 2015-2025 Ɓukasz "JustArchi" Domeradzki
10 // Contact: JustArchi@JustArchi.net
11 // |
12 // Licensed under the Apache License, Version 2.0 (the "License");
13 // you may not use this file except in compliance with the License.
14 // You may obtain a copy of the License at
15 // |
16 // http://www.apache.org/licenses/LICENSE-2.0
17 // |
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the License is distributed on an "AS IS" BASIS,
20 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 // See the License for the specific language governing permissions and
22 // limitations under the License.
24 using System;
25 using System.Net;
26 using System.Net.Http;
27 using System.Threading.Tasks;
28 using AngleSharp.Dom;
29 using ArchiSteamFarm.Core;
30 using ArchiSteamFarm.CustomPlugins.SignInWithSteam.Data;
31 using ArchiSteamFarm.IPC.Controllers.Api;
32 using ArchiSteamFarm.IPC.Responses;
33 using ArchiSteamFarm.Localization;
34 using ArchiSteamFarm.Steam;
35 using ArchiSteamFarm.Steam.Integration;
36 using ArchiSteamFarm.Web;
37 using ArchiSteamFarm.Web.Responses;
38 using Microsoft.AspNetCore.Mvc;
40 namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam;
42 [Route("/Api/Bot/{botName:required}/SignInWithSteam")]
43 public sealed class SignInWithSteamController : ArchiController {
44 [HttpPost]
45 [ProducesResponseType<GenericResponse<SignInWithSteamResponse>>((int) HttpStatusCode.OK)]
46 [ProducesResponseType<GenericResponse>((int) HttpStatusCode.BadRequest)]
47 [ProducesResponseType<GenericResponse>((int) HttpStatusCode.ServiceUnavailable)]
48 public async Task<ActionResult<GenericResponse>> Post(string botName, [FromBody] SignInWithSteamRequest request) {
49 ArgumentException.ThrowIfNullOrEmpty(botName);
50 ArgumentNullException.ThrowIfNull(request);
52 Bot? bot = Bot.GetBot(botName);
54 if (bot == null) {
55 return BadRequest(new GenericResponse(false, Strings.FormatBotNotFound(botName)));
58 if (!bot.IsConnectedAndLoggedOn) {
59 return StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, Strings.BotNotConnected));
62 // We've got a redirection, initiate OpenID procedure by following it
63 using HtmlDocumentResponse? challengeResponse = await bot.ArchiWebHandler.UrlGetToHtmlDocumentWithSession(request.RedirectURL).ConfigureAwait(false);
65 if (challengeResponse?.Content == null) {
66 return StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, Strings.FormatErrorRequestFailedTooManyTimes(WebBrowser.MaxTries)));
69 IAttr? paramsNode = challengeResponse.Content.SelectSingleNode<IAttr>("//input[@name='openidparams']/@value");
71 if (paramsNode == null) {
72 ASF.ArchiLogger.LogNullError(paramsNode);
74 return StatusCode((int) HttpStatusCode.InternalServerError, new GenericResponse(false, Strings.FormatErrorObjectIsNull(nameof(paramsNode))));
77 string paramsValue = paramsNode.Value;
79 if (string.IsNullOrEmpty(paramsValue)) {
80 ASF.ArchiLogger.LogNullError(paramsValue);
82 return StatusCode((int) HttpStatusCode.InternalServerError, new GenericResponse(false, Strings.FormatErrorObjectIsNull(nameof(paramsValue))));
85 IAttr? nonceNode = challengeResponse.Content.SelectSingleNode<IAttr>("//input[@name='nonce']/@value");
87 if (nonceNode == null) {
88 ASF.ArchiLogger.LogNullError(nonceNode);
90 return StatusCode((int) HttpStatusCode.InternalServerError, new GenericResponse(false, Strings.FormatErrorObjectIsNull(nameof(nonceNode))));
93 string nonceValue = nonceNode.Value;
95 if (string.IsNullOrEmpty(nonceValue)) {
96 ASF.ArchiLogger.LogNullError(nonceValue);
98 return StatusCode((int) HttpStatusCode.InternalServerError, new GenericResponse(false, Strings.FormatErrorObjectIsNull(nameof(nonceValue))));
101 Uri loginRequest = new(ArchiWebHandler.SteamCommunityURL, "/openid/login");
103 using StringContent actionContent = new("steam_openid_login");
104 using StringContent modeContent = new("checkid_setup");
105 using StringContent paramsContent = new(paramsValue);
106 using StringContent nonceContent = new(nonceValue);
108 using MultipartFormDataContent data = new();
110 data.Add(actionContent, "action");
111 data.Add(modeContent, "openid.mode");
112 data.Add(paramsContent, "openidparams");
113 data.Add(nonceContent, "nonce");
115 // Accept OpenID request presented and follow redirection back to the data we initially expected
116 BasicResponse? loginResponse = await bot.ArchiWebHandler.WebBrowser.UrlPost(loginRequest, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections).ConfigureAwait(false);
118 return loginResponse != null ? Ok(new GenericResponse<SignInWithSteamResponse>(new SignInWithSteamResponse(loginResponse.FinalUri))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, Strings.FormatErrorRequestFailedTooManyTimes(WebBrowser.MaxTries)));