Login with Apple

This commit is contained in:
2025-06-15 17:29:30 +08:00
parent bf013a108b
commit 16ff5588b9
14 changed files with 4007 additions and 149 deletions

View File

@ -1,5 +1,8 @@
using DysonNetwork.Sphere.Account;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using NodaTime;
namespace DysonNetwork.Sphere.Auth.OpenId;
@ -8,7 +11,7 @@ namespace DysonNetwork.Sphere.Auth.OpenId;
public class OidcController(
IServiceProvider serviceProvider,
AppDatabase db,
Account.AccountService accountService,
AccountService accounts,
AuthService authService
)
: ControllerBase
@ -35,6 +38,51 @@ public class OidcController(
}
}
/// <summary>
/// Mobile Apple Sign In endpoint
/// Handles Apple authentication directly from mobile apps
/// </summary>
[HttpPost("apple/mobile")]
public async Task<ActionResult<AuthController.TokenExchangeResponse>> 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
@ -45,4 +93,51 @@ public class OidcController(
_ => throw new ArgumentException($"Unsupported provider: {provider}")
};
}
private async Task<Account.Account> 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;
}
}