1 // ----------------------------------------------------------------------------------------------
3 // / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
4 // / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
5 // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
6 // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
7 // ----------------------------------------------------------------------------------------------
9 // Copyright 2015-2025 Ćukasz "JustArchi" Domeradzki
10 // Contact: JustArchi@JustArchi.net
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
16 // http://www.apache.org/licenses/LICENSE-2.0
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.
26 using System
.Net
.Http
;
27 using System
.Threading
.Tasks
;
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
{
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
);
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
)));