using DysonNetwork.Sphere.Auth.Proto; using Grpc.Core; using Google.Protobuf.WellKnownTypes; using DysonNetwork.Sphere.Account; using Microsoft.EntityFrameworkCore; using NodaTime; using System.Text.Json; using DysonNetwork.Shared.Models; using Challenge = DysonNetwork.Sphere.Auth.Proto.Challenge; using Session = DysonNetwork.Sphere.Auth.Proto.Session; namespace DysonNetwork.Sphere.Auth; public class AuthGrpcService : DysonNetwork.Sphere.Auth.Proto.AuthService.AuthServiceBase { private readonly AppDatabase _db; private readonly AccountService _accounts; private readonly AuthService _auth; public AuthGrpcService(AppDatabase db, AccountService accounts, AuthService auth) { _db = db; _accounts = accounts; _auth = auth; } public override async Task Login(LoginRequest request, ServerCallContext context) { var account = await _accounts.LookupAccount(request.Username); if (account == null) { throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Account not found.")); } var factor = await _db.AccountAuthFactors.FirstOrDefaultAsync(f => f.AccountId == account.Id && f.Type == AccountAuthFactorType.Password); if (factor == null || !factor.VerifyPassword(request.Password)) { throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Invalid credentials.")); } var session = new Session { LastGrantedAt = Instant.FromDateTimeUtc(DateTime.UtcNow), ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(30)), Account = account, Challenge = new Challenge() // Create a dummy challenge }; _db.AuthSessions.Add(session); await _db.SaveChangesAsync(); var token = _auth.CreateToken(session); return new LoginResponse { AccessToken = token, ExpiresIn = (long)(session.ExpiredAt.Value - session.LastGrantedAt.Value).TotalSeconds }; } public override async Task IntrospectToken(IntrospectTokenRequest request, ServerCallContext context) { if (_auth.ValidateToken(request.Token, out var sessionId)) { var session = await _db.AuthSessions .Include(s => s.Account) .Include(s => s.Challenge) .FirstOrDefaultAsync(s => s.Id == sessionId); if (session != null) { return new IntrospectionResponse { Active = true, Claims = JsonSerializer.Serialize(new { sub = session.AccountId }), ClientId = session.AppId?.ToString() ?? "", Username = session.Account.Name, Scope = string.Join(" ", session.Challenge.Scopes), Iat = Timestamp.FromDateTime(session.CreatedAt.ToDateTimeUtc()), Exp = Timestamp.FromDateTime(session.ExpiredAt?.ToDateTimeUtc() ?? DateTime.MaxValue) }; } } return new IntrospectionResponse { Active = false }; } public override async Task Logout(Empty request, ServerCallContext context) { var authorizationHeader = context.RequestHeaders.FirstOrDefault(h => h.Key == "authorization"); if (authorizationHeader != null) { var token = authorizationHeader.Value.Replace("Bearer ", ""); if (_auth.ValidateToken(token, out var sessionId)) { var session = await _db.AuthSessions.FindAsync(sessionId); if (session != null) { _db.AuthSessions.Remove(session); await _db.SaveChangesAsync(); } } } return new Empty(); } }