using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; using WatchIt.Database; using WatchIt.Database.Model.Account; using WatchIt.WebAPI.Services.Utility.Configuration; using WatchIt.WebAPI.Services.Utility.Tokens.Exceptions; namespace WatchIt.WebAPI.Services.Utility.Tokens; public class TokensService(DatabaseContext database, IConfigurationService configurationService) : ITokensService { #region FIELDS private readonly Configuration.Model.Tokens _tokensConfig = configurationService.Data.Authentication.Tokens; #endregion #region PUBLIC METHODS public async Task CreateRefreshTokenAsync(Account account, bool extendable) { int expirationMinutes = extendable ? _tokensConfig.RefreshToken.ExtendedLifetime : _tokensConfig.RefreshToken.NormalLifetime; DateTime expirationDate = DateTime.UtcNow.AddMinutes(expirationMinutes); Guid id = Guid.NewGuid(); database.AccountRefreshTokens.Add(new AccountRefreshToken { Id = id, AccountId = account.Id, ExpirationDate = expirationDate, IsExtendable = extendable, }); await database.SaveChangesAsync(); return GenerateRefreshJwt(account, id, expirationDate, extendable); } public async Task ExtendRefreshTokenAsync(Account account, Guid id) { AccountRefreshToken? token = account.AccountRefreshTokens.FirstOrDefault(x => x.Id == id); switch (token) { case null: throw new TokenNotFoundException(); case { IsExtendable: true }: throw new TokenNotExtendableException(); } DateTime expirationDate = DateTime.UtcNow.AddMinutes(_tokensConfig.RefreshToken.ExtendedLifetime); token.ExpirationDate = expirationDate; await database.SaveChangesAsync(); return GenerateRefreshJwt(account, id, expirationDate, true); } public async Task CreateAccessTokenAsync(Account account) => await Task.Run(() => CreateAccessToken(account)); public string CreateAccessToken(Account account) { DateTime lifetime = DateTime.Now.AddMinutes(_tokensConfig.AccessToken.NormalLifetime); Guid id = Guid.NewGuid(); SecurityTokenDescriptor tokenDescriptor = CreateBaseSecurityTokenDescriptor(account, id, lifetime); tokenDescriptor.Audience = "access"; return TokenToString(tokenDescriptor); } #endregion #region PRIVATE METHODS protected string GenerateRefreshJwt(Account account, Guid id, DateTime expirationDate, bool extendable) { SecurityTokenDescriptor tokenDescriptor = CreateBaseSecurityTokenDescriptor(account, id, expirationDate); tokenDescriptor.Audience = "refresh"; tokenDescriptor.Subject.AddClaim(new Claim("extend", extendable.ToString())); return TokenToString(tokenDescriptor); } protected SecurityTokenDescriptor CreateBaseSecurityTokenDescriptor(Account account, Guid id, DateTime expirationTime) => new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new List { new Claim(JwtRegisteredClaimNames.Jti, id.ToString()), new Claim(JwtRegisteredClaimNames.Sub, account.Id.ToString()), new Claim(JwtRegisteredClaimNames.Email, account.Email), new Claim(JwtRegisteredClaimNames.UniqueName, account.Username), new Claim(JwtRegisteredClaimNames.Exp, expirationTime.Ticks.ToString()), new Claim("admin", account.IsAdmin.ToString()), }), Expires = expirationTime, Issuer = configurationService.Data.Authentication.Issuer, SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurationService.Data.Authentication.Key)), SecurityAlgorithms.HmacSha512) }; protected string TokenToString(SecurityTokenDescriptor tokenDescriptor) { JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); handler.InboundClaimTypeMap.Clear(); SecurityToken token = handler.CreateToken(tokenDescriptor); return handler.WriteToken(token); } #endregion }