feat(backend): add add email address endpoint
This commit is contained in:
		
							parent
							
								
									7f971e8549
								
							
						
					
					
						commit
						5b17c716cb
					
				
					 6 changed files with 114 additions and 3 deletions
				
			
		|  | @ -183,10 +183,40 @@ public class EmailAuthController( | |||
| 
 | ||||
|     [HttpPost("add")] | ||||
|     [Authorize("*")] | ||||
|     public async Task<IActionResult> AddEmailAddressAsync() | ||||
|     public async Task<IActionResult> AddEmailAddressAsync([FromBody] AddEmailAddressRequest req) | ||||
|     { | ||||
|         _logger.Information("beep"); | ||||
|         var emails = await db | ||||
|             .AuthMethods.Where(m => m.UserId == CurrentUser!.Id && m.AuthType == AuthType.Email) | ||||
|             .ToListAsync(); | ||||
|         if (emails.Count > AuthUtils.MaxAuthMethodsPerType) | ||||
|         { | ||||
|             throw new ApiError.BadRequest( | ||||
|                 "Too many email addresses, maximum of 3 per account.", | ||||
|                 "email", | ||||
|                 null | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         var validPassword = await authService.ValidatePasswordAsync(CurrentUser!, req.Password); | ||||
|         if (!validPassword) | ||||
|         { | ||||
|             throw new ApiError.Forbidden("Invalid password"); | ||||
|         } | ||||
| 
 | ||||
|         var state = await keyCacheService.GenerateRegisterEmailStateAsync( | ||||
|             req.Email, | ||||
|             userId: CurrentUser!.Id | ||||
|         ); | ||||
| 
 | ||||
|         var emailExists = await db | ||||
|             .AuthMethods.Where(m => m.AuthType == AuthType.Email && m.RemoteId == req.Email) | ||||
|             .AnyAsync(); | ||||
|         if (emailExists) | ||||
|         { | ||||
|             return NoContent(); | ||||
|         } | ||||
| 
 | ||||
|         mailService.QueueAddEmailAddressEmail(req.Email, state, CurrentUser.Username); | ||||
|         return NoContent(); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ public class ApiError( | |||
|         IReadOnlyDictionary<string, IEnumerable<ValidationError>>? errors = null | ||||
|     ) : ApiError(message, statusCode: HttpStatusCode.BadRequest) | ||||
|     { | ||||
|         public BadRequest(string message, string field, object actualValue) | ||||
|         public BadRequest(string message, string field, object? actualValue) | ||||
|             : this( | ||||
|                 "Error validating input", | ||||
|                 new Dictionary<string, IEnumerable<ValidationError>> | ||||
|  |  | |||
							
								
								
									
										18
									
								
								Foxnouns.Backend/Mailables/AddEmailMailable.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Foxnouns.Backend/Mailables/AddEmailMailable.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| using Coravel.Mailer.Mail; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Mailables; | ||||
| 
 | ||||
| public class AddEmailMailable(Config config, AddEmailMailableView view) | ||||
|     : Mailable<AddEmailMailableView> | ||||
| { | ||||
|     public override void Build() | ||||
|     { | ||||
|         To(view.To).From(config.EmailAuth.From!).View("~/Views/Mail/AddEmail.cshtml", view); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| public class AddEmailMailableView : BaseView | ||||
| { | ||||
|     public required string Code { get; init; } | ||||
|     public required string Username { get; init; } | ||||
| } | ||||
|  | @ -129,6 +129,30 @@ public class AuthService(IClock clock, DatabaseContext db, ISnowflakeGenerator s | |||
|         return (user, EmailAuthenticationResult.AuthSuccessful); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Validates a user's password outside an authentication context, for when a password is required for changing | ||||
|     /// a setting, such as adding a new email address or changing passwords. | ||||
|     /// </summary> | ||||
|     public async Task<bool> ValidatePasswordAsync( | ||||
|         User user, | ||||
|         string password, | ||||
|         CancellationToken ct = default | ||||
|     ) | ||||
|     { | ||||
|         if (user.Password == null) | ||||
|         { | ||||
|             throw new FoxnounsError("Password for user supplied to ValidatePasswordAsync was null"); | ||||
|         } | ||||
| 
 | ||||
|         var pwResult = await Task.Run( | ||||
|             () => _passwordHasher.VerifyHashedPassword(user, user.Password!, password), | ||||
|             ct | ||||
|         ); | ||||
|         return pwResult | ||||
|             is PasswordVerificationResult.SuccessRehashNeeded | ||||
|                 or PasswordVerificationResult.Success; | ||||
|     } | ||||
| 
 | ||||
|     public enum EmailAuthenticationResult | ||||
|     { | ||||
|         AuthSuccessful, | ||||
|  |  | |||
|  | @ -33,4 +33,31 @@ public class MailService(ILogger logger, IMailer mailer, IQueue queue, Config co | |||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public void QueueAddEmailAddressEmail(string to, string code, string username) | ||||
|     { | ||||
|         _logger.Debug("Sending add email address email to {ToEmail}", to); | ||||
|         queue.QueueAsyncTask(async () => | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await mailer.SendAsync( | ||||
|                     new AddEmailMailable( | ||||
|                         config, | ||||
|                         new AddEmailMailableView | ||||
|                         { | ||||
|                             BaseUrl = config.BaseUrl, | ||||
|                             To = to, | ||||
|                             Code = code, | ||||
|                             Username = username, | ||||
|                         } | ||||
|                     ) | ||||
|                 ); | ||||
|             } | ||||
|             catch (Exception exc) | ||||
|             { | ||||
|                 _logger.Error(exc, "Sending add email address email"); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										12
									
								
								Foxnouns.Backend/Views/Mail/AddEmail.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Foxnouns.Backend/Views/Mail/AddEmail.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| @model Foxnouns.Backend.Mailables.AddEmailMailableView | ||||
| 
 | ||||
| <p> | ||||
|     Hello @@@Model.Username, please confirm adding this email address to your account by using the following link: | ||||
|     <br/> | ||||
|     <a href="@Model.BaseUrl/settings/auth/confirm-email/@Model.Code">Confirm your email address</a> | ||||
|     <br/> | ||||
|     Note that this link will expire in one hour. | ||||
| </p> | ||||
| <p> | ||||
|     If you didn't mean to link this email address to @@@Model.Username, feel free to ignore this email. | ||||
| </p> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue