Files
ChronoCCG/Chrono/Web/Services/AuthService.cs
T
6d486f49 ffaab59585 ...
2026-09-09 17:19:03 -04:00

243 lines
8.5 KiB
C#

using System.Net.Http.Json;
using System.Text.Json.Serialization;
using Microsoft.JSInterop;
namespace Web.Services;
public class AuthService
{
private readonly string _graphqlEndpoint;
private readonly HttpClient _http;
private readonly IJSRuntime? _jsRuntime;
private readonly NetworkStatusService _networkStatus;
public AuthService(HttpClient http, NetworkStatusService networkStatus, IServiceProvider serviceProvider)
{
_http = http;
_networkStatus = networkStatus;
_jsRuntime = serviceProvider.GetService(typeof(IJSRuntime)) as IJSRuntime;
_graphqlEndpoint = http.BaseAddress != null && http.BaseAddress.Port == 7266
? "/graphql"
: "https://localhost:7266/graphql";
}
public bool IsLoggedIn => !string.IsNullOrEmpty(AuthToken);
public string? CurrentUsername { get; private set; }
public string? AuthToken { get; private set; }
public event Action? AuthStateChanged;
public async Task InitializeAsync()
{
if (_jsRuntime != null)
try
{
var storedToken = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "chrono_auth_token");
var storedUser = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "chrono_auth_user");
if (string.IsNullOrEmpty(storedToken) || string.IsNullOrEmpty(storedUser))
{
try
{
var cookieStr = await _jsRuntime.InvokeAsync<string?>("eval", "document.cookie");
if (!string.IsNullOrEmpty(cookieStr))
{
foreach (var part in cookieStr.Split(';'))
{
var kv = part.Trim().Split('=');
if (kv.Length == 2)
{
if (kv[0] == "chrono_auth_token" && string.IsNullOrEmpty(storedToken))
storedToken = kv[1];
if (kv[0] == "chrono_auth_user" && string.IsNullOrEmpty(storedUser))
storedUser = kv[1];
}
}
}
}
catch
{
}
}
if (!string.IsNullOrEmpty(storedToken) && !string.IsNullOrEmpty(storedUser))
{
AuthToken = storedToken;
CurrentUsername = storedUser;
AuthStateChanged?.Invoke();
}
}
catch
{
// LocalStorage not available in testing or SSR
}
}
public async Task<AuthResult> LoginAsync(string username, string password)
{
if (!_networkStatus.IsOnline)
return new AuthResult { Success = false, Message = "Cannot login while offline." };
try
{
var request = new GraphQLRequest
{
Query = @"mutation Login($username: String!, $password: String!) {
login(username: $username, password: $password) {
success
token
username
message
}
}",
Variables = new Dictionary<string, object>
{
["username"] = username,
["password"] = password
}
};
var response = await SendGraphQLAsync<LoginResponseData>(request);
var payload = response?.Data?.Login;
if (payload != null && payload.Success && !string.IsNullOrEmpty(payload.Token))
{
AuthToken = payload.Token;
CurrentUsername = payload.Username ?? username;
if (_jsRuntime != null)
try
{
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "chrono_auth_token", AuthToken);
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "chrono_auth_user", CurrentUsername);
}
catch
{
}
AuthStateChanged?.Invoke();
return new AuthResult { Success = true, Message = payload.Message ?? "Login successful" };
}
return new AuthResult { Success = false, Message = payload?.Message ?? "Invalid username or password." };
}
catch (Exception ex)
{
return new AuthResult { Success = false, Message = $"Login error: {ex.Message}" };
}
}
public async Task LogoutAsync()
{
AuthToken = null;
CurrentUsername = null;
if (_jsRuntime != null)
try
{
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "chrono_auth_token");
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "chrono_auth_user");
}
catch
{
}
AuthStateChanged?.Invoke();
}
public void SetAuth(string username, string token)
{
CurrentUsername = username;
AuthToken = token;
AuthStateChanged?.Invoke();
}
private async Task<GraphQLResponse<T>?> SendGraphQLAsync<T>(GraphQLRequest request)
{
try
{
var httpResponse = await _http.PostAsJsonAsync(_graphqlEndpoint, request);
if (httpResponse.IsSuccessStatusCode)
return await httpResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
if (_graphqlEndpoint == "/graphql" || _graphqlEndpoint.StartsWith("https://"))
{
var fallbackUrl = _graphqlEndpoint == "/graphql"
? "https://localhost:7266/graphql"
: "http://localhost:7266/graphql";
var fallbackResponse = await _http.PostAsJsonAsync(fallbackUrl, request);
if (fallbackResponse.IsSuccessStatusCode)
return await fallbackResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
}
}
catch
{
try
{
var fallbackUrl = _graphqlEndpoint.StartsWith("http://")
? "https://localhost:7266/graphql"
: "http://localhost:7266/graphql";
var fallbackResponse = await _http.PostAsJsonAsync(fallbackUrl, request);
if (fallbackResponse.IsSuccessStatusCode)
return await fallbackResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
}
catch
{
if (_graphqlEndpoint == "/graphql")
{
try
{
var fallbackHttp = await _http.PostAsJsonAsync("http://localhost:7266/graphql", request);
if (fallbackHttp.IsSuccessStatusCode)
return await fallbackHttp.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
}
catch
{
}
}
}
}
return null;
}
public class AuthResult
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
}
private class GraphQLRequest
{
[JsonPropertyName("query")] public string Query { get; set; } = string.Empty;
[JsonPropertyName("variables")] public Dictionary<string, object>? Variables { get; set; }
}
private class GraphQLResponse<T>
{
[JsonPropertyName("data")] public T? Data { get; set; }
[JsonPropertyName("errors")] public List<GraphQLError>? Errors { get; set; }
}
private class GraphQLError
{
[JsonPropertyName("message")] public string Message { get; set; } = string.Empty;
}
private class LoginResponseData
{
[JsonPropertyName("login")] public LoginPayloadData? Login { get; set; }
}
private class LoginPayloadData
{
[JsonPropertyName("success")] public bool Success { get; set; }
[JsonPropertyName("token")] public string? Token { get; set; }
[JsonPropertyName("username")] public string? Username { get; set; }
[JsonPropertyName("message")] public string? Message { get; set; }
}
}