Release/v2.1.0 #44
83
src/HopFrame.Api/Controller/UserController.cs
Normal file
83
src/HopFrame.Api/Controller/UserController.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using HopFrame.Api.Logic;
|
||||||
|
using HopFrame.Api.Models;
|
||||||
|
using HopFrame.Database.Models;
|
||||||
|
using HopFrame.Database.Repositories;
|
||||||
|
using HopFrame.Security.Authorization;
|
||||||
|
using HopFrame.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace HopFrame.Api.Controller;
|
||||||
|
|
||||||
|
[ApiController, Route("api/v1/users")]
|
||||||
|
public class UserController(IOptions<AdminPermissionOptions> permissions, IPermissionRepository perms, ITokenContext context, IUserLogic logic) : ControllerBase {
|
||||||
|
|
||||||
|
private async Task<bool> AuthorizeRequest(string permission) {
|
||||||
|
return await perms.HasPermission(context.AccessToken, permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet, Authorized]
|
||||||
|
public async Task<ActionResult<IList<User>>> GetUsers() {
|
||||||
|
if (!await AuthorizeRequest(permissions.Value.Users.Read))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return await logic.GetUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{userId}"), Authorized]
|
||||||
|
public async Task<ActionResult<User>> GetUser(string userId) {
|
||||||
|
if (!await AuthorizeRequest(permissions.Value.Users.Read))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return await logic.GetUser(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("username/{username}"), Authorized]
|
||||||
|
public async Task<ActionResult<User>> GetUserByUsername(string username) {
|
||||||
|
if (!await AuthorizeRequest(permissions.Value.Users.Read))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return await logic.GetUserByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("email/{email}"), Authorized]
|
||||||
|
public async Task<ActionResult<User>> GetUserByEmail(string email) {
|
||||||
|
if (!await AuthorizeRequest(permissions.Value.Users.Read))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return await logic.GetUserByEmail(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost, Authorized]
|
||||||
|
public async Task<ActionResult<User>> CreateUser([FromBody] UserCreator user) {
|
||||||
|
if (!await AuthorizeRequest(permissions.Value.Users.Create))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return await logic.CreateUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{userId}"), Authorized]
|
||||||
|
public async Task<ActionResult<User>> UpdateUser(string userId, [FromBody] User user) {
|
||||||
|
if (!await AuthorizeRequest(permissions.Value.Users.Update))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return await logic.UpdateUser(userId, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{userId}"), Authorized]
|
||||||
|
public async Task<ActionResult> DeleteUser(string userId) {
|
||||||
|
if (!await AuthorizeRequest(permissions.Value.Users.Delete))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return await logic.DeleteUser(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{userId}/password"), Authorized]
|
||||||
|
public async Task<ActionResult> ChangePassword(string userId, [FromBody] UserPasswordChange passwordChange) {
|
||||||
|
if (context.User.Id.ToString() != userId && !await AuthorizeRequest(permissions.Value.Users.Update))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return await logic.UpdatePassword(userId, passwordChange.OldPassword, passwordChange.NewPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,9 +19,10 @@ public static class ServiceCollectionExtensions {
|
|||||||
/// <param name="configuration">The configuration used to configure HopFrame authentication</param>
|
/// <param name="configuration">The configuration used to configure HopFrame authentication</param>
|
||||||
/// <typeparam name="TDbContext">The data source for all HopFrame entities</typeparam>
|
/// <typeparam name="TDbContext">The data source for all HopFrame entities</typeparam>
|
||||||
public static void AddHopFrame<TDbContext>(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase {
|
public static void AddHopFrame<TDbContext>(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase {
|
||||||
var controllers = new List<Type>();
|
var controllers = new List<Type> { typeof(UserController) };
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("HopFrame:Authentication:DefaultAuthentication"))
|
var defaultAuthenticationSection = configuration.GetSection("HopFrame:Authentication:DefaultAuthentication");
|
||||||
|
if (!defaultAuthenticationSection.Exists() || configuration.GetValue<bool>("HopFrame:Authentication:DefaultAuthentication"))
|
||||||
controllers.Add(typeof(AuthController));
|
controllers.Add(typeof(AuthController));
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("HopFrame:Authentication:OpenID:Enabled"))
|
if (configuration.GetValue<bool>("HopFrame:Authentication:OpenID:Enabled"))
|
||||||
@@ -46,6 +47,7 @@ public static class ServiceCollectionExtensions {
|
|||||||
services.AddHopFrameRepositories<TDbContext>();
|
services.AddHopFrameRepositories<TDbContext>();
|
||||||
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
services.AddScoped<IAuthLogic, AuthLogic>();
|
services.AddScoped<IAuthLogic, AuthLogic>();
|
||||||
|
services.AddScoped<IUserLogic, UserLogic>();
|
||||||
|
|
||||||
services.AddHopFrameAuthentication(configuration);
|
services.AddHopFrameAuthentication(configuration);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace HopFrame.Api.Logic;
|
namespace HopFrame.Api.Logic;
|
||||||
|
|
||||||
|
|||||||
16
src/HopFrame.Api/Logic/IUserLogic.cs
Normal file
16
src/HopFrame.Api/Logic/IUserLogic.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using HopFrame.Api.Models;
|
||||||
|
using HopFrame.Database.Models;
|
||||||
|
|
||||||
|
namespace HopFrame.Api.Logic;
|
||||||
|
|
||||||
|
public interface IUserLogic {
|
||||||
|
Task<LogicResult<IList<User>>> GetUsers();
|
||||||
|
Task<LogicResult<User>> GetUser(string id);
|
||||||
|
Task<LogicResult<User>> GetUserByUsername(string username);
|
||||||
|
Task<LogicResult<User>> GetUserByEmail(string email);
|
||||||
|
|
||||||
|
Task<LogicResult<User>> CreateUser(UserCreator user);
|
||||||
|
Task<LogicResult<User>> UpdateUser(string id, User user);
|
||||||
|
Task<LogicResult> DeleteUser(string id);
|
||||||
|
Task<LogicResult> UpdatePassword(string id, string oldPassword, string newPassword);
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Options;
|
|||||||
|
|
||||||
namespace HopFrame.Api.Logic.Implementation;
|
namespace HopFrame.Api.Logic.Implementation;
|
||||||
|
|
||||||
internal class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenContext tokenContext, IHttpContextAccessor accessor, IOptions<HopFrameAuthenticationOptions> options) : IAuthLogic {
|
internal sealed class AuthLogic(IUserRepository users, ITokenRepository tokens, ITokenContext tokenContext, IHttpContextAccessor accessor, IOptions<HopFrameAuthenticationOptions> options) : IAuthLogic {
|
||||||
|
|
||||||
public async Task<LogicResult<SingleValueResult<string>>> Login(UserLogin login) {
|
public async Task<LogicResult<SingleValueResult<string>>> Login(UserLogin login) {
|
||||||
if (!options.Value.DefaultAuthentication) return LogicResult<SingleValueResult<string>>.BadRequest("HopFrame authentication scheme is disabled");
|
if (!options.Value.DefaultAuthentication) return LogicResult<SingleValueResult<string>>.BadRequest("HopFrame authentication scheme is disabled");
|
||||||
|
|||||||
105
src/HopFrame.Api/Logic/Implementation/UserLogic.cs
Normal file
105
src/HopFrame.Api/Logic/Implementation/UserLogic.cs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
using HopFrame.Api.Models;
|
||||||
|
using HopFrame.Database.Models;
|
||||||
|
using HopFrame.Database.Repositories;
|
||||||
|
using HopFrame.Security.Claims;
|
||||||
|
|
||||||
|
namespace HopFrame.Api.Logic.Implementation;
|
||||||
|
|
||||||
|
internal sealed class UserLogic(IUserRepository users, ITokenContext context) : IUserLogic {
|
||||||
|
public async Task<LogicResult<IList<User>>> GetUsers() {
|
||||||
|
return LogicResult<IList<User>>.Ok(await users.GetUsers());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogicResult<User>> GetUser(string id) {
|
||||||
|
if (!Guid.TryParse(id, out var userId))
|
||||||
|
return LogicResult<User>.BadRequest("Invalid user id");
|
||||||
|
|
||||||
|
var user = await users.GetUser(userId);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
return LogicResult<User>.NotFound("That user does not exist");
|
||||||
|
|
||||||
|
return LogicResult<User>.Ok(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogicResult<User>> GetUserByUsername(string username) {
|
||||||
|
var user = await users.GetUserByUsername(username);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
return LogicResult<User>.NotFound("That user does not exist");
|
||||||
|
|
||||||
|
return LogicResult<User>.Ok(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogicResult<User>> GetUserByEmail(string email) {
|
||||||
|
var user = await users.GetUserByEmail(email);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
return LogicResult<User>.NotFound("That user does not exist");
|
||||||
|
|
||||||
|
return LogicResult<User>.Ok(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogicResult<User>> CreateUser(UserCreator user) {
|
||||||
|
var createdUser = new User {
|
||||||
|
Email = user.Email,
|
||||||
|
Username = user.Username,
|
||||||
|
Password = user.Password,
|
||||||
|
};
|
||||||
|
createdUser.Permissions = user.Permissions?.Select(p => new Permission {
|
||||||
|
GrantedAt = DateTime.Now,
|
||||||
|
PermissionName = p,
|
||||||
|
User = createdUser
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var newUser = await users.AddUser(createdUser);
|
||||||
|
|
||||||
|
if (newUser is null)
|
||||||
|
return LogicResult<User>.Conflict("That user already exists");
|
||||||
|
|
||||||
|
return LogicResult<User>.Ok(newUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogicResult<User>> UpdateUser(string id, User user) {
|
||||||
|
if (!Guid.TryParse(id, out var userId))
|
||||||
|
return LogicResult<User>.BadRequest("Invalid user id");
|
||||||
|
|
||||||
|
if (user.Id != userId)
|
||||||
|
return LogicResult<User>.Conflict("Cannot edit user with different user id");
|
||||||
|
|
||||||
|
if (await users.GetUser(userId) is null)
|
||||||
|
return LogicResult<User>.NotFound("That user does not exist");
|
||||||
|
|
||||||
|
await users.UpdateUser(user);
|
||||||
|
return LogicResult<User>.Ok(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogicResult> DeleteUser(string id) {
|
||||||
|
if (!Guid.TryParse(id, out var userId))
|
||||||
|
return LogicResult.BadRequest("Invalid user id");
|
||||||
|
|
||||||
|
var user = await users.GetUser(userId);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
return LogicResult.NotFound("That user does not exist");
|
||||||
|
|
||||||
|
await users.DeleteUser(user);
|
||||||
|
return LogicResult.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogicResult> UpdatePassword(string id, string oldPassword, string newPassword) {
|
||||||
|
if (!Guid.TryParse(id, out var userId))
|
||||||
|
return LogicResult.BadRequest("Invalid user id");
|
||||||
|
|
||||||
|
var user = await users.GetUser(userId);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
return LogicResult.NotFound("That user does not exist");
|
||||||
|
|
||||||
|
if (userId == context.User.Id && !await users.CheckUserPassword(user, oldPassword))
|
||||||
|
return LogicResult.Conflict("Old password is not correct");
|
||||||
|
|
||||||
|
await users.ChangePassword(user, newPassword);
|
||||||
|
return LogicResult.Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/HopFrame.Api/Models/UserCreator.cs
Normal file
8
src/HopFrame.Api/Models/UserCreator.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace HopFrame.Api.Models;
|
||||||
|
|
||||||
|
public class UserCreator {
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
public virtual List<string> Permissions { get; set; }
|
||||||
|
}
|
||||||
6
src/HopFrame.Api/Models/UserPasswordChange.cs
Normal file
6
src/HopFrame.Api/Models/UserPasswordChange.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace HopFrame.Api.Models;
|
||||||
|
|
||||||
|
public class UserPasswordChange {
|
||||||
|
public string OldPassword { get; set; }
|
||||||
|
public string NewPassword { get; set; }
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace HopFrame.Database.Models;
|
|||||||
|
|
||||||
public class User : IPermissionOwner {
|
public class User : IPermissionOwner {
|
||||||
|
|
||||||
[Key, Required, MinLength(36), MaxLength(36)]
|
[Key, Required]
|
||||||
public Guid Id { get; init; }
|
public Guid Id { get; init; }
|
||||||
|
|
||||||
[Required, MaxLength(50)]
|
[Required, MaxLength(50)]
|
||||||
@@ -14,7 +14,7 @@ public class User : IPermissionOwner {
|
|||||||
[Required, MaxLength(50), EmailAddress]
|
[Required, MaxLength(50), EmailAddress]
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
|
|
||||||
[Required, MinLength(8), MaxLength(255), JsonIgnore]
|
[MinLength(8), MaxLength(255), JsonIgnore]
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
|
|||||||
Reference in New Issue
Block a user