using DysonNetwork.Sphere.Account; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using NodaTime; namespace DysonNetwork.Sphere.Auth.OpenId; [ApiController] [Route("/auth/login")] public class OidcController( IServiceProvider serviceProvider, AppDatabase db, AccountService accounts, AuthService authService ) : ControllerBase { [HttpGet("{provider}")] public ActionResult SignIn([FromRoute] string provider, [FromQuery] string? returnUrl = "/") { try { // Get the appropriate provider service var oidcService = GetOidcService(provider); // Generate state (containing return URL) and nonce var state = returnUrl; var nonce = Guid.NewGuid().ToString(); // Get the authorization URL and redirect the user var authUrl = oidcService.GetAuthorizationUrl(state, nonce); return Redirect(authUrl); } catch (Exception ex) { return BadRequest($"Error initiating OpenID Connect flow: {ex.Message}"); } } /// /// Mobile Apple Sign In endpoint /// Handles Apple authentication directly from mobile apps /// [HttpPost("apple/mobile")] public async Task> AppleMobileSignIn([FromBody] AppleMobileSignInRequest request) { try { // Get Apple OIDC service if (GetOidcService("apple") is not AppleOidcService appleService) return StatusCode(503, "Apple OIDC service not available"); // Prepare callback data for processing var callbackData = new OidcCallbackData { IdToken = request.IdentityToken, Code = request.AuthorizationCode, }; // Process the authentication var userInfo = await appleService.ProcessCallbackAsync(callbackData); // Find or create user account using existing logic var account = await FindOrCreateAccount(userInfo, "apple"); // Create session using the OIDC service var session = await appleService.CreateSessionForUserAsync(userInfo, account); // Generate token using existing auth service var token = authService.CreateToken(session); return Ok(new AuthController.TokenExchangeResponse { Token = token }); } catch (SecurityTokenValidationException ex) { return Unauthorized($"Invalid identity token: {ex.Message}"); } catch (Exception ex) { // Log the error return StatusCode(500, $"Authentication failed: {ex.Message}"); } } private OidcService GetOidcService(string provider) { return provider.ToLower() switch { "apple" => serviceProvider.GetRequiredService(), "google" => serviceProvider.GetRequiredService(), // Add more providers as needed _ => throw new ArgumentException($"Unsupported provider: {provider}") }; } private async Task FindOrCreateAccount(OidcUserInfo userInfo, string provider) { if (string.IsNullOrEmpty(userInfo.Email)) throw new ArgumentException("Email is required for account creation"); // Check if account exists by email var existingAccount = await accounts.LookupAccount(userInfo.Email); if (existingAccount != null) { // Check if this provider connection already exists var existingConnection = await db.AccountConnections .FirstOrDefaultAsync(c => c.AccountId == existingAccount.Id && c.Provider == provider && c.ProvidedIdentifier == userInfo.UserId); // If no connection exists, create one if (existingConnection != null) return existingAccount; var connection = new AccountConnection { AccountId = existingAccount.Id, Provider = provider, ProvidedIdentifier = userInfo.UserId!, }; db.AccountConnections.Add(connection); await db.SaveChangesAsync(); return existingAccount; } // Create new account using the AccountService var newAccount = await accounts.CreateAccount(userInfo); // Create the provider connection var newConnection = new AccountConnection { AccountId = newAccount.Id, Provider = provider, ProvidedIdentifier = userInfo.UserId!, }; db.AccountConnections.Add(newConnection); await db.SaveChangesAsync(); return newAccount; } }