feat: link fediverse account to existing user
This commit is contained in:
		
							parent
							
								
									03209e4028
								
							
						
					
					
						commit
						57e1ec09c0
					
				
					 17 changed files with 335 additions and 95 deletions
				
			
		|  | @ -104,21 +104,9 @@ public class DiscordAuthController( | |||
|     { | ||||
|         CheckRequirements(); | ||||
| 
 | ||||
|         var existingAccounts = await db | ||||
|             .AuthMethods.Where(m => m.UserId == CurrentUser!.Id && m.AuthType == AuthType.Discord) | ||||
|             .CountAsync(); | ||||
|         if (existingAccounts > AuthUtils.MaxAuthMethodsPerType) | ||||
|         { | ||||
|             throw new ApiError.BadRequest( | ||||
|                 "Too many linked Discord accounts, maximum of 3 per account." | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         var state = HttpUtility.UrlEncode( | ||||
|             await keyCacheService.GenerateAddExtraAccountStateAsync( | ||||
|                 AuthType.Discord, | ||||
|                 CurrentUser!.Id | ||||
|             ) | ||||
|         var state = await remoteAuthService.ValidateAddAccountRequestAsync( | ||||
|             CurrentUser!.Id, | ||||
|             AuthType.Discord | ||||
|         ); | ||||
| 
 | ||||
|         var url = | ||||
|  | @ -138,12 +126,11 @@ public class DiscordAuthController( | |||
|     { | ||||
|         CheckRequirements(); | ||||
| 
 | ||||
|         var accountState = await keyCacheService.GetAddExtraAccountStateAsync(req.State); | ||||
|         if ( | ||||
|             accountState is not { AuthType: AuthType.Discord } | ||||
|             || accountState.UserId != CurrentUser!.Id | ||||
|         ) | ||||
|             throw new ApiError.BadRequest("Invalid state", "state", req.State); | ||||
|         await remoteAuthService.ValidateAddAccountStateAsync( | ||||
|             req.State, | ||||
|             CurrentUser!.Id, | ||||
|             AuthType.Discord | ||||
|         ); | ||||
| 
 | ||||
|         var remoteUser = await remoteAuthService.RequestDiscordTokenAsync(req.Code); | ||||
|         try | ||||
|  |  | |||
|  | @ -1,5 +1,8 @@ | |||
| using System.Net; | ||||
| using EntityFramework.Exceptions.Common; | ||||
| using Foxnouns.Backend.Database; | ||||
| using Foxnouns.Backend.Database.Models; | ||||
| using Foxnouns.Backend.Middleware; | ||||
| using Foxnouns.Backend.Services; | ||||
| using Foxnouns.Backend.Services.Auth; | ||||
| using Foxnouns.Backend.Utils; | ||||
|  | @ -15,13 +18,14 @@ public class FediverseAuthController( | |||
|     DatabaseContext db, | ||||
|     FediverseAuthService fediverseAuthService, | ||||
|     AuthService authService, | ||||
|     RemoteAuthService remoteAuthService, | ||||
|     KeyCacheService keyCacheService | ||||
| ) : ApiControllerBase | ||||
| { | ||||
|     private readonly ILogger _logger = logger.ForContext<FediverseAuthController>(); | ||||
| 
 | ||||
|     [HttpGet] | ||||
|     [ProducesResponseType<FediverseUrlResponse>(statusCode: StatusCodes.Status200OK)] | ||||
|     [ProducesResponseType<AuthController.SingleUrlResponse>(statusCode: StatusCodes.Status200OK)] | ||||
|     public async Task<IActionResult> GetFediverseUrlAsync( | ||||
|         [FromQuery] string instance, | ||||
|         [FromQuery] bool forceRefresh = false | ||||
|  | @ -31,7 +35,7 @@ public class FediverseAuthController( | |||
|             throw new ApiError.BadRequest("Not a valid domain.", "instance", instance); | ||||
| 
 | ||||
|         var url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh); | ||||
|         return Ok(new FediverseUrlResponse(url)); | ||||
|         return Ok(new AuthController.SingleUrlResponse(url)); | ||||
|     } | ||||
| 
 | ||||
|     [HttpPost("callback")] | ||||
|  | @ -118,9 +122,74 @@ public class FediverseAuthController( | |||
|         return Ok(await authService.GenerateUserTokenAsync(user)); | ||||
|     } | ||||
| 
 | ||||
|     public record CallbackRequest(string Instance, string Code, string State); | ||||
|     [HttpGet("add-account")] | ||||
|     [Authorize("*")] | ||||
|     public async Task<IActionResult> AddFediverseAccountAsync( | ||||
|         [FromQuery] string instance, | ||||
|         [FromQuery] bool forceRefresh = false | ||||
|     ) | ||||
|     { | ||||
|         if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.')) | ||||
|             throw new ApiError.BadRequest("Not a valid domain.", "instance", instance); | ||||
| 
 | ||||
|     private record FediverseUrlResponse(string Url); | ||||
|         var state = await remoteAuthService.ValidateAddAccountRequestAsync( | ||||
|             CurrentUser!.Id, | ||||
|             AuthType.Fediverse, | ||||
|             instance | ||||
|         ); | ||||
| 
 | ||||
|         var url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh, state); | ||||
|         return Ok(new AuthController.SingleUrlResponse(url)); | ||||
|     } | ||||
| 
 | ||||
|     [HttpPost("add-account/callback")] | ||||
|     [Authorize("*")] | ||||
|     public async Task<IActionResult> AddAccountCallbackAsync([FromBody] CallbackRequest req) | ||||
|     { | ||||
|         await remoteAuthService.ValidateAddAccountStateAsync( | ||||
|             req.State, | ||||
|             CurrentUser!.Id, | ||||
|             AuthType.Fediverse, | ||||
|             req.Instance | ||||
|         ); | ||||
| 
 | ||||
|         var app = await fediverseAuthService.GetApplicationAsync(req.Instance); | ||||
|         var remoteUser = await fediverseAuthService.GetRemoteFediverseUserAsync(app, req.Code); | ||||
|         try | ||||
|         { | ||||
|             var authMethod = await authService.AddAuthMethodAsync( | ||||
|                 CurrentUser.Id, | ||||
|                 AuthType.Fediverse, | ||||
|                 remoteUser.Id, | ||||
|                 remoteUser.Username, | ||||
|                 app | ||||
|             ); | ||||
|             _logger.Debug( | ||||
|                 "Added new Fediverse auth method {AuthMethodId} to user {UserId}", | ||||
|                 authMethod.Id, | ||||
|                 CurrentUser.Id | ||||
|             ); | ||||
| 
 | ||||
|             return Ok( | ||||
|                 new AuthController.AddOauthAccountResponse( | ||||
|                     authMethod.Id, | ||||
|                     AuthType.Fediverse, | ||||
|                     authMethod.RemoteId, | ||||
|                     $"{authMethod.RemoteUsername}@{app.Domain}" | ||||
|                 ) | ||||
|             ); | ||||
|         } | ||||
|         catch (UniqueConstraintException) | ||||
|         { | ||||
|             throw new ApiError( | ||||
|                 "That account is already linked.", | ||||
|                 HttpStatusCode.BadRequest, | ||||
|                 ErrorCode.AccountAlreadyLinked | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public record CallbackRequest(string Instance, string Code, string State); | ||||
| 
 | ||||
|     private record FediverseTicketData( | ||||
|         Snowflake ApplicationId, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue