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) { } }