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")] |     [HttpPost("add")] | ||||||
|     [Authorize("*")] |     [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(); |         return NoContent(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ public class ApiError( | ||||||
|         IReadOnlyDictionary<string, IEnumerable<ValidationError>>? errors = null |         IReadOnlyDictionary<string, IEnumerable<ValidationError>>? errors = null | ||||||
|     ) : ApiError(message, statusCode: HttpStatusCode.BadRequest) |     ) : ApiError(message, statusCode: HttpStatusCode.BadRequest) | ||||||
|     { |     { | ||||||
|         public BadRequest(string message, string field, object actualValue) |         public BadRequest(string message, string field, object? actualValue) | ||||||
|             : this( |             : this( | ||||||
|                 "Error validating input", |                 "Error validating input", | ||||||
|                 new Dictionary<string, IEnumerable<ValidationError>> |                 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); |         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 |     public enum EmailAuthenticationResult | ||||||
|     { |     { | ||||||
|         AuthSuccessful, |         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