using System; using System.Threading.Tasks; using DysonNetwork.Common.Models; using DysonNetwork.Pass.Data; using DysonNetwork.Pass.Features.Auth.Models; using Microsoft.EntityFrameworkCore; using NodaTime; // Use fully qualified names to avoid ambiguity using CommonAccount = DysonNetwork.Common.Models.Account; using CommonAccountConnection = DysonNetwork.Common.Models.AccountConnection; using CommonOidcUserInfo = DysonNetwork.Common.Models.OidcUserInfo; namespace DysonNetwork.Pass.Features.Auth.Services; public class AccountConnectionService : IAccountConnectionService { private readonly PassDatabase _db; private readonly IClock _clock; private readonly ISessionService _sessionService; public AccountConnectionService(PassDatabase db, IClock clock, ISessionService sessionService) { _db = db; _clock = clock; _sessionService = sessionService; } public async Task FindOrCreateConnection(CommonOidcUserInfo userInfo, string provider) { if (string.IsNullOrEmpty(userInfo.UserId)) throw new ArgumentException("User ID is required", nameof(userInfo)); // Try to find existing connection var connection = await _db.AccountConnections .FirstOrDefaultAsync(c => c.Provider == provider && c.ProvidedIdentifier == userInfo.UserId); if (connection == null) { // Create new connection connection = new CommonAccountConnection { Id = Guid.NewGuid().ToString("N"), Provider = provider, ProvidedIdentifier = userInfo.UserId, DisplayName = userInfo.Name, CreatedAt = _clock.GetCurrentInstant(), LastUsedAt = _clock.GetCurrentInstant(), Meta = userInfo.ToMetadata() }; await _db.AccountConnections.AddAsync(connection); } // Update connection with latest info await UpdateConnection(connection, userInfo); await _db.SaveChangesAsync(); return connection; } public async Task UpdateConnection(CommonAccountConnection connection, CommonOidcUserInfo userInfo) { connection.LastUsedAt = _clock.GetCurrentInstant(); connection.AccessToken = userInfo.AccessToken; connection.RefreshToken = userInfo.RefreshToken; connection.ExpiresAt = userInfo.ExpiresAt != null ? Instant.FromDateTimeOffset(userInfo.ExpiresAt.Value) : null; // Update metadata var metadata = userInfo.ToMetadata(); if (metadata != null) { connection.Meta = metadata; } _db.AccountConnections.Update(connection); await _db.SaveChangesAsync(); } public async Task FindConnection(string provider, string userId) { if (string.IsNullOrEmpty(provider) || string.IsNullOrEmpty(userId)) return null; return await _db.AccountConnections .AsNoTracking() .FirstOrDefaultAsync(c => c.Provider == provider && c.ProvidedIdentifier == userId); } public async Task CreateSessionAsync(CommonAccount account, string? deviceId = null) { if (account == null) throw new ArgumentNullException(nameof(account)); var now = _clock.GetCurrentInstant(); var session = new Models.AuthSession { Id = Guid.NewGuid(), AccountId = Guid.Parse(account.Id), Label = $"OIDC Session {DateTime.UtcNow:yyyy-MM-dd}", LastGrantedAt = now, ExpiredAt = now.Plus(Duration.FromDays(30)), // 30-day session // Challenge will be set later if needed }; await _db.AuthSessions.AddAsync(session); await _db.SaveChangesAsync(); return session; } public async Task AddConnectionAsync(CommonAccount account, CommonOidcUserInfo userInfo, string provider) { if (account == null) throw new ArgumentNullException(nameof(account)); if (string.IsNullOrEmpty(userInfo.UserId)) throw new ArgumentException("User ID is required", nameof(userInfo)); // Check if connection already exists var existingConnection = await FindConnection(provider, userInfo.UserId); if (existingConnection != null) { // Update existing connection await UpdateConnection(existingConnection, userInfo); return existingConnection; } // Create new connection var connection = new CommonAccountConnection { Id = Guid.NewGuid().ToString("N"), AccountId = account.Id, Provider = provider, ProvidedIdentifier = userInfo.UserId, DisplayName = userInfo.Name, CreatedAt = _clock.GetCurrentInstant(), LastUsedAt = _clock.GetCurrentInstant(), Meta = userInfo.ToMetadata() }; // Set token info if available if (userInfo.AccessToken != null) { connection.AccessToken = userInfo.AccessToken; connection.RefreshToken = userInfo.RefreshToken; connection.ExpiresAt = userInfo.ExpiresAt != null ? Instant.FromDateTimeOffset(userInfo.ExpiresAt.Value) : null; } await _db.AccountConnections.AddAsync(connection); await _db.SaveChangesAsync(); return connection; } }