deleting password

This commit is contained in:
john 2025-07-19 14:10:01 +02:00
parent 36d8cc9a4d
commit 2519fc77d2
15 changed files with 237 additions and 47 deletions

View file

@ -4,6 +4,7 @@ using Femto.Api.Sessions;
using Femto.Common;
using Femto.Modules.Auth.Application;
using Femto.Modules.Auth.Application.Dto;
using Femto.Modules.Auth.Contracts;
using Femto.Modules.Auth.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
@ -26,10 +27,10 @@ internal class SessionAuthenticationHandler(
if (user is null)
user = await this.TryAuthenticateWithRememberMeToken();
if (user is null)
return AuthenticateResult.NoResult();
var claims = new List<Claim>
{
new(ClaimTypes.Name, user.Username),
@ -41,7 +42,11 @@ internal class SessionAuthenticationHandler(
var identity = new ClaimsIdentity(claims, this.Scheme.Name);
var principal = new ClaimsPrincipal(identity);
currentUserContext.CurrentUser = new CurrentUser(user.Id, user.Username);
currentUserContext.CurrentUser = new CurrentUser(
user.Id,
user.Username,
user.Roles.Contains(Role.SuperUser)
);
return AuthenticateResult.Success(new AuthenticationTicket(principal, this.Scheme.Name));
}
@ -99,21 +104,23 @@ internal class SessionAuthenticationHandler(
* if it is valid, create a new weak session, return the user
* if it is almost expired, refresh it
*/
var rememberMeToken = this.Context.GetRememberMeToken();
if (rememberMeToken is null)
return null;
var (user, newRememberMeToken) = await authService.GetUserWithRememberMeToken(rememberMeToken);
var (user, newRememberMeToken) = await authService.GetUserWithRememberMeToken(
rememberMeToken
);
if (user is null)
return null;
var session = await authService.CreateWeakSession(user.Id);
this.Context.SetSession(session, user);
if (newRememberMeToken is not null)
this.Context.SetRememberMeToken(newRememberMeToken);

View file

@ -28,9 +28,9 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService
return this.BadRequest();
var (user, session) = result;
HttpContext.SetSession(session, user);
if (request.RememberMe)
{
var newRememberMeToken = await authService.CreateRememberMeToken(user.Id);
@ -41,7 +41,10 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService
}
[HttpPost("register")]
public async Task<ActionResult<RegisterResponse>> Register([FromBody] RegisterRequest request, CancellationToken cancellationToken)
public async Task<ActionResult<RegisterResponse>> Register(
[FromBody] RegisterRequest request,
CancellationToken cancellationToken
)
{
var (user, session) = await authService.CreateUserWithCredentials(
request.Username,
@ -51,7 +54,7 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService
);
HttpContext.SetSession(session, user);
if (request.RememberMe)
{
var newRememberMeToken = await authService.CreateRememberMeToken(user.Id);
@ -65,6 +68,60 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService
);
}
[HttpPost("change-password")]
public async Task<ActionResult> ChangePassword(
[FromBody] ChangePasswordRequestBody req,
CancellationToken cancellationToken
)
{
if (currentUserContext.CurrentUser is not {} user)
return this.BadRequest();
// superuser do what superuser want
if (!user.IsSuperUser)
{
if (user.Id != req.UserId)
return this.BadRequest();
var session = await authService.GetSession(this.HttpContext.GetSessionId()!);
// require strong authentication to change password
// the user can re-enter their password
if (session is null || !session.IsStronglyAuthenticated)
return this.BadRequest();
}
await authService.ChangePassword(req.UserId, req.NewPassword, cancellationToken);
// TODO would be better do handle this from inside the auth service. maybe just have it happen in a post-save event handler?
await authService.InvalidateUserSessions(req.UserId, cancellationToken);
return this.Ok(new {});
}
[HttpPost("delete-current-session")]
public async Task<ActionResult> DeleteSessionV2()
{
var sessionId = HttpContext.GetSessionId();
if (sessionId is not null)
{
await authService.DeleteSession(sessionId);
HttpContext.DeleteSession();
}
var rememberMeToken = HttpContext.GetRememberMeToken();
if (rememberMeToken is not null)
{
await authService.DeleteRememberMeToken(rememberMeToken);
HttpContext.DeleteRememberMeToken();
}
return Ok(new { });
}
[Obsolete("use POST /auth/delete-current-session")]
[HttpDelete("session")]
public async Task<ActionResult> DeleteSession()
{
@ -111,6 +168,7 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService
);
}
[Obsolete("use POST /auth/create-signup-code")]
[HttpPost("signup-codes")]
[Authorize(Roles = "SuperUser")]
public async Task<ActionResult> CreateSignupCode(
@ -123,6 +181,7 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService
return Ok(new { });
}
[Obsolete("use GET /auth/list-signup-codes")]
[HttpGet("signup-codes")]
[Authorize(Roles = "SuperUser")]
public async Task<ActionResult<ListSignupCodesResult>> ListSignupCodes(
@ -142,4 +201,36 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService
))
);
}
[HttpPost("create-signup-code")]
[Authorize(Roles = "SuperUser")]
public async Task<ActionResult> CreateSignupCodeV2(
[FromBody] CreateSignupCodeRequest request,
CancellationToken cancellationToken
)
{
await authService.AddSignupCode(request.Code, request.Name, cancellationToken);
return Ok(new { });
}
[HttpGet("list-signup-codes")]
[Authorize(Roles = "SuperUser")]
public async Task<ActionResult<ListSignupCodesResult>> ListSignupCodesV2(
CancellationToken cancellationToken
)
{
var codes = await authService.GetSignupCodes(cancellationToken);
return new ListSignupCodesResult(
codes.Select(c => new SignupCodeDto(
c.Code,
c.Email,
c.Name,
c.RedeemedByUserId,
c.RedeemedByUsername,
c.ExpiresOn
))
);
}
}

View file

@ -0,0 +1,3 @@
namespace Femto.Api.Controllers.Auth;
public record ChangePasswordRequestBody(Guid UserId, string NewPassword);

View file

@ -46,7 +46,7 @@ builder.Services.AddHostedService(_ => eventBus);
builder.Services.InitializeBlogModule(connectionString, eventBus, loggerFactory);
builder.Services.InitializeMediaModule(connectionString, blobStorageRoot);
builder.Services.InitializeAuthenticationModule(connectionString, eventBus, loggerFactory);
builder.Services.InitializeAuthenticationModule(connectionString, eventBus, loggerFactory, TimeProvider.System);
builder.Services.AddScoped<CurrentUserContext, CurrentUserContext>();
builder.Services.AddScoped<ICurrentUserContext>(s => s.GetRequiredService<CurrentUserContext>());