97 lines
3.4 KiB
C#
97 lines
3.4 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using RecNet.Application.Common.Interfaces;
|
|
using RecNet.Application.Common.Tokens;
|
|
using RecNet.Domain.Entities.Profiles;
|
|
|
|
namespace RecNet.Infrastructure.Services.Tokens;
|
|
|
|
public class TokenService(IOptions<JwtOptions> options) : ITokenService
|
|
{
|
|
public TokenResult GenerateProfileToken(Profile profile)
|
|
{
|
|
var jwtOptions = options.Value;
|
|
if (string.IsNullOrWhiteSpace(jwtOptions.Secret))
|
|
throw new InvalidOperationException("JWT secret is not configured.");
|
|
|
|
var expiresAt = DateTimeOffset.UtcNow.AddMinutes(jwtOptions.ExpiryMinutes);
|
|
var signingCredentials = new SigningCredentials(
|
|
CreateSecurityKey(jwtOptions),
|
|
SecurityAlgorithms.HmacSha256);
|
|
|
|
var claims = new List<Claim>
|
|
{
|
|
new(JwtRegisteredClaimNames.Sub, profile.ProfileId.ToString()),
|
|
new("rn.plat", ((int)profile.Platform).ToString()),
|
|
new("rn.platid", profile.PlatformId)
|
|
};
|
|
|
|
if (profile.IsModerator)
|
|
claims.Add(new Claim(ClaimTypes.Role, "moderator"));
|
|
|
|
var token = new JwtSecurityToken(
|
|
issuer: jwtOptions.Issuer,
|
|
audience: jwtOptions.Audience,
|
|
claims: claims,
|
|
expires: expiresAt.UtcDateTime,
|
|
signingCredentials: signingCredentials);
|
|
|
|
return new TokenResult(
|
|
new JwtSecurityTokenHandler().WriteToken(token),
|
|
jwtOptions.ExpiryMinutes * 60);
|
|
}
|
|
|
|
public TokenVerifyResult VerifyToken(string token)
|
|
{
|
|
var jwtOptions = options.Value;
|
|
if (string.IsNullOrWhiteSpace(jwtOptions.Secret))
|
|
throw new InvalidOperationException("JWT secret is not configured.");
|
|
|
|
if (string.IsNullOrWhiteSpace(token))
|
|
return TokenVerifyResult.Failure();
|
|
|
|
var tokenHandler = new JwtSecurityTokenHandler();
|
|
if (!tokenHandler.CanReadToken(token))
|
|
return TokenVerifyResult.Failure();
|
|
|
|
var validationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuerSigningKey = true,
|
|
IssuerSigningKey = CreateSecurityKey(jwtOptions),
|
|
ValidateIssuer = true,
|
|
ValidIssuer = jwtOptions.Issuer,
|
|
ValidateAudience = true,
|
|
ValidAudience = jwtOptions.Audience,
|
|
ValidateLifetime = true,
|
|
ClockSkew = TimeSpan.FromMinutes(1)
|
|
};
|
|
|
|
try
|
|
{
|
|
var principal = tokenHandler.ValidateToken(token, validationParameters, out _);
|
|
|
|
var profileIdClaim =
|
|
principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value ??
|
|
principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
|
|
|
return Guid.TryParse(profileIdClaim, out var profileId)
|
|
? TokenVerifyResult.Success(profileId)
|
|
: TokenVerifyResult.Failure();
|
|
}
|
|
catch (SecurityTokenException)
|
|
{
|
|
return TokenVerifyResult.Failure();
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
return TokenVerifyResult.Failure();
|
|
}
|
|
}
|
|
|
|
private static SymmetricSecurityKey CreateSecurityKey(JwtOptions jwtOptions)
|
|
=> new(Encoding.UTF8.GetBytes(jwtOptions.Secret));
|
|
}
|