using System.Net.Http.Json;
using System.Text.Json;
namespace foodmarket.Pos.Core.Auth;
public record AuthTokens(string AccessToken, string RefreshToken, int ExpiresIn);
/// Вход кассира (password grant) и обновление токена (refresh) против
/// OpenIddict-сервера food-market. Касса использует тот же client_id,
/// что и веб. PIN поддерживается как пароль кассира (PIN = пароль учётки).
public sealed class AuthService
{
private readonly HttpClient _http;
public AuthService(HttpClient http) { _http = http; }
public async Task LoginAsync(string serverUrl, string username, string password, CancellationToken ct = default)
{
var form = new FormUrlEncodedContent(new Dictionary
{
["grant_type"] = "password",
["username"] = username,
["password"] = password,
["client_id"] = "food-market-web",
["scope"] = "openid profile email roles api offline_access",
});
using var resp = await _http.PostAsync(serverUrl.TrimEnd('/') + "/connect/token", form, ct);
var body = await resp.Content.ReadAsStringAsync(ct);
if (!resp.IsSuccessStatusCode)
throw new AuthException(DescribeError(body) ?? $"Вход не удался ({(int)resp.StatusCode})");
return Parse(body);
}
public async Task RefreshAsync(string serverUrl, string refreshToken, CancellationToken ct = default)
{
var form = new FormUrlEncodedContent(new Dictionary
{
["grant_type"] = "refresh_token",
["refresh_token"] = refreshToken,
["client_id"] = "food-market-web",
});
using var resp = await _http.PostAsync(serverUrl.TrimEnd('/') + "/connect/token", form, ct);
var body = await resp.Content.ReadAsStringAsync(ct);
if (!resp.IsSuccessStatusCode)
throw new AuthException("Сессия истекла — войдите снова.");
return Parse(body);
}
private static AuthTokens Parse(string body)
{
using var doc = JsonDocument.Parse(body);
var r = doc.RootElement;
return new AuthTokens(
r.GetProperty("access_token").GetString()!,
r.TryGetProperty("refresh_token", out var rt) ? rt.GetString()! : "",
r.TryGetProperty("expires_in", out var e) ? e.GetInt32() : 3600);
}
private static string? DescribeError(string body)
{
try { using var d = JsonDocument.Parse(body); return d.RootElement.TryGetProperty("error_description", out var e) ? e.GetString() : null; }
catch { return null; }
}
}
public sealed class AuthException : Exception { public AuthException(string m) : base(m) { } }