80 lines
3.0 KiB
C#
80 lines
3.0 KiB
C#
using Lactose.Repositories;
|
|
using Lactose.Services;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using System.Security.Claims;
|
|
|
|
namespace Lactose.Authorization;
|
|
|
|
/*
|
|
* Technical note:
|
|
*
|
|
* The RefreshTokenTransformation is used to add a claim to the user's claims principal that indicates if
|
|
* the user's login is still valid. while UnexpiredLoginRequirement is used to check the claim.
|
|
*
|
|
* More Claim Transformation and Requirements can be added with IClaimsTransformation IAuthorizationRequirement and
|
|
* added to Program.cs
|
|
*/
|
|
|
|
/// <summary>
|
|
/// Authorization requirement that checks whether the user's login (refresh token) is still valid.
|
|
/// </summary>
|
|
public class UnexpiredLoginRequirement : IAuthorizationRequirement {
|
|
|
|
/// <summary>
|
|
/// Handles the <see cref="UnexpiredLoginRequirement"/> by checking the login validity claim.
|
|
/// </summary>
|
|
public class RefreshTokenValidityHandler : AuthorizationHandler<UnexpiredLoginRequirement> {
|
|
/// <inheritdoc />
|
|
protected override Task HandleRequirementAsync(
|
|
AuthorizationHandlerContext context,
|
|
UnexpiredLoginRequirement requirement
|
|
) {
|
|
Claim? firstOrDefault
|
|
= context.User.Claims.FirstOrDefault(claim => claim.Type == RefreshTokenTransformation.ClaimType);
|
|
|
|
if (firstOrDefault is not { Value: "true" }) {
|
|
context.Fail();
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
context.Succeed(requirement);
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Claims transformation that adds a login validity claim based on the user's refresh token status.
|
|
/// </summary>
|
|
public class RefreshTokenTransformation(
|
|
IUserRepository userRepository,
|
|
LactoseAuthService authService
|
|
) : IClaimsTransformation
|
|
{
|
|
/// <summary>
|
|
/// The claim type used to indicate login validity.
|
|
/// </summary>
|
|
public const string ClaimType = "LoginValid";
|
|
/// <inheritdoc />
|
|
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
|
|
{
|
|
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
|
|
if (principal.HasClaim(claim => claim.Type == ClaimType)) {
|
|
foreach (ClaimsIdentity identity in principal.Identities) {
|
|
if (identity.HasClaim(claim => claim.Type == ClaimType)) {
|
|
identity.RemoveClaim(identity.FindFirst(ClaimType)!);
|
|
}
|
|
}
|
|
}
|
|
var userData = authService.GetUserData(principal);
|
|
if (userData == null) return Task.FromResult(principal);
|
|
var user = userRepository.Find(userData.Id);
|
|
if (user == null) return Task.FromResult(principal);
|
|
if (string.IsNullOrEmpty(user.RefreshToken) || user.RefreshTokenExpires == null || user.RefreshTokenExpires < DateTime.UtcNow) return Task.FromResult(principal);
|
|
claimsIdentity.AddClaim(new Claim(ClaimType, "true", ClaimValueTypes.Boolean));
|
|
principal.AddIdentity(claimsIdentity);
|
|
return Task.FromResult(principal);
|
|
}
|
|
}
|