243 lines
8.5 KiB
C#
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; }
|
|
}
|
|
} |