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…
Reference in a new issue