feat(backend): add JWT authentication, RBAC and consent/audit services

Add signup/signin with backend-issued JWT (Argon2 passwords in SurrealDB), role-based authorization policies, per-user data access levels, consent register and read audit log. Protect user endpoints and record sensitive reads.
This commit is contained in:
Rafał Tyński
2026-10-04 02:11:56 +02:00
parent 24c9e582c9
commit 482a4b41f0
26 changed files with 896 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TarninaHub.Application.Abstractions;
using TarninaHub.Application.Dtos;
using TarninaHub.Application.Mappings;
using TarninaHub.Application.Security;
namespace TarninaHub.Controllers;
[ApiController]
[ApiVersion("1.0")]
[Route("api/users")]
[Authorize]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly IAuditLogService _auditLogService;
public UsersController(IUserService userService, IAuditLogService auditLogService)
{
_userService = userService;
_auditLogService = auditLogService;
}
[HttpGet("me")]
public async Task<ActionResult<UserResponse>> GetMe(CancellationToken cancellationToken)
{
var userId = User.GetUserId();
if (string.IsNullOrWhiteSpace(userId))
{
return Unauthorized();
}
var user = await _userService.GetByIdAsync(userId, cancellationToken);
return user is null ? NotFound() : Ok(user.ToResponse());
}
[HttpGet("{userId}")]
public async Task<ActionResult<UserResponse>> GetById(
string userId,
CancellationToken cancellationToken)
{
var currentUserId = User.GetUserId();
var isOwner = string.Equals(currentUserId, userId, StringComparison.Ordinal);
if (!isOwner && !User.IsInRole(RoleNames.Operator) && !User.IsInRole(RoleNames.Manager) && !User.IsInRole(RoleNames.Admin))
{
return Forbid();
}
var user = await _userService.GetByIdAsync(userId, cancellationToken);
if (user is null)
{
return NotFound();
}
if (!isOwner)
{
await _auditLogService.RecordAsync(
currentUserId,
"user.read",
userId,
new Dictionary<string, object?> { ["reason"] = "staff-lookup" },
cancellationToken);
}
return Ok(user.ToResponse());
}
}