From a25459d2cba1947204ab269f2c1559932952fddc Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Tue, 10 Jun 2025 22:10:16 +0300 Subject: [PATCH] Add UserRole handling --- src/Imprink.Application/IUnitOfWork.cs | 1 + src/Imprink.Application/Users/Dtos/RoleDto.cs | 7 ++++ .../Users/Dtos/UserRoleDto.cs | 7 ++++ .../Users/GetUserRolesHandler.cs | 19 ++++++--- .../Users/Roles/AddUserToRoleHandler.cs | 39 ------------------- .../Users/Roles/RemoveUserFromRoleHandler.cs | 32 --------------- .../Users/SetUserRoleHandler.cs | 29 ++++++++++++++ .../Repositories/Users/IUserRoleRepository.cs | 8 ++-- .../Repositories/Users/UserRoleRepository.cs | 25 ++++-------- src/Imprink.Infrastructure/UnitOfWork.cs | 1 + .../Controllers/Users/UserRoleController.cs | 15 ++++++- src/Imprink.WebApi/Startup.cs | 14 +++---- webui/src/app/page.js | 4 +- webui/src/app/token/route.js | 12 +----- webui/src/app/untoken/route.js | 22 ----------- 15 files changed, 92 insertions(+), 143 deletions(-) create mode 100644 src/Imprink.Application/Users/Dtos/RoleDto.cs create mode 100644 src/Imprink.Application/Users/Dtos/UserRoleDto.cs delete mode 100644 src/Imprink.Application/Users/Roles/AddUserToRoleHandler.cs delete mode 100644 src/Imprink.Application/Users/Roles/RemoveUserFromRoleHandler.cs create mode 100644 src/Imprink.Application/Users/SetUserRoleHandler.cs delete mode 100644 webui/src/app/untoken/route.js diff --git a/src/Imprink.Application/IUnitOfWork.cs b/src/Imprink.Application/IUnitOfWork.cs index 0d602ab..e5f0f57 100644 --- a/src/Imprink.Application/IUnitOfWork.cs +++ b/src/Imprink.Application/IUnitOfWork.cs @@ -1,5 +1,6 @@ using Imprink.Domain.Repositories; using Imprink.Domain.Repositories.Products; +using Imprink.Domain.Repositories.Users; namespace Imprink.Application; diff --git a/src/Imprink.Application/Users/Dtos/RoleDto.cs b/src/Imprink.Application/Users/Dtos/RoleDto.cs new file mode 100644 index 0000000..ac90f6f --- /dev/null +++ b/src/Imprink.Application/Users/Dtos/RoleDto.cs @@ -0,0 +1,7 @@ +namespace Imprink.Application.Users.Dtos; + +public class RoleDto +{ + public required Guid RoleId { get; set; } + public required string RoleName { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/Users/Dtos/UserRoleDto.cs b/src/Imprink.Application/Users/Dtos/UserRoleDto.cs new file mode 100644 index 0000000..cb53e64 --- /dev/null +++ b/src/Imprink.Application/Users/Dtos/UserRoleDto.cs @@ -0,0 +1,7 @@ +namespace Imprink.Application.Users.Dtos; + +public class UserRoleDto +{ + public required string UserId { get; set; } + public required Guid RoleId { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/Users/GetUserRolesHandler.cs b/src/Imprink.Application/Users/GetUserRolesHandler.cs index 3d9845f..ef2091a 100644 --- a/src/Imprink.Application/Users/GetUserRolesHandler.cs +++ b/src/Imprink.Application/Users/GetUserRolesHandler.cs @@ -1,15 +1,22 @@ -using Imprink.Domain.Entities.Users; +using Imprink.Application.Users.Dtos; using MediatR; namespace Imprink.Application.Users; -public record GetUserRolesCommand(string Sub) : IRequest>; +public record GetUserRolesCommand(string Sub) : IRequest>; -public class GetUserRolesHandler(IUnitOfWork uw): IRequestHandler> +public class GetUserRolesHandler(IUnitOfWork uw): IRequestHandler> { - public async Task> Handle(GetUserRolesCommand request, CancellationToken cancellationToken) + public async Task> Handle(GetUserRolesCommand request, CancellationToken cancellationToken) { - if (await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) return []; - return await uw.UserRoleRepository.GetUserRolesAsync(request.Sub, cancellationToken);; + if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) return []; + + var roles = await uw.UserRoleRepository.GetUserRolesAsync(request.Sub, cancellationToken); + + return roles.Select(role => new RoleDto + { + RoleId = role.Id, + RoleName = role.RoleName + }).ToList(); } } \ No newline at end of file diff --git a/src/Imprink.Application/Users/Roles/AddUserToRoleHandler.cs b/src/Imprink.Application/Users/Roles/AddUserToRoleHandler.cs deleted file mode 100644 index 9beb472..0000000 --- a/src/Imprink.Application/Users/Roles/AddUserToRoleHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Imprink.Domain.Entities.Users; -using Imprink.Domain.Repositories; -using MediatR; - -namespace Imprink.Application.Users.Roles; - -public record AddUserToRoleCommand(string UserId, Guid RoleId) : IRequest; - -public class AddUserToRoleCommandHandler( - IUserRoleRepository userRoleRepository, - IRoleRepository roleRepository, - IUserRepository userRepository) - : IRequestHandler -{ - public async Task Handle(AddUserToRoleCommand request, CancellationToken cancellationToken) - { - var userExists = await userRepository.UserExistsAsync(request.UserId, cancellationToken); - if (!userExists) - return false; - - var roleExists = await roleRepository.RoleExistsAsync(request.RoleId, cancellationToken); - if (!roleExists) - return false; - - var isAlreadyInRole = await userRoleRepository.IsUserInRoleAsync(request.UserId, request.RoleId, cancellationToken); - if (isAlreadyInRole) - return true; - - var userRole = new UserRole - { - UserId = request.UserId, - RoleId = request.RoleId - }; - - await userRoleRepository.AddUserRoleAsync(userRole, cancellationToken); - - return true; - } -} \ No newline at end of file diff --git a/src/Imprink.Application/Users/Roles/RemoveUserFromRoleHandler.cs b/src/Imprink.Application/Users/Roles/RemoveUserFromRoleHandler.cs deleted file mode 100644 index 2cf237b..0000000 --- a/src/Imprink.Application/Users/Roles/RemoveUserFromRoleHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Imprink.Domain.Repositories; -using MediatR; - -namespace Imprink.Application.Users.Roles; - -public record RemoveUserFromRoleCommand(string UserId, Guid RoleId) : IRequest; - -public class RemoveUserFromRoleCommandHandler( - IUserRoleRepository userRoleRepository, - IRoleRepository roleRepository, - IUserRepository userRepository) - : IRequestHandler -{ - public async Task Handle(RemoveUserFromRoleCommand request, CancellationToken cancellationToken) - { - var userExists = await userRepository.UserExistsAsync(request.UserId, cancellationToken); - if (!userExists) - return false; - - var roleExists = await roleRepository.RoleExistsAsync(request.RoleId, cancellationToken); - if (!roleExists) - return false; - - var userRole = await userRoleRepository.GetUserRoleAsync(request.UserId, request.RoleId, cancellationToken); - if (userRole == null) - return true; - - await userRoleRepository.RemoveUserRoleAsync(userRole, cancellationToken); - - return true; - } -} \ No newline at end of file diff --git a/src/Imprink.Application/Users/SetUserRoleHandler.cs b/src/Imprink.Application/Users/SetUserRoleHandler.cs new file mode 100644 index 0000000..3fe8ca7 --- /dev/null +++ b/src/Imprink.Application/Users/SetUserRoleHandler.cs @@ -0,0 +1,29 @@ +using Imprink.Application.Users.Dtos; +using Imprink.Domain.Entities.Users; +using MediatR; + +namespace Imprink.Application.Users; + +public record SetUserRoleCommand(string Sub, Guid RoleId) : IRequest; + +public class SetUserRoleHandler(IUnitOfWork uw) : IRequestHandler +{ + public async Task Handle(SetUserRoleCommand request, CancellationToken cancellationToken) + { + if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) return null; + + var userRole = new UserRole + { + UserId = request.Sub, + RoleId = request.RoleId + }; + + var addedRole = await uw.UserRoleRepository.AddUserRoleAsync(userRole, cancellationToken); + + return new UserRoleDto + { + UserId = addedRole.UserId, + RoleId = addedRole.RoleId + }; + } +} \ No newline at end of file diff --git a/src/Imprink.Domain/Repositories/Users/IUserRoleRepository.cs b/src/Imprink.Domain/Repositories/Users/IUserRoleRepository.cs index b60441a..6c36eda 100644 --- a/src/Imprink.Domain/Repositories/Users/IUserRoleRepository.cs +++ b/src/Imprink.Domain/Repositories/Users/IUserRoleRepository.cs @@ -1,15 +1,13 @@ using Imprink.Domain.Entities.Users; -namespace Imprink.Domain.Repositories; +namespace Imprink.Domain.Repositories.Users; public interface IUserRoleRepository { Task> GetUserRolesAsync(string userId, CancellationToken cancellationToken = default); Task> GetUsersInRoleAsync(Guid roleId, CancellationToken cancellationToken = default); - Task IsUserInRoleAsync(string userId, Guid roleId, CancellationToken cancellationToken = default); Task GetUserRoleAsync(string userId, Guid roleId, CancellationToken cancellationToken = default); - Task AddUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default); - Task RemoveUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default); - Task> GetUserRolesByUserIdAsync(string userId, CancellationToken cancellationToken = default); + Task AddUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default); + Task RemoveUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Repositories/Users/UserRoleRepository.cs b/src/Imprink.Infrastructure/Repositories/Users/UserRoleRepository.cs index bb57623..0fc1f79 100644 --- a/src/Imprink.Infrastructure/Repositories/Users/UserRoleRepository.cs +++ b/src/Imprink.Infrastructure/Repositories/Users/UserRoleRepository.cs @@ -1,5 +1,6 @@ using Imprink.Domain.Entities.Users; using Imprink.Domain.Repositories; +using Imprink.Domain.Repositories.Users; using Imprink.Infrastructure.Database; using Microsoft.EntityFrameworkCore; @@ -25,12 +26,6 @@ public class UserRoleRepository(ApplicationDbContext context) : IUserRoleReposit .ToListAsync(cancellationToken); } - public async Task IsUserInRoleAsync(string userId, Guid roleId, CancellationToken cancellationToken = default) - { - return await context.UserRole - .AnyAsync(ur => ur.UserId == userId && ur.RoleId == roleId, cancellationToken); - } - public async Task GetUserRoleAsync(string userId, Guid roleId, CancellationToken cancellationToken = default) { return await context.UserRole @@ -38,23 +33,17 @@ public class UserRoleRepository(ApplicationDbContext context) : IUserRoleReposit .FirstOrDefaultAsync(ur => ur.UserId == userId && ur.RoleId == roleId, cancellationToken); } - public async Task AddUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default) + public async Task AddUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default) { - context.UserRole.Add(userRole); + var ur = context.UserRole.Add(userRole); await context.SaveChangesAsync(cancellationToken); + return ur.Entity; } - public async Task RemoveUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default) + public async Task RemoveUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default) { - context.UserRole.Remove(userRole); + var ur = context.UserRole.Remove(userRole); await context.SaveChangesAsync(cancellationToken); - } - - public async Task> GetUserRolesByUserIdAsync(string userId, CancellationToken cancellationToken = default) - { - return await context.UserRole - .AsNoTracking() - .Where(ur => ur.UserId == userId) - .ToListAsync(cancellationToken); + return ur.Entity; } } \ No newline at end of file diff --git a/src/Imprink.Infrastructure/UnitOfWork.cs b/src/Imprink.Infrastructure/UnitOfWork.cs index 161c867..c5e3642 100644 --- a/src/Imprink.Infrastructure/UnitOfWork.cs +++ b/src/Imprink.Infrastructure/UnitOfWork.cs @@ -1,6 +1,7 @@ using Imprink.Application; using Imprink.Domain.Repositories; using Imprink.Domain.Repositories.Products; +using Imprink.Domain.Repositories.Users; using Imprink.Infrastructure.Database; namespace Imprink.Infrastructure; diff --git a/src/Imprink.WebApi/Controllers/Users/UserRoleController.cs b/src/Imprink.WebApi/Controllers/Users/UserRoleController.cs index 0b68dd2..f2090ed 100644 --- a/src/Imprink.WebApi/Controllers/Users/UserRoleController.cs +++ b/src/Imprink.WebApi/Controllers/Users/UserRoleController.cs @@ -1,6 +1,7 @@ using System.Security.Claims; using Imprink.Application.Users; using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Imprink.WebApi.Controllers.Users; @@ -9,7 +10,7 @@ namespace Imprink.WebApi.Controllers.Users; [Route("/api/users/roles")] public class UserRoleController(IMediator mediator) : ControllerBase { - //[Authorize] + [Authorize] [HttpGet("me")] public async Task GetMyRoles() { @@ -20,4 +21,16 @@ public class UserRoleController(IMediator mediator) : ControllerBase return Ok(myRoles); } + + [Authorize(Roles = "Admin")] + [HttpPost("set")] + public async Task SetUserRole(SetUserRoleCommand command) + { + var userRole = await mediator.Send(command); + + if (userRole == null) + return BadRequest(); + + return Ok(userRole); + } } \ No newline at end of file diff --git a/src/Imprink.WebApi/Startup.cs b/src/Imprink.WebApi/Startup.cs index a522b9c..cf44d43 100644 --- a/src/Imprink.WebApi/Startup.cs +++ b/src/Imprink.WebApi/Startup.cs @@ -5,6 +5,7 @@ using Imprink.Application.Products.Create; using Imprink.Application.Validation.Models; using Imprink.Domain.Repositories; using Imprink.Domain.Repositories.Products; +using Imprink.Domain.Repositories.Users; using Imprink.Infrastructure; using Imprink.Infrastructure.Database; using Imprink.Infrastructure.Repositories.Products; @@ -74,6 +75,7 @@ public static class Startup foreach (var role in roles) identity!.AddClaim(new Claim(ClaimTypes.Role, role)); identity!.AddClaim(new Claim(ClaimTypes.Role, "User")); + return Task.CompletedTask; } }; @@ -99,8 +101,9 @@ public static class Startup Description = "JWT Authorization header using the Bearer scheme.", Name = "Authorization", In = ParameterLocation.Header, - Type = SecuritySchemeType.ApiKey, - Scheme = "Bearer" + Type = SecuritySchemeType.Http, + Scheme = "Bearer", + BearerFormat = "JWT" }); options.AddSecurityRequirement(new OpenApiSecurityRequirement @@ -112,12 +115,9 @@ public static class Startup { Type = ReferenceType.SecurityScheme, Id = "Bearer" - }, - Scheme = "Bearer", - Name = "Bearer", - In = ParameterLocation.Header + } }, - new List() + [] } }); }); diff --git a/webui/src/app/page.js b/webui/src/app/page.js index 028c0cf..141ace0 100644 --- a/webui/src/app/page.js +++ b/webui/src/app/page.js @@ -10,13 +10,13 @@ export default function Home() { const fetchAccessToken = async () => { if (user) { try { - const v = await fetch('/token'); + await fetch('/token'); } catch (error) { console.error("Error fetching token"); } } else { try { - const resp = await fetch('/untoken'); + await fetch('/untoken'); } catch (e) { console.error('Error in /api/untoken:', e); } diff --git a/webui/src/app/token/route.js b/webui/src/app/token/route.js index d649080..a3fad83 100644 --- a/webui/src/app/token/route.js +++ b/webui/src/app/token/route.js @@ -1,4 +1,3 @@ -import {cookies, headers} from 'next/headers'; import { NextResponse } from 'next/server'; import {auth0} from "@/lib/auth0"; import api from "@/lib/api"; @@ -9,20 +8,11 @@ export async function GET() { if (!token) { return NextResponse.json({ error: 'No access token found' }, { status: 401 }); } - (await cookies()).set('access_token', token, { - httpOnly: true, - secure: true, - sameSite: 'strict', - path: '/', - domain: process.env.COOKIE_DOMAIN, - maxAge: 3600, - }); - await api.post('/users/sync', {}, { headers: { Cookie: `access_token=${token}` } }); - return NextResponse.json({ message: 'Access token set in cookie' }); + return NextResponse.json({ access_token: token }); } catch (error) { console.error('Error in /api/token:', error); return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); diff --git a/webui/src/app/untoken/route.js b/webui/src/app/untoken/route.js deleted file mode 100644 index cd053fe..0000000 --- a/webui/src/app/untoken/route.js +++ /dev/null @@ -1,22 +0,0 @@ -import {cookies} from 'next/headers'; -import {NextResponse} from 'next/server'; - -export async function GET() { - try { - (await cookies()).set({ - name: 'access_token', - value: '', - httpOnly: true, - secure: true, - sameSite: 'strict', - path: '/', - domain: process.env.COOKIE_DOMAIN, - maxAge: -1, - }); - - return NextResponse.json({message: 'Deleted access token'}); - } catch (error) { - console.error('Error in /api/untoken:', error); - return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); - } -} \ No newline at end of file