feat(backend): add RequestDiscordTokenAsync method
This commit is contained in:
		
							parent
							
								
									2a7bd746aa
								
							
						
					
					
						commit
						6186eda092
					
				
					 12 changed files with 230 additions and 22 deletions
				
			
		|  | @ -6,12 +6,16 @@ using NodaTime; | |||
| namespace Foxnouns.Backend.Controllers.Authentication; | ||||
| 
 | ||||
| [Route("/api/v2/auth")] | ||||
| public class AuthController(Config config, KeyCacheService keyCacheSvc) : ApiControllerBase | ||||
| {    | ||||
| public class AuthController(Config config, KeyCacheService keyCacheSvc, ILogger logger) : ApiControllerBase | ||||
| { | ||||
|     [HttpPost("urls")] | ||||
|     [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UrlsResponse))] | ||||
|     [ProducesResponseType<UrlsResponse>(StatusCodes.Status200OK)] | ||||
|     public async Task<IActionResult> UrlsAsync() | ||||
|     { | ||||
|         logger.Debug("Generating auth URLs for Discord: {Discord}, Google: {Google}, Tumblr: {Tumblr}", | ||||
|             config.DiscordAuth.Enabled, | ||||
|             config.GoogleAuth.Enabled, | ||||
|             config.TumblrAuth.Enabled); | ||||
|         var state = HttpUtility.UrlEncode(await keyCacheSvc.GenerateAuthStateAsync()); | ||||
|         string? discord = null; | ||||
|         if (config.DiscordAuth.ClientId != null && config.DiscordAuth.ClientSecret != null) | ||||
|  | @ -35,4 +39,6 @@ public class AuthController(Config config, KeyCacheService keyCacheSvc) : ApiCon | |||
|         string Token, | ||||
|         Instant ExpiresAt | ||||
|     ); | ||||
| 
 | ||||
|     public record CallbackRequest(string Code, string State); | ||||
| } | ||||
|  | @ -1,16 +1,61 @@ | |||
| using Foxnouns.Backend.Database; | ||||
| using Foxnouns.Backend.Database.Models; | ||||
| using Foxnouns.Backend.Services; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using NodaTime; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Controllers.Authentication; | ||||
| 
 | ||||
| [Route("/api/v2/auth/discord")] | ||||
| public class DiscordAuthController(Config config, DatabaseContext db) : ApiControllerBase | ||||
| public class DiscordAuthController( | ||||
|     Config config, | ||||
|     ILogger logger, | ||||
|     IClock clock, | ||||
|     DatabaseContext db, | ||||
|     KeyCacheService keyCacheSvc, | ||||
|     AuthService authSvc, | ||||
|     RemoteAuthService remoteAuthSvc, | ||||
|     UserRendererService userRendererSvc) : ApiControllerBase | ||||
| { | ||||
|     [HttpPost("callback")] | ||||
|     public async Task<IActionResult> CallbackAsync([FromBody] AuthController.CallbackRequest req) | ||||
|     { | ||||
|         CheckRequirements(); | ||||
|         await keyCacheSvc.ValidateAuthStateAsync(req.State); | ||||
| 
 | ||||
|         var remoteUser = await remoteAuthSvc.RequestDiscordTokenAsync(req.Code, req.State); | ||||
|         var user = await authSvc.AuthenticateUserAsync(AuthType.Discord, remoteUser.Id); | ||||
|         if (user != null) return Ok(await GenerateUserTokenAsync(user)); | ||||
| 
 | ||||
|         logger.Debug("Discord user {Username} ({Id}) authenticated with no local account", remoteUser.Username, | ||||
|             remoteUser.Id); | ||||
| 
 | ||||
|         throw new NotImplementedException(); | ||||
|     } | ||||
| 
 | ||||
|     private async Task<AuthController.AuthResponse> GenerateUserTokenAsync(User user) | ||||
|     { | ||||
|         var frontendApp = await db.GetFrontendApplicationAsync(); | ||||
|         logger.Debug("Logging user {Id} in with Discord", user.Id); | ||||
| 
 | ||||
|         var (tokenStr, token) = | ||||
|             authSvc.GenerateToken(user, frontendApp, ["*"], clock.GetCurrentInstant() + Duration.FromDays(365)); | ||||
|         db.Add(token); | ||||
| 
 | ||||
|         logger.Debug("Generated token {TokenId} for {UserId}", user.Id, token.Id); | ||||
| 
 | ||||
|         await db.SaveChangesAsync(); | ||||
| 
 | ||||
|         return new AuthController.AuthResponse( | ||||
|             await userRendererSvc.RenderUserAsync(user, selfUser: user, renderMembers: false), | ||||
|             tokenStr, | ||||
|             token.ExpiresAt | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private void CheckRequirements() | ||||
|     { | ||||
|         if (config.DiscordAuth.ClientId == null || config.DiscordAuth.ClientSecret == null) | ||||
|         { | ||||
|         if (!config.DiscordAuth.Enabled) | ||||
|             throw new ApiError.BadRequest("Discord authentication is not enabled on this instance."); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -6,18 +6,31 @@ using NodaTime; | |||
| namespace Foxnouns.Backend.Controllers.Authentication; | ||||
| 
 | ||||
| [Route("/api/v2/auth/email")] | ||||
| public class EmailAuthController(DatabaseContext db, AuthService authSvc, UserRendererService userRendererSvc, IClock clock, ILogger logger) : ApiControllerBase | ||||
| public class EmailAuthController( | ||||
|     DatabaseContext db, | ||||
|     AuthService authSvc, | ||||
|     UserRendererService userRendererSvc, | ||||
|     IClock clock, | ||||
|     ILogger logger) : ApiControllerBase | ||||
| { | ||||
|     [HttpPost("login")] | ||||
|     [ProducesResponseType<AuthController.AuthResponse>(StatusCodes.Status200OK)] | ||||
|     public async Task<IActionResult> LoginAsync([FromBody] LoginRequest req) | ||||
|     { | ||||
|         var user = await authSvc.AuthenticateUserAsync(req.Email, req.Password); | ||||
|         var (user, authenticationResult) = await authSvc.AuthenticateUserAsync(req.Email, req.Password); | ||||
|         if (authenticationResult == AuthService.EmailAuthenticationResult.MfaRequired) | ||||
|             throw new NotImplementedException("MFA is not implemented yet"); | ||||
| 
 | ||||
|         var frontendApp = await db.GetFrontendApplicationAsync(); | ||||
|          | ||||
| 
 | ||||
|         logger.Debug("Logging user {Id} in with email and password", user.Id); | ||||
| 
 | ||||
|         var (tokenStr, token) = | ||||
|             authSvc.GenerateToken(user, frontendApp, ["*"], clock.GetCurrentInstant() + Duration.FromDays(365)); | ||||
|         db.Add(token); | ||||
| 
 | ||||
|         logger.Debug("Generated token {TokenId} for {UserId}", user.Id, token.Id); | ||||
| 
 | ||||
|         await db.SaveChangesAsync(); | ||||
| 
 | ||||
|         return Ok(new AuthController.AuthResponse( | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue