Skip to content

Commit 563a9e2

Browse files
committed
add email confirmation feature
fixes #1056
1 parent 1b91582 commit 563a9e2

File tree

7 files changed

+56
-8
lines changed

7 files changed

+56
-8
lines changed

src/api/framework/Infrastructure/Identity/Tokens/TokenService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ public async Task<TokenResponse> GenerateTokenAsync(TokenGenerationCommand reque
5050
throw new UnauthorizedException("user is deactivated");
5151
}
5252

53+
if (!user.EmailConfirmed)
54+
{
55+
throw new UnauthorizedException("email not confirmed");
56+
}
57+
5358
if (currentTenant.Id != TenantConstants.Root.Id)
5459
{
5560
if (!currentTenant.IsActive)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using FSH.Framework.Core.Identity.Users.Abstractions;
2+
using Microsoft.AspNetCore.Builder;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Routing;
5+
6+
namespace FSH.Framework.Infrastructure.Identity.Users.Endpoints;
7+
public static class ConfirmEmailEndpoint
8+
{
9+
internal static RouteHandlerBuilder MapConfirmEmailEndpoint(this IEndpointRouteBuilder endpoints)
10+
{
11+
return endpoints.MapGet("/confirm-email", (string userId, string code, string tenant, IUserService service) =>
12+
{
13+
return service.ConfirmEmailAsync(userId, code, tenant, default);
14+
})
15+
.WithName(nameof(ConfirmEmailEndpoint))
16+
.WithSummary("confirm user email")
17+
.WithDescription("confirm user email")
18+
.AllowAnonymous();
19+
}
20+
}

src/api/framework/Infrastructure/Identity/Users/Endpoints/Extensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public static IEndpointRouteBuilder MapUserEndpoints(this IEndpointRouteBuilder
2121
app.MapAssignRolesToUserEndpoint();
2222
app.MapGetUserRolesEndpoint();
2323
app.MapGetUserAuditTrailEndpoint();
24+
app.MapConfirmEmailEndpoint();
2425
return app;
2526
}
2627
}

src/api/framework/Infrastructure/Identity/Users/Services/UserService.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,22 @@ private void EnsureValidTenant()
4646
}
4747
}
4848

49-
public Task<string> ConfirmEmailAsync(string userId, string code, string tenant, CancellationToken cancellationToken)
49+
public async Task<string> ConfirmEmailAsync(string userId, string code, string tenant, CancellationToken cancellationToken)
5050
{
51-
throw new NotImplementedException();
51+
EnsureValidTenant();
52+
53+
var user = await userManager.Users
54+
.Where(u => u.Id == userId && !u.EmailConfirmed)
55+
.FirstOrDefaultAsync(cancellationToken);
56+
57+
_ = user ?? throw new FshException("An error occurred while confirming E-Mail.");
58+
59+
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
60+
var result = await userManager.ConfirmEmailAsync(user, code);
61+
62+
return result.Succeeded
63+
? string.Format("Account Confirmed for E-Mail {0}. You can now use the /api/tokens endpoint to generate JWT.", user.Email)
64+
: throw new FshException(string.Format("An error occurred while confirming {0}", user.Email));
5265
}
5366

5467
public Task<string> ConfirmPhoneNumberAsync(string userId, string code)
@@ -111,7 +124,8 @@ public async Task<RegisterUserResponse> RegisterAsync(RegisterUserCommand reques
111124
UserName = request.UserName,
112125
PhoneNumber = request.PhoneNumber,
113126
IsActive = true,
114-
EmailConfirmed = true
127+
EmailConfirmed = false,
128+
PhoneNumberConfirmed = false,
115129
};
116130

117131
// register user

src/api/framework/Infrastructure/Tenant/Extensions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ public static IServiceCollection ConfigureMultitenancy(this IServiceCollection s
4444
})
4545
.WithClaimStrategy(FshClaims.Tenant)
4646
.WithHeaderStrategy(TenantConstants.Identifier)
47+
.WithDelegateStrategy(async context =>
48+
{
49+
if (context is not HttpContext httpContext)
50+
return null;
51+
if (!httpContext.Request.Query.TryGetValue("tenant", out var tenantIdentifier) || string.IsNullOrEmpty(tenantIdentifier))
52+
return null;
53+
return await Task.FromResult(tenantIdentifier.ToString());
54+
})
4755
.WithDistributedCacheStore(TimeSpan.FromMinutes(60))
4856
.WithEFCoreStore<TenantDbContext, FshTenantInfo>();
4957
services.AddScoped<ITenantService, TenantService>();

src/api/server/appsettings.Development.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"DatabaseOptions": {
33
"Provider": "postgresql",
4-
"ConnectionString": "Server=localhost;Database=fullstackhero;Port=5432;User Id=admin;Password=admin;"
4+
"ConnectionString": "Server=192.168.1.110;Database=fsh;User Id=postgres;Password=password"
55
},
66
"OriginOptions": {
77
"OriginUrl": "https://localhost:7000"
@@ -23,8 +23,8 @@
2323
"From": "[email protected]",
2424
"Host": "smtp.ethereal.email",
2525
"Port": 587,
26-
"UserName": "sherman.oconnell47@ethereal.email",
27-
"Password": "KbuTCFv4J6Fy7256vh",
26+
"UserName": "ruth.ruecker@ethereal.email",
27+
"Password": "wygzuX6kpcK6AfDJcd",
2828
"DisplayName": "Mukesh Murugan"
2929
},
3030
"CorsOptions": {

src/api/server/appsettings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
"From": "[email protected]",
2424
"Host": "smtp.ethereal.email",
2525
"Port": 587,
26-
"UserName": "sherman.oconnell47@ethereal.email",
27-
"Password": "KbuTCFv4J6Fy7256vh",
26+
"UserName": "ruth.ruecker@ethereal.email",
27+
"Password": "wygzuX6kpcK6AfDJcd",
2828
"DisplayName": "Mukesh Murugan"
2929
},
3030
"CorsOptions": {

0 commit comments

Comments
 (0)