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; namespace DysonNetwork.Sphere.Auth; public class AuthGrpcService(AppDatabase db, AccountService accounts, AuthService auth) : DysonNetwork.Sphere.Auth.Proto.AuthService.AuthServiceBase { 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(); } }