mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-07 15:08:58 +00:00
BLOCK-10: Add CloudWatch alarms (DLQ, Lambda errors, RDS, API 5xx) with SNS email BLOCK-11: Remove sync-over-async deadlock in CurrentUserService BLOCK-12: Add AppDelegate OAuth URL callback handler for mobile BLOCK-13: Wire mobile 401 interceptor to dispatch Redux logout BLOCK-14: Fix JWT base64 padding crash and SysAdmin role detection BLOCK-15: Reset pagination to page 1 on filter change BLOCK-16: Add unsaved-changes guard (beforeunload + useBlocker) to AdminWorkspace FIX-08: Add BulkUpdateLineItems FluentValidation validator FIX-13: Display auth errors on LoginPage FIX-25: Add token refresh with retry queue to mobile API client FIX-44: Add httpx retry logic to all Lambda handlers FIX-42/43: Align docker-compose PG version (15) and DB name (proposals) with RDS
101 lines
3.5 KiB
C#
101 lines
3.5 KiB
C#
using System.Security.Claims;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using ProposalSystem.Application.Interfaces;
|
|
using ProposalSystem.Domain.Entities;
|
|
using ProposalSystem.Infrastructure.Data;
|
|
|
|
namespace ProposalSystem.Api.Services;
|
|
|
|
public class CurrentUserService : ICurrentUserService
|
|
{
|
|
private readonly IHttpContextAccessor _httpContext;
|
|
private readonly ProposalDbContext _db;
|
|
private User? _cachedUser;
|
|
|
|
public CurrentUserService(IHttpContextAccessor httpContext, ProposalDbContext db)
|
|
{
|
|
_httpContext = httpContext;
|
|
_db = db;
|
|
}
|
|
|
|
public Guid UserId => GetOrThrow().Id;
|
|
public string Email => GetOrThrow().Email;
|
|
public UserRole Role => GetOrThrow().Role;
|
|
|
|
public string? IpAddress =>
|
|
_httpContext.HttpContext?.Connection.RemoteIpAddress?.ToString();
|
|
|
|
public async Task ResolveAsync()
|
|
{
|
|
if (_cachedUser != null) return;
|
|
|
|
var principal = _httpContext.HttpContext?.User
|
|
?? throw new UnauthorizedAccessException("No authenticated user");
|
|
|
|
var sub = principal.FindFirstValue("sub");
|
|
var email = principal.FindFirstValue(ClaimTypes.Email)
|
|
?? principal.FindFirstValue("email")
|
|
?? "unknown@seahaven.com";
|
|
var name = principal.FindFirstValue("name")
|
|
?? email.Split('@')[0];
|
|
var groups = principal.FindAll("cognito:groups")
|
|
.Select(c => c.Value).ToList();
|
|
var role = groups.Contains("sysadmins") ? UserRole.SysAdmin
|
|
: groups.Contains("admins") ? UserRole.Admin
|
|
: UserRole.Dispatcher;
|
|
|
|
var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (userId != null && Guid.TryParse(userId, out var parsedId))
|
|
_cachedUser = await _db.Users.FirstOrDefaultAsync(u => u.Id == parsedId);
|
|
|
|
if (_cachedUser == null && sub != null)
|
|
_cachedUser = await _db.Users.FirstOrDefaultAsync(u => u.CognitoSub == sub);
|
|
|
|
if (_cachedUser == null)
|
|
_cachedUser = await _db.Users.FirstOrDefaultAsync(u => u.Email == email);
|
|
|
|
if (_cachedUser != null)
|
|
{
|
|
var changed = false;
|
|
if (_cachedUser.Email != email) { _cachedUser.Email = email; changed = true; }
|
|
if (_cachedUser.DisplayName != name) { _cachedUser.DisplayName = name; changed = true; }
|
|
if (changed)
|
|
{
|
|
_cachedUser.UpdatedAt = DateTime.UtcNow;
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
return;
|
|
}
|
|
|
|
_cachedUser = new User
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
CognitoSub = sub ?? $"auto-{Guid.NewGuid():N}",
|
|
Email = email,
|
|
DisplayName = name,
|
|
Role = role,
|
|
IsActive = true,
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
};
|
|
|
|
_db.Users.Add(_cachedUser);
|
|
try
|
|
{
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
catch (DbUpdateException)
|
|
{
|
|
_db.Entry(_cachedUser).State = EntityState.Detached;
|
|
_cachedUser = await _db.Users.FirstOrDefaultAsync(u => u.Email == email)
|
|
?? throw new UnauthorizedAccessException("Could not resolve current user");
|
|
}
|
|
}
|
|
|
|
private User GetOrThrow()
|
|
{
|
|
return _cachedUser
|
|
?? throw new InvalidOperationException(
|
|
"CurrentUserService.ResolveAsync() was not called. Ensure the authentication middleware runs before accessing user properties.");
|
|
}
|
|
}
|