using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Azaion.Common.Configs; using Azaion.Common.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; namespace Azaion.Services; public interface IAuthService { Task GetCurrentUser(); /// /// AZ-531 / AZ-532 — mint a 15-minute ES256 access token. /// is stamped as the sid claim (logout / family-revocation key in AZ-535) /// and is the per-token unique id (AZ-535 access denylist). /// AZ-534 — values are stamped as repeated amr /// claims so verifiers can require step-up MFA. Defaults to ["pwd"]. /// AccessToken CreateToken(User user, Guid sessionId, Guid jti, IEnumerable? amr = null); } public sealed record AccessToken(string Jwt, DateTime ExpiresAt); public class AuthService( IHttpContextAccessor httpContextAccessor, IOptions jwtConfig, IJwtSigningKeyProvider signingKeys, IUserService userService) : IAuthService { private readonly JwtConfig _jwt = jwtConfig.Value; private string? GetCurrentUserEmail() { var claims = httpContextAccessor.HttpContext?.User.Claims.ToDictionary(x => x.Type); return claims?[ClaimTypes.Name].Value; } public async Task GetCurrentUser() { var email = GetCurrentUserEmail(); return await userService.GetByEmail(email); } public AccessToken CreateToken(User user, Guid sessionId, Guid jti, IEnumerable? amr = null) { var active = signingKeys.Active; var signingCredentials = new SigningCredentials(active.SecurityKey, SecurityAlgorithms.EcdsaSha256); var expires = DateTime.UtcNow.AddMinutes(_jwt.AccessTokenLifetimeMinutes); var claims = new List { new(ClaimTypes.NameIdentifier, user.Id.ToString()), new(ClaimTypes.Name, user.Email), new(ClaimTypes.Role, user.Role.ToString()), new(JwtRegisteredClaimNames.Sid, sessionId.ToString()), new(JwtRegisteredClaimNames.Jti, jti.ToString()) }; // AZ-534 — stamp authentication-methods-reference per RFC 8176. Multi-valued: // password+TOTP login produces ["pwd","mfa"]; recovery-code login adds "recovery". var amrValues = amr?.ToArray() ?? ["pwd"]; foreach (var v in amrValues) claims.Add(new Claim("amr", v)); var tokenHandler = new JwtSecurityTokenHandler(); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = expires, Issuer = _jwt.Issuer, Audience = _jwt.Audience, SigningCredentials = signingCredentials }; var token = tokenHandler.CreateToken(tokenDescriptor); return new AccessToken(tokenHandler.WriteToken(token), expires); } }