2025-02-01 18:19:18 +03:00
|
|
|
|
using System.Net;
|
|
|
|
|
|
using System.Net.Http.Headers;
|
|
|
|
|
|
using System.Text.Json.Serialization;
|
2025-01-05 17:05:23 +03:00
|
|
|
|
using Nebula.Shared.Models.Auth;
|
2025-05-05 20:43:28 +03:00
|
|
|
|
using Nebula.Shared.Services.Logging;
|
2025-02-01 18:19:18 +03:00
|
|
|
|
using Nebula.Shared.Utils;
|
2024-12-22 21:38:19 +03:00
|
|
|
|
|
2025-01-05 17:05:23 +03:00
|
|
|
|
namespace Nebula.Shared.Services;
|
2024-12-22 21:38:19 +03:00
|
|
|
|
|
|
|
|
|
|
[ServiceRegister]
|
2025-01-14 22:10:16 +03:00
|
|
|
|
public class AuthService(
|
|
|
|
|
|
RestService restService,
|
|
|
|
|
|
DebugService debugService,
|
2025-01-05 17:05:23 +03:00
|
|
|
|
CancellationService cancellationService)
|
2024-12-22 21:38:19 +03:00
|
|
|
|
{
|
|
|
|
|
|
private readonly HttpClient _httpClient = new();
|
2025-05-05 20:43:28 +03:00
|
|
|
|
private readonly ILogger _logger = debugService.GetLogger("AuthService");
|
2024-12-27 19:15:33 +03:00
|
|
|
|
|
2025-07-10 15:22:15 +03:00
|
|
|
|
public async Task<AuthTokenCredentials> Auth(string login, string password, string authServer, string? code = null)
|
2024-12-22 21:38:19 +03:00
|
|
|
|
{
|
2025-05-05 20:43:28 +03:00
|
|
|
|
_logger.Debug($"Auth to {authServer}api/auth/authenticate {login}");
|
2025-01-14 22:10:16 +03:00
|
|
|
|
|
2024-12-28 08:29:01 +03:00
|
|
|
|
var authUrl = new Uri($"{authServer}api/auth/authenticate");
|
2024-12-22 21:38:19 +03:00
|
|
|
|
|
2025-02-01 18:19:18 +03:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var result =
|
|
|
|
|
|
await restService.PostAsync<AuthenticateResponse, AuthenticateRequest>(
|
|
|
|
|
|
new AuthenticateRequest(login, null, password, code), authUrl, cancellationService.Token);
|
2025-01-14 22:10:16 +03:00
|
|
|
|
|
2025-07-10 15:22:15 +03:00
|
|
|
|
return new AuthTokenCredentials(result.UserId,
|
|
|
|
|
|
new LoginToken(result.Token, result.ExpireTime), login, authServer);
|
2025-02-01 18:19:18 +03:00
|
|
|
|
}
|
|
|
|
|
|
catch (RestRequestException e)
|
2024-12-27 19:15:33 +03:00
|
|
|
|
{
|
2025-02-01 18:19:18 +03:00
|
|
|
|
Console.WriteLine(e.Content);
|
|
|
|
|
|
if (e.StatusCode != HttpStatusCode.Unauthorized) throw;
|
|
|
|
|
|
var err = await e.Content.AsJson<AuthDenyError>();
|
|
|
|
|
|
|
|
|
|
|
|
if (err is null) throw;
|
2025-11-08 13:42:11 +03:00
|
|
|
|
e.Dispose();
|
2025-02-01 18:19:18 +03:00
|
|
|
|
throw new AuthException(err);
|
2024-12-27 19:15:33 +03:00
|
|
|
|
}
|
2024-12-22 21:38:19 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-10 15:22:15 +03:00
|
|
|
|
public async Task EnsureToken(AuthTokenCredentials tokenCredentials)
|
2025-01-05 17:05:23 +03:00
|
|
|
|
{
|
2025-07-10 15:22:15 +03:00
|
|
|
|
var authUrl = new Uri($"{tokenCredentials.AuthServer}api/auth/ping");
|
2024-12-22 21:38:19 +03:00
|
|
|
|
|
|
|
|
|
|
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, authUrl);
|
2025-07-10 15:22:15 +03:00
|
|
|
|
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("SS14Auth", tokenCredentials.Token.Token);
|
2025-01-05 17:05:23 +03:00
|
|
|
|
using var resp = await _httpClient.SendAsync(requestMessage, cancellationService.Token);
|
2024-12-22 21:38:19 +03:00
|
|
|
|
}
|
2025-08-06 21:29:00 +03:00
|
|
|
|
|
|
|
|
|
|
public async Task Logout(AuthTokenCredentials tokenCredentials)
|
|
|
|
|
|
{
|
|
|
|
|
|
var authUrl = new Uri($"{tokenCredentials.AuthServer}api/auth/logout");
|
|
|
|
|
|
await restService.PostAsync<NullResponse, TokenRequest>(TokenRequest.From(tokenCredentials), authUrl, cancellationService.Token);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<AuthTokenCredentials> Refresh(AuthTokenCredentials tokenCredentials)
|
|
|
|
|
|
{
|
|
|
|
|
|
var authUrl = new Uri($"{tokenCredentials.AuthServer}api/auth/refresh");
|
2025-11-08 13:42:11 +03:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var newToken = await restService.PostAsync<LoginToken, TokenRequest>(
|
|
|
|
|
|
TokenRequest.From(tokenCredentials), authUrl, cancellationService.Token);
|
|
|
|
|
|
|
|
|
|
|
|
return tokenCredentials with { Token = newToken };
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (RestRequestException e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (e.StatusCode == HttpStatusCode.Unauthorized)
|
|
|
|
|
|
throw new AuthTokenExpiredException(tokenCredentials);
|
|
|
|
|
|
|
|
|
|
|
|
e.Dispose();
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public sealed class AuthTokenExpiredException : Exception
|
|
|
|
|
|
{
|
|
|
|
|
|
public AuthTokenExpiredException(AuthTokenCredentials credentials): base("Taken token is expired. Login: " + credentials.Login)
|
|
|
|
|
|
{
|
2025-08-06 21:29:00 +03:00
|
|
|
|
}
|
2024-12-22 21:38:19 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-10 15:22:15 +03:00
|
|
|
|
public sealed record AuthTokenCredentials(Guid UserId, LoginToken Token, string Login, string AuthServer);
|
2025-11-08 13:42:11 +03:00
|
|
|
|
public sealed record ProfileAuthCredentials(
|
|
|
|
|
|
string Login,
|
|
|
|
|
|
string Password,
|
|
|
|
|
|
string AuthServer);
|
2025-01-14 22:10:16 +03:00
|
|
|
|
|
2025-02-01 18:19:18 +03:00
|
|
|
|
public sealed record AuthDenyError(string[] Errors, AuthenticateDenyCode Code);
|
|
|
|
|
|
|
|
|
|
|
|
public sealed class AuthException(AuthDenyError error) : Exception
|
|
|
|
|
|
{
|
|
|
|
|
|
public AuthDenyError Error { get; } = error;
|
2025-08-17 21:02:45 +03:00
|
|
|
|
|
|
|
|
|
|
public override string Message
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
var str = "Error while logging in. Please try again. " + Error.Code;
|
|
|
|
|
|
foreach (var error in Error.Errors)
|
|
|
|
|
|
{
|
|
|
|
|
|
str += "\n" + error;
|
|
|
|
|
|
}
|
|
|
|
|
|
return str;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-02-01 18:19:18 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
|
|
|
|
|
public enum AuthenticateDenyCode
|
|
|
|
|
|
{
|
|
|
|
|
|
None = 0,
|
|
|
|
|
|
InvalidCredentials = 1,
|
|
|
|
|
|
AccountUnconfirmed = 2,
|
|
|
|
|
|
TfaRequired = 3,
|
|
|
|
|
|
TfaInvalid = 4,
|
|
|
|
|
|
AccountLocked = 5,
|
|
|
|
|
|
}
|
2025-08-06 21:29:00 +03:00
|
|
|
|
|
|
|
|
|
|
public sealed record TokenRequest(string Token)
|
|
|
|
|
|
{
|
|
|
|
|
|
public static TokenRequest From(AuthTokenCredentials authTokenCredentials)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new TokenRequest(authTokenCredentials.Token.Token);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static TokenRequest Empty { get; } = new TokenRequest("");
|
|
|
|
|
|
}
|