Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup Identity #443

Merged
merged 18 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor profile into personal and users
  • Loading branch information
fretje committed Feb 7, 2022
commit 6d703a76906d0b25dce4adfcbce87794df6fe422
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace FSH.WebApi.Application.Identity.Users.Profile;
namespace FSH.WebApi.Application.Identity.Users;

public class CreateProfileRequest
public class CreateUserRequest
{
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
namespace FSH.WebApi.Application.Identity.Users.Profile;
namespace FSH.WebApi.Application.Identity.Users;

public class CreateProfileRequestValidator : CustomValidator<CreateProfileRequest>
public class CreateUserRequestValidator : CustomValidator<CreateUserRequest>
{
public CreateProfileRequestValidator(IUserService userService, IStringLocalizer<CreateProfileRequestValidator> localizer)
public CreateUserRequestValidator(IUserService userService, IStringLocalizer<CreateUserRequestValidator> localizer)
{
RuleFor(u => u.Email).Cascade(CascadeMode.Stop)
.NotEmpty()
Expand Down
15 changes: 14 additions & 1 deletion src/Core/Application/Identity/Users/IUserService.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System.Security.Claims;
using FSH.WebApi.Application.Identity.Users.Password;

namespace FSH.WebApi.Application.Identity.Users;

public interface IUserService : ITransientService
Expand All @@ -15,12 +18,22 @@ public interface IUserService : ITransientService
Task<UserDetailsDto> GetAsync(string userId, CancellationToken cancellationToken);

Task<List<UserRoleDto>> GetRolesAsync(string userId, CancellationToken cancellationToken);

Task<string> AssignRolesAsync(string userId, UserRolesRequest request, CancellationToken cancellationToken);

Task<List<string>> GetPermissionsAsync(string userId, CancellationToken cancellationToken);
Task<bool> HasPermissionAsync(string userId, string permission, CancellationToken cancellationToken = default);
Task InvalidatePermissionCacheAsync(string userId, CancellationToken cancellationToken);

Task ToggleStatusAsync(ToggleUserStatusRequest request, CancellationToken cancellationToken);

Task<string> GetOrCreateFromPrincipalAsync(ClaimsPrincipal principal);
Task<string> CreateAsync(CreateUserRequest request, string origin);
Task UpdateAsync(UpdateUserRequest request, string userId);

Task<string> ConfirmEmailAsync(string userId, string code, string tenant, CancellationToken cancellationToken);
Task<string> ConfirmPhoneNumberAsync(string userId, string code);

Task<string> ForgotPasswordAsync(ForgotPasswordRequest request, string origin);
Task<string> ResetPasswordAsync(ResetPasswordRequest request);
Task ChangePasswordAsync(ChangePasswordRequest request, string userId);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace FSH.WebApi.Application.Identity.Users.Profile;
namespace FSH.WebApi.Application.Identity.Users.Password;

public class ChangePasswordRequest
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace FSH.WebApi.Application.Identity.Users.Profile;
namespace FSH.WebApi.Application.Identity.Users.Password;

public class ForgotPasswordRequest
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace FSH.WebApi.Application.Identity.Users.Profile;
namespace FSH.WebApi.Application.Identity.Users.Password;

public class ResetPasswordRequest
{
Expand Down
22 changes: 0 additions & 22 deletions src/Core/Application/Identity/Users/Profile/IProfileService.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace FSH.WebApi.Application.Identity.Users.Profile;
namespace FSH.WebApi.Application.Identity.Users;

public class UpdateProfileRequest
public class UpdateUserRequest
{
public string Id { get; set; } = default!;
public string? FirstName { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
namespace FSH.WebApi.Application.Identity.Users.Profile;
namespace FSH.WebApi.Application.Identity.Users;

public class UpdateProfileRequestValidator : CustomValidator<UpdateProfileRequest>
public class UpdateUserRequestValidator : CustomValidator<UpdateUserRequest>
{
public UpdateProfileRequestValidator(IUserService userService, IStringLocalizer<UpdateProfileRequestValidator> localizer)
public UpdateUserRequestValidator(IUserService userService, IStringLocalizer<UpdateUserRequestValidator> localizer)
{
RuleFor(p => p.Id)
.NotEmpty();
Expand Down
99 changes: 0 additions & 99 deletions src/Host/Controllers/Identity/ProfileController.cs

This file was deleted.

56 changes: 53 additions & 3 deletions src/Host/Controllers/Identity/UsersController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FSH.WebApi.Application.Identity.Users;
using FSH.WebApi.Application.Identity.Users.Password;

namespace FSH.WebApi.Host.Controllers.Identity;

Expand All @@ -18,7 +19,7 @@ public Task<List<UserDetailsDto>> GetListAsync(CancellationToken cancellationTok

[HttpGet("{id}")]
[MustHavePermission(FSHAction.View, FSHResource.Users)]
[OpenApiOperation("Get user details.", "")]
[OpenApiOperation("Get a user's details.", "")]
public Task<UserDetailsDto> GetByIdAsync(string id, CancellationToken cancellationToken)
{
return _userService.GetAsync(id, cancellationToken);
Expand All @@ -35,17 +36,28 @@ public Task<List<UserRoleDto>> GetRolesAsync(string id, CancellationToken cancel
[HttpPost("{id}/roles")]
[ApiConventionMethod(typeof(FSHApiConventions), nameof(FSHApiConventions.Register))]
[MustHavePermission(FSHAction.Update, FSHResource.UserRoles)]
[OpenApiOperation("Change a user's assigned roles.", "")]
[OpenApiOperation("Update a user's assigned roles.", "")]
public Task<string> AssignRolesAsync(string id, UserRolesRequest request, CancellationToken cancellationToken)
{
return _userService.AssignRolesAsync(id, request, cancellationToken);
}

[HttpPost]
[AllowAnonymous]
[OpenApiOperation("Create a new user.", "")]
public Task<string> CreateAsync(CreateUserRequest request)
{
// TODO: check if registering anonymous users is actually allowed (should probably be an appsetting)
// and return UnAuthorized when it isn't
// Also: add other protection to prevent automatic posting (captcha?)
return _userService.CreateAsync(request, GetOriginFromRequest());
}

[HttpPost("{id}/toggle-status")]
[MustHavePermission(FSHAction.Update, FSHResource.Users)]
[ApiConventionMethod(typeof(FSHApiConventions), nameof(FSHApiConventions.Register))]
[OpenApiOperation("Toggle a user's active status.", "")]
public async Task<ActionResult> ToggleUserStatusAsync(string id, ToggleUserStatusRequest request, CancellationToken cancellationToken)
public async Task<ActionResult> ToggleStatusAsync(string id, ToggleUserStatusRequest request, CancellationToken cancellationToken)
{
if (id != request.UserId)
{
Expand All @@ -55,4 +67,42 @@ public async Task<ActionResult> ToggleUserStatusAsync(string id, ToggleUserStatu
await _userService.ToggleStatusAsync(request, cancellationToken);
return Ok();
}

[HttpGet("confirm-email")]
[AllowAnonymous]
[OpenApiOperation("Confirm email address for a user.", "")]
[ApiConventionMethod(typeof(FSHApiConventions), nameof(FSHApiConventions.Search))]
public Task<string> ConfirmEmailAsync([FromQuery] string tenant, [FromQuery] string userId, [FromQuery] string code, CancellationToken cancellationToken)
{
return _userService.ConfirmEmailAsync(userId, code, tenant, cancellationToken);
}

[HttpGet("confirm-phone-number")]
[AllowAnonymous]
[OpenApiOperation("Confirm phone number for a user.", "")]
[ApiConventionMethod(typeof(FSHApiConventions), nameof(FSHApiConventions.Search))]
public Task<string> ConfirmPhoneNumberAsync([FromQuery] string userId, [FromQuery] string code)
{
return _userService.ConfirmPhoneNumberAsync(userId, code);
}

[HttpPost("forgot-password")]
[AllowAnonymous]
[TenantIdHeader]
[OpenApiOperation("Request a pasword reset email for a user.", "")]
[ApiConventionMethod(typeof(FSHApiConventions), nameof(FSHApiConventions.Register))]
public Task<string> ForgotPasswordAsync(ForgotPasswordRequest request)
{
return _userService.ForgotPasswordAsync(request, GetOriginFromRequest());
}

[HttpPost("reset-password")]
[OpenApiOperation("Reset a user's password.", "")]
[ApiConventionMethod(typeof(FSHApiConventions), nameof(FSHApiConventions.Register))]
public Task<string> ResetPasswordAsync(ResetPasswordRequest request)
{
return _userService.ResetPasswordAsync(request);
}

private string GetOriginFromRequest() => $"{Request.Scheme}://{Request.Host.Value}{Request.PathBase.Value}";
}
50 changes: 42 additions & 8 deletions src/Host/Controllers/Personal/PersonalController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Security.Claims;
using FSH.WebApi.Application.Auditing;
using FSH.WebApi.Application.Identity.Users;
using FSH.WebApi.Application.Identity.Users.Password;

namespace FSH.WebApi.Host.Controllers.Identity;

Expand All @@ -10,22 +11,55 @@ public class PersonalController : VersionNeutralApiController

public PersonalController(IUserService userService) => _userService = userService;

[HttpGet("logs")]
[OpenApiOperation("Get audit logs of currently logged in user.", "")]
public Task<List<AuditDto>> GetMyLogsAsync()
[HttpGet("profile")]
[OpenApiOperation("Get profile details of currently logged in user.", "")]
public async Task<ActionResult<UserDetailsDto>> GetProfileAsync(CancellationToken cancellationToken)
{
return Mediator.Send(new GetMyAuditLogsRequest());
return User.GetUserId() is not { } userId || string.IsNullOrEmpty(userId)
? Unauthorized()
: Ok(await _userService.GetAsync(userId, cancellationToken));
}

[HttpGet("permissions")]
[OpenApiOperation("Get permissions of currently logged in user.", "")]
public async Task<ActionResult<List<string>>> GetMyPermissionsAsync(CancellationToken cancellationToken)
[HttpPut("profile")]
[OpenApiOperation("Update profile details of currently logged in user.", "")]
public async Task<ActionResult> UpdateProfileAsync(UpdateUserRequest request)
{
if (User.GetUserId() is not { } userId || string.IsNullOrEmpty(userId))
{
return Unauthorized();
}

await _userService.UpdateAsync(request, userId);
return Ok();
}

[HttpPut("change-password")]
[OpenApiOperation("Change password of currently logged in user.", "")]
[ApiConventionMethod(typeof(FSHApiConventions), nameof(FSHApiConventions.Register))]
public async Task<ActionResult> ChangePasswordAsync(ChangePasswordRequest model)
{
if (User.GetUserId() is not { } userId || string.IsNullOrEmpty(userId))
{
return Unauthorized();
}

return Ok(await _userService.GetPermissionsAsync(userId, cancellationToken));
await _userService.ChangePasswordAsync(model, userId);
return Ok();
}

[HttpGet("permissions")]
[OpenApiOperation("Get permissions of currently logged in user.", "")]
public async Task<ActionResult<List<string>>> GetPermissionsAsync(CancellationToken cancellationToken)
{
return User.GetUserId() is not { } userId || string.IsNullOrEmpty(userId)
? Unauthorized()
: Ok(await _userService.GetPermissionsAsync(userId, cancellationToken));
}

[HttpGet("logs")]
[OpenApiOperation("Get audit logs of currently logged in user.", "")]
public Task<List<AuditDto>> GetLogsAsync()
{
return Mediator.Send(new GetMyAuditLogsRequest());
}
}
4 changes: 2 additions & 2 deletions src/Infrastructure/Auth/AzureAd/AzureAdJwtBearerEvents.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Security.Claims;
using Finbuckle.MultiTenant;
using FSH.WebApi.Application.Common.Exceptions;
using FSH.WebApi.Application.Identity.Users.Profile;
using FSH.WebApi.Application.Identity.Users;
using FSH.WebApi.Infrastructure.Multitenancy;
using FSH.WebApi.Shared.Authorization;
using FSH.WebApi.Shared.Multitenancy;
Expand Down Expand Up @@ -77,7 +77,7 @@ public override async Task TokenValidated(TokenValidatedContext context)
context.HttpContext.TrySetTenantInfo(tenant, false);

// Lookup local user or create one if none exist.
string userId = await context.HttpContext.RequestServices.GetRequiredService<IProfileService>()
string userId = await context.HttpContext.RequestServices.GetRequiredService<IUserService>()
.GetOrCreateFromPrincipalAsync(principal);

// We use the nameidentifier claim to store the user id.
Expand Down
Loading