New project file, added Aspire, moved security

This commit is contained in:
2026-06-03 18:44:48 -07:00
parent 4c6df1f037
commit d916549402
78 changed files with 2786 additions and 21210 deletions

View File

@@ -0,0 +1,145 @@
using System.Security.Cryptography;
using MicCheck.Api.Data;
using MicCheck.Api.Organizations;
using MicCheck.Api.Users;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace MicCheck.Api.Common.Security.Authorization;
public class AuthService(
MicCheckDbContext db,
ITokenService tokenService,
IPasswordHasher<User> passwordHasher)
{
public async Task<LoginResponse?> LoginAsync(string email, string password, CancellationToken ct = default)
{
var user = await db.Users
.Include(u => u.Organizations)
.FirstOrDefaultAsync(u => u.Email == email && u.IsActive, ct);
if (user is null)
return null;
var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password);
if (result == PasswordVerificationResult.Failed)
return null;
var accessToken = tokenService.GenerateToken(user);
var refreshTokenValue = GenerateSecureToken();
db.RefreshTokens.Add(new RefreshToken
{
Token = refreshTokenValue,
UserId = user.Id,
ExpiresAt = DateTimeOffset.UtcNow.AddDays(30),
CreatedAt = DateTimeOffset.UtcNow
});
user.LastLoginAt = DateTimeOffset.UtcNow;
await db.SaveChangesAsync(ct);
return new LoginResponse(accessToken.Token, refreshTokenValue, accessToken.ExpiresAt);
}
public async Task<LoginResponse?> RegisterAsync(
string email, string password,
string firstName, string lastName,
string organizationName,
CancellationToken ct = default)
{
if (await db.Users.AnyAsync(u => u.Email == email, ct))
return null;
var user = new User
{
Email = email,
FirstName = firstName,
LastName = lastName,
IsActive = true,
CreatedAt = DateTimeOffset.UtcNow,
PasswordHash = string.Empty
};
user.PasswordHash = passwordHasher.HashPassword(user, password);
db.Users.Add(user);
await db.SaveChangesAsync(ct);
var organization = new Organization
{
Name = organizationName,
CreatedAt = DateTimeOffset.UtcNow
};
db.Organizations.Add(organization);
await db.SaveChangesAsync(ct);
db.OrganizationUsers.Add(new OrganizationUser
{
OrganizationId = organization.Id,
UserId = user.Id,
Role = OrganizationRole.Admin
});
await db.SaveChangesAsync(ct);
await db.Entry(user).Collection(u => u.Organizations).LoadAsync(ct);
var accessToken = tokenService.GenerateToken(user);
var refreshTokenValue = GenerateSecureToken();
db.RefreshTokens.Add(new RefreshToken
{
Token = refreshTokenValue,
UserId = user.Id,
ExpiresAt = DateTimeOffset.UtcNow.AddDays(30),
CreatedAt = DateTimeOffset.UtcNow
});
await db.SaveChangesAsync(ct);
return new LoginResponse(accessToken.Token, refreshTokenValue, accessToken.ExpiresAt);
}
public async Task<LoginResponse?> RefreshAsync(string refreshToken, CancellationToken ct = default)
{
var token = await db.RefreshTokens
.Include(rt => rt.User)
.ThenInclude(u => u.Organizations)
.FirstOrDefaultAsync(rt => rt.Token == refreshToken && !rt.IsRevoked, ct);
if (token is null || token.ExpiresAt < DateTimeOffset.UtcNow)
return null;
token.IsRevoked = true;
var accessToken = tokenService.GenerateToken(token.User);
var newRefreshTokenValue = GenerateSecureToken();
db.RefreshTokens.Add(new RefreshToken
{
Token = newRefreshTokenValue,
UserId = token.UserId,
ExpiresAt = DateTimeOffset.UtcNow.AddDays(30),
CreatedAt = DateTimeOffset.UtcNow
});
await db.SaveChangesAsync(ct);
return new LoginResponse(accessToken.Token, newRefreshTokenValue, accessToken.ExpiresAt);
}
public async Task LogoutAsync(string refreshToken, CancellationToken ct = default)
{
var token = await db.RefreshTokens
.FirstOrDefaultAsync(rt => rt.Token == refreshToken && !rt.IsRevoked, ct);
if (token is null)
return;
token.IsRevoked = true;
await db.SaveChangesAsync(ct);
}
private static string GenerateSecureToken()
{
var bytes = RandomNumberGenerator.GetBytes(64);
return Convert.ToBase64String(bytes);
}
}