-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b5c3889
commit 443d557
Showing
41 changed files
with
1,569 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
src/PlaceApi.Application/Authentication/Notifications/Confirmation/SendConfirmationEmail.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
using MediatR; | ||
using PlaceApi.Domain.Authentication.Entities; | ||
|
||
namespace PlaceApi.Application.Authentication.Notifications.Confirmation; | ||
|
||
public record SendConfirmationEmail(ApplicationUser User, string Email, bool IsChange = false) | ||
: INotification; |
56 changes: 56 additions & 0 deletions
56
...Api.Application/Authentication/Notifications/Confirmation/SendConfirmationEmailHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
using System; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using MediatR; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Identity; | ||
using Microsoft.AspNetCore.Routing; | ||
using Microsoft.AspNetCore.WebUtilities; | ||
using PlaceApi.Domain.Authentication.Entities; | ||
|
||
namespace PlaceApi.Application.Authentication.Notifications.Confirmation; | ||
|
||
public class SendConfirmationEmailHandler( | ||
UserManager<ApplicationUser> userManager, | ||
IHttpContextAccessor httpContextAccessor, | ||
LinkGenerator linkGenerator, | ||
IEmailSender<ApplicationUser> emailSender | ||
) : INotificationHandler<SendConfirmationEmail> | ||
{ | ||
public async Task Handle( | ||
SendConfirmationEmail notification, | ||
CancellationToken cancellationToken | ||
) | ||
{ | ||
string code = notification.IsChange | ||
? await userManager.GenerateChangeEmailTokenAsync(notification.User, notification.Email) | ||
: await userManager.GenerateEmailConfirmationTokenAsync(notification.User); | ||
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); | ||
|
||
string userId = await userManager.GetUserIdAsync(notification.User); | ||
RouteValueDictionary routeValues = new() { ["userId"] = userId, ["code"] = code }; | ||
|
||
if (notification.IsChange) | ||
{ | ||
routeValues.Add("changedEmail", notification.Email); | ||
} | ||
|
||
var confirmEmailEndpoint = linkGenerator.GetUriByName( | ||
httpContextAccessor.HttpContext!, | ||
"ConfirmEmail", | ||
routeValues | ||
); | ||
string confirmEmailUrl = | ||
confirmEmailEndpoint | ||
?? throw new NotSupportedException( | ||
$"Could not find endpoint named '{confirmEmailEndpoint}'." | ||
); | ||
|
||
await emailSender.SendConfirmationLinkAsync( | ||
notification.User, | ||
notification.Email, | ||
confirmEmailUrl | ||
); | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/PlaceApi.Application/Authentication/Register/RegisterCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
using MediatR; | ||
using Microsoft.AspNetCore.Http.HttpResults; | ||
|
||
namespace PlaceApi.Application.Authentication.Register; | ||
|
||
public record RegisterCommand(string UserName, string Email, string Password) | ||
: IRequest<Results<Ok<RegisterResult>, ValidationProblem>>; |
89 changes: 89 additions & 0 deletions
89
src/PlaceApi.Application/Authentication/Register/RegisterCommandHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel.DataAnnotations; | ||
using System.Diagnostics; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using MediatR; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Http.HttpResults; | ||
using Microsoft.AspNetCore.Identity; | ||
using PlaceApi.Application.Authentication.Notifications.Confirmation; | ||
using PlaceApi.Domain.Authentication.Entities; | ||
|
||
namespace PlaceApi.Application.Authentication.Register; | ||
|
||
public class RegisterCommandHandler( | ||
UserManager<ApplicationUser> userManager, | ||
IUserStore<ApplicationUser> userStore, | ||
IPublisher publisher | ||
) : IRequestHandler<RegisterCommand, Results<Ok<RegisterResult>, ValidationProblem>> | ||
{ | ||
private static readonly EmailAddressAttribute EmailAddressAttribute = new(); | ||
|
||
public async Task<Results<Ok<RegisterResult>, ValidationProblem>> Handle( | ||
RegisterCommand request, | ||
CancellationToken cancellationToken | ||
) | ||
{ | ||
if (!userManager.SupportsUserEmail) | ||
{ | ||
throw new NotSupportedException( | ||
$"{nameof(RegisterCommandHandler)} requires a user store with email support." | ||
); | ||
} | ||
|
||
if (string.IsNullOrEmpty(request.Email) || !EmailAddressAttribute.IsValid(request.Email)) | ||
{ | ||
return CreateValidationProblem( | ||
IdentityResult.Failed(userManager.ErrorDescriber.InvalidEmail(request.Email)) | ||
); | ||
} | ||
|
||
ApplicationUser user = new(); | ||
IUserEmailStore<ApplicationUser> emailStore = (IUserEmailStore<ApplicationUser>)userStore; | ||
await userStore.SetUserNameAsync(user, request.UserName, CancellationToken.None); | ||
await emailStore.SetEmailAsync(user, request.Email, CancellationToken.None); | ||
IdentityResult result = await userManager.CreateAsync(user, request.Password); | ||
|
||
if (!result.Succeeded) | ||
{ | ||
return CreateValidationProblem(result); | ||
} | ||
|
||
var registered = await userManager.FindByEmailAsync(request.Email); | ||
|
||
await publisher.Publish( | ||
new SendConfirmationEmail(registered!, registered!.Email!), | ||
cancellationToken | ||
); | ||
|
||
return TypedResults.Ok(new RegisterResult(registered.Id, registered.Email!)); | ||
} | ||
|
||
private static ValidationProblem CreateValidationProblem(IdentityResult result) | ||
{ | ||
Debug.Assert(!result.Succeeded); | ||
Dictionary<string, string[]> errorDictionary = new(1); | ||
|
||
foreach (IdentityError error in result.Errors) | ||
{ | ||
string[] newDescriptions; | ||
|
||
if (errorDictionary.TryGetValue(error.Code, out string[]? descriptions)) | ||
{ | ||
newDescriptions = new string[descriptions.Length + 1]; | ||
Array.Copy(descriptions, newDescriptions, descriptions.Length); | ||
newDescriptions[descriptions.Length] = error.Description; | ||
} | ||
else | ||
{ | ||
newDescriptions = [error.Description]; | ||
} | ||
|
||
errorDictionary[error.Code] = newDescriptions; | ||
} | ||
|
||
return TypedResults.ValidationProblem(errorDictionary); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/PlaceApi.Application/Authentication/Register/RegisterErrors.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using ErrorOr; | ||
|
||
namespace PlaceApi.Application.Authentication.Register; | ||
|
||
public static class RegisterErrors | ||
{ | ||
public static Error InvalidEmail { get; } = | ||
Error.Validation( | ||
code: nameof(InvalidEmail), | ||
description: "The email provided is not a valid. " | ||
); | ||
} |
5 changes: 5 additions & 0 deletions
5
src/PlaceApi.Application/Authentication/Register/RegisterResult.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
using System; | ||
|
||
namespace PlaceApi.Application.Authentication.Register; | ||
|
||
public record RegisterResult(string UserId, string Email); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using MediatR; | ||
|
||
namespace PlaceApi.Application.ConfirmEmail; | ||
|
||
using ErrorOr; | ||
|
||
public record ConfirmEmailCommand(string UserId, string Code, string? ChangedEmail) | ||
: IRequest<ErrorOr<bool>>; |
60 changes: 60 additions & 0 deletions
60
src/PlaceApi.Application/ConfirmEmail/ConfirmEmailCommandHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
using System; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using ErrorOr; | ||
using MediatR; | ||
using Microsoft.AspNetCore.Identity; | ||
using Microsoft.AspNetCore.WebUtilities; | ||
using PlaceApi.Domain.Authentication.Entities; | ||
|
||
namespace PlaceApi.Application.ConfirmEmail; | ||
|
||
public class ConfirmEmailCommandHandler(UserManager<ApplicationUser> userManager) | ||
: IRequestHandler<ConfirmEmailCommand, ErrorOr<bool>> | ||
{ | ||
public async Task<ErrorOr<bool>> Handle( | ||
ConfirmEmailCommand request, | ||
CancellationToken cancellationToken | ||
) | ||
{ | ||
var code = request.Code; | ||
|
||
if (await userManager.FindByIdAsync(request.UserId) is not { } user) | ||
{ | ||
return Error.Unauthorized(); | ||
} | ||
|
||
try | ||
{ | ||
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code)); | ||
} | ||
catch (FormatException) | ||
{ | ||
return Error.Unauthorized(); | ||
} | ||
|
||
IdentityResult result; | ||
|
||
if (string.IsNullOrEmpty(request.ChangedEmail)) | ||
{ | ||
result = await userManager.ConfirmEmailAsync(user, code); | ||
} | ||
else | ||
{ | ||
result = await userManager.ChangeEmailAsync(user, request.ChangedEmail, code); | ||
|
||
if (result.Succeeded) | ||
{ | ||
result = await userManager.SetUserNameAsync(user, request.ChangedEmail); | ||
} | ||
} | ||
|
||
if (!result.Succeeded) | ||
{ | ||
return Error.Unauthorized(); | ||
} | ||
|
||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace PlaceApi.Application; | ||
|
||
public static class DependencyInjection | ||
{ | ||
public static IServiceCollection AddApplication(this IServiceCollection services) | ||
{ | ||
services.AddMediatR(options => | ||
{ | ||
options.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly); | ||
}); | ||
return services; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
src/PlaceApi.Domain/Authentication/Entities/ApplicationUser.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
using Microsoft.AspNetCore.Identity; | ||
|
||
namespace PlaceApi.Domain.Authentication.Entities; | ||
|
||
/// <inheritdoc /> | ||
public class ApplicationUser : IdentityUser; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
src/PlaceApi.Infrastructure/Authentication/DependencyInjection.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
using PlaceApi.Infrastructure.Authentication.Persistence; | ||
|
||
namespace PlaceApi.Infrastructure.Authentication; | ||
|
||
public static class DependencyInjection | ||
{ | ||
public static IServiceCollection AddAuth(this IServiceCollection services) | ||
{ | ||
services.AddAuthenticationPersistence(); | ||
|
||
return services; | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
src/PlaceApi.Infrastructure/Authentication/Persistence/AuthenticationDbContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore; | ||
using PlaceApi.Domain.Authentication.Entities; | ||
|
||
namespace PlaceApi.Infrastructure.Authentication.Persistence; | ||
|
||
public class AuthenticationDbContext : IdentityDbContext<ApplicationUser> | ||
{ | ||
public AuthenticationDbContext(DbContextOptions<AuthenticationDbContext> options) | ||
: base(options) { } | ||
} |
Oops, something went wrong.