Adds profile and settings pages
This commit is contained in:
@@ -24,6 +24,10 @@ orchestrator.RegisterModuleServices(builder.Services);
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
options.Conventions.Add(new ApiPrefixConvention("api/v1"));
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -5,6 +5,8 @@ public record LoginRequest(string Email, string Password);
|
||||
public record InviteUserRequest(string Email, string Role);
|
||||
public record CompleteSetupRequest(string Token, string Password);
|
||||
public record ChangeRoleRequest(string NewRole);
|
||||
public record UpdateProfileRequest(string Name, string Email);
|
||||
public record ChangePasswordRequest(string CurrentPassword, string NewPassword);
|
||||
public record SetUserActiveRequest(bool IsActive);
|
||||
public record PendingInvitationInfo(Guid Id, string Email, string Role, string Token, DateTimeOffset CreatedAt);
|
||||
public record UserDto(
|
||||
|
||||
@@ -77,6 +77,19 @@ public class AuthService : IAuthService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ChangePasswordAsync(Guid userId, string currentPassword, string newPassword)
|
||||
{
|
||||
var user = await _userManager.FindByIdAsync(userId.ToString())
|
||||
?? throw new UnauthorizedException("Gebruiker niet gevonden.");
|
||||
|
||||
var result = await _userManager.ChangePasswordAsync(user, currentPassword, newPassword);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
var errors = string.Join(", ", result.Errors.Select(e => e.Description));
|
||||
throw new ValidationException(errors);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<TokenResponse> GenerateTokenResponseAsync(ApplicationUser user)
|
||||
{
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
|
||||
@@ -21,4 +21,9 @@ public interface IAuthService
|
||||
/// Trekt een refresh token in (uitloggen).
|
||||
/// </summary>
|
||||
Task RevokeTokenAsync(string refreshToken);
|
||||
|
||||
/// <summary>
|
||||
/// Wijzigt het wachtwoord van de opgegeven gebruiker.
|
||||
/// </summary>
|
||||
Task ChangePasswordAsync(Guid userId, string currentPassword, string newPassword);
|
||||
}
|
||||
|
||||
@@ -22,10 +22,20 @@ public class AvailabilityMiddleware
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// Paths that are always accessible regardless of system availability.
|
||||
// Auth and Setup must stay open so admins can log in and the frontend
|
||||
// can determine whether the system is initialized.
|
||||
private static readonly string[] _bypassPrefixes =
|
||||
[
|
||||
"/api/v1/Availability/status",
|
||||
"/api/v1/Auth/",
|
||||
"/api/v1/Setup/status",
|
||||
];
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, IAvailabilityService availabilityService)
|
||||
{
|
||||
// Bypass voor status endpoint
|
||||
if (context.Request.Path.StartsWithSegments("/api/availability/status"))
|
||||
var path = context.Request.Path.Value ?? string.Empty;
|
||||
if (_bypassPrefixes.Any(p => path.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -61,6 +62,20 @@ public class AuthController : ControllerBase
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("change-password")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
|
||||
{
|
||||
var userIdClaim = User.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? User.FindFirstValue("sub");
|
||||
|
||||
if (!Guid.TryParse(userIdClaim, out var userId))
|
||||
return Unauthorized();
|
||||
|
||||
await _authService.ChangePasswordAsync(userId, request.CurrentPassword, request.NewPassword);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private void SetTokenCookie(string refreshToken)
|
||||
{
|
||||
Response.Cookies.Append("refreshToken", refreshToken, GetCookieOptions());
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -75,6 +76,51 @@ public class UsersController : ControllerBase
|
||||
return Ok(result.OrderBy(u => u.CreatedAt));
|
||||
}
|
||||
|
||||
[HttpPut("me")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> UpdateProfile([FromBody] UpdateProfileRequest request)
|
||||
{
|
||||
var userIdClaim = User.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? User.FindFirstValue("sub");
|
||||
|
||||
if (!Guid.TryParse(userIdClaim, out var userId))
|
||||
return Unauthorized();
|
||||
|
||||
var user = await _userManager.FindByIdAsync(userId.ToString());
|
||||
if (user is null) return NotFound();
|
||||
|
||||
// Check email uniqueness if email is changing
|
||||
if (!string.Equals(user.Email, request.Email, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var existing = await _userManager.FindByEmailAsync(request.Email);
|
||||
if (existing is not null)
|
||||
return BadRequest(new { detail = "Email is already in use." });
|
||||
}
|
||||
|
||||
user.DisplayName = request.Name;
|
||||
user.Email = request.Email;
|
||||
user.UserName = request.Email;
|
||||
user.NormalizedEmail = request.Email.ToUpperInvariant();
|
||||
user.NormalizedUserName = request.Email.ToUpperInvariant();
|
||||
|
||||
var result = await _userManager.UpdateAsync(user);
|
||||
if (!result.Succeeded)
|
||||
return StatusCode(500, new { detail = "Failed to update profile." });
|
||||
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
var role = roles.FirstOrDefault() ?? "User";
|
||||
|
||||
return Ok(new UserDto(
|
||||
user.Id,
|
||||
user.Email ?? string.Empty,
|
||||
user.DisplayName ?? user.Email ?? string.Empty,
|
||||
role,
|
||||
user.IsActive,
|
||||
user.CreatedAt,
|
||||
false,
|
||||
null));
|
||||
}
|
||||
|
||||
[HttpPost("invite")]
|
||||
public async Task<IActionResult> Invite([FromBody] InviteUserRequest request)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user