From da9139b7bab95f6e5ba5f4bb2d99011c2d6db03a Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 23 Mar 2022 21:30:14 +0800 Subject: … MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Token/DatabaseUserTokenHandler.cs | 0 .../Timeline/Services/Token/IUserTokenHandler.cs | 29 --- .../Timeline/Services/Token/IUserTokenManager.cs | 35 --- .../Timeline/Services/Token/IUserTokenService.cs | 45 ++++ .../Token/JwtUserTokenBadFormatException.cs | 47 ---- .../Timeline/Services/Token/Resource.Designer.cs | 276 ++++++--------------- BackEnd/Timeline/Services/Token/Resource.resx | 165 +++++------- .../Services/Token/SecureRandomUserTokenService.cs | 125 ++++++++++ .../TokenServicesServiceColletionExtensions.cs | 4 +- .../Services/Token/UserTokenBadFormatException.cs | 17 -- .../Services/Token/UserTokenExpiredException.cs | 21 ++ .../Timeline/Services/Token/UserTokenHandler.cs | 117 --------- BackEnd/Timeline/Services/Token/UserTokenInfo.cs | 6 +- .../Timeline/Services/Token/UserTokenManager.cs | 102 -------- .../Token/UserTokenTimeExpiredException.cs | 21 -- .../Token/UserTokenUserNotExistException.cs | 16 -- .../Token/UserTokenVersionExpiredException.cs | 21 -- 17 files changed, 336 insertions(+), 711 deletions(-) delete mode 100644 BackEnd/Timeline/Services/Token/DatabaseUserTokenHandler.cs delete mode 100644 BackEnd/Timeline/Services/Token/IUserTokenHandler.cs delete mode 100644 BackEnd/Timeline/Services/Token/IUserTokenManager.cs create mode 100644 BackEnd/Timeline/Services/Token/IUserTokenService.cs delete mode 100644 BackEnd/Timeline/Services/Token/JwtUserTokenBadFormatException.cs create mode 100644 BackEnd/Timeline/Services/Token/SecureRandomUserTokenService.cs delete mode 100644 BackEnd/Timeline/Services/Token/UserTokenBadFormatException.cs create mode 100644 BackEnd/Timeline/Services/Token/UserTokenExpiredException.cs delete mode 100644 BackEnd/Timeline/Services/Token/UserTokenHandler.cs delete mode 100644 BackEnd/Timeline/Services/Token/UserTokenManager.cs delete mode 100644 BackEnd/Timeline/Services/Token/UserTokenTimeExpiredException.cs delete mode 100644 BackEnd/Timeline/Services/Token/UserTokenUserNotExistException.cs delete mode 100644 BackEnd/Timeline/Services/Token/UserTokenVersionExpiredException.cs (limited to 'BackEnd/Timeline/Services/Token') diff --git a/BackEnd/Timeline/Services/Token/DatabaseUserTokenHandler.cs b/BackEnd/Timeline/Services/Token/DatabaseUserTokenHandler.cs deleted file mode 100644 index e69de29b..00000000 diff --git a/BackEnd/Timeline/Services/Token/IUserTokenHandler.cs b/BackEnd/Timeline/Services/Token/IUserTokenHandler.cs deleted file mode 100644 index 62e01de5..00000000 --- a/BackEnd/Timeline/Services/Token/IUserTokenHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Timeline.Services.Token -{ - public interface IUserTokenHandler - { - /// - /// Create a token for a given token info. - /// - /// The info to generate token. - /// Return the generated token. - /// Thrown when is null. - Task GenerateTokenAsync(UserTokenInfo tokenInfo); - - /// - /// Verify a token and get the saved info. Do not validate lifetime!!! - /// - /// The token to verify. - /// The saved info in token. - /// Thrown when is null. - /// Thrown when the token is of bad format. - /// - /// If this method throw , it usually means the token is not created by this service. - /// Do not check expire time in this method, only check whether it is present. - /// - Task ValidateTokenAsync(string token); - } -} diff --git a/BackEnd/Timeline/Services/Token/IUserTokenManager.cs b/BackEnd/Timeline/Services/Token/IUserTokenManager.cs deleted file mode 100644 index 39009d69..00000000 --- a/BackEnd/Timeline/Services/Token/IUserTokenManager.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Services.User; - -namespace Timeline.Services.Token -{ - public interface IUserTokenManager - { - /// - /// Try to create a token for given username and password. - /// - /// The username. - /// The password. - /// The expire time of the token. - /// The created token and the user info. - /// Thrown when or is null. - /// Thrown when is of bad format. - /// Thrown when the user with does not exist. - /// Thrown when is wrong. - public Task CreateTokenAsync(string username, string password, DateTime? expireAt = null); - - /// - /// Verify a token and get the saved user info. This also check the database for existence of the user. - /// - /// The token. - /// The user stored in token. - /// Thrown when is null. - /// Thrown when the token is expired. - /// Thrown when the token is of bad version. - /// Thrown when the token is of bad format. - /// Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued. - public Task VerifyTokenAsync(string token); - } -} diff --git a/BackEnd/Timeline/Services/Token/IUserTokenService.cs b/BackEnd/Timeline/Services/Token/IUserTokenService.cs new file mode 100644 index 00000000..22fb0fb4 --- /dev/null +++ b/BackEnd/Timeline/Services/Token/IUserTokenService.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading.Tasks; + +namespace Timeline.Services.Token +{ + public interface IUserTokenService + { + /// + /// Create a token for a user. Please ensure the user id exists! + /// + /// The user id. + /// The expire time of the token. + /// Return the generated token. + Task CreateTokenAsync(long userId, DateTime? expireTime); + + /// + /// Verify a token and get the info of the token. + /// + /// The token to verify. + /// The info of the token. + /// Thrown when is null. + /// Thrown when the token is not valid for reasons other than expired. + /// Thrown when the token is expired. + Task ValidateTokenAsync(string token); + + /// + /// Revoke a token to make it no longer valid. + /// + /// The token to revoke. + /// Return true if a token is revoked. + /// Thrown when is null. + /// + /// This method returns true if a real token is revoked and returns false if the token is not valid. + /// If the token is expired, false is return. + /// + Task RevokeTokenAsync(string token); + + /// + /// Revoke all tokens of a user. + /// + /// User id of tokens. + /// Return the task. + Task RevokeAllTokenByUserIdAsync(long userId); + } +} diff --git a/BackEnd/Timeline/Services/Token/JwtUserTokenBadFormatException.cs b/BackEnd/Timeline/Services/Token/JwtUserTokenBadFormatException.cs deleted file mode 100644 index 7d272170..00000000 --- a/BackEnd/Timeline/Services/Token/JwtUserTokenBadFormatException.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Globalization; - -namespace Timeline.Services.Token -{ - [Serializable] - public class JwtUserTokenBadFormatException : UserTokenBadFormatException - { - public enum ErrorKind - { - NoIdClaim, - IdClaimBadFormat, - NoVersionClaim, - VersionClaimBadFormat, - NoExp, - Other - } - - public JwtUserTokenBadFormatException() : this("", ErrorKind.Other) { } - public JwtUserTokenBadFormatException(string message) : base(message) { } - public JwtUserTokenBadFormatException(string message, Exception inner) : base(message, inner) { } - - public JwtUserTokenBadFormatException(string token, ErrorKind type) : base(token, GetErrorMessage(type)) { ErrorType = type; } - public JwtUserTokenBadFormatException(string token, ErrorKind type, Exception inner) : base(token, GetErrorMessage(type), inner) { ErrorType = type; } - public JwtUserTokenBadFormatException(string token, ErrorKind type, string message, Exception inner) : base(token, message, inner) { ErrorType = type; } - protected JwtUserTokenBadFormatException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public ErrorKind ErrorType { get; set; } - - private static string GetErrorMessage(ErrorKind type) - { - var reason = type switch - { - ErrorKind.NoIdClaim => Resource.ExceptionJwtUserTokenBadFormatReasonIdMissing, - ErrorKind.IdClaimBadFormat => Resource.ExceptionJwtUserTokenBadFormatReasonIdBadFormat, - ErrorKind.NoVersionClaim => Resource.ExceptionJwtUserTokenBadFormatReasonVersionMissing, - ErrorKind.VersionClaimBadFormat => Resource.ExceptionJwtUserTokenBadFormatReasonVersionBadFormat, - ErrorKind.Other => Resource.ExceptionJwtUserTokenBadFormatReasonOthers, - _ => Resource.ExceptionJwtUserTokenBadFormatReasonUnknown - }; - - return string.Format(CultureInfo.CurrentCulture, Resource.ExceptionJwtUserTokenBadFormat, reason); - } - } -} diff --git a/BackEnd/Timeline/Services/Token/Resource.Designer.cs b/BackEnd/Timeline/Services/Token/Resource.Designer.cs index ac6f3707..c1bd30ef 100644 --- a/BackEnd/Timeline/Services/Token/Resource.Designer.cs +++ b/BackEnd/Timeline/Services/Token/Resource.Designer.cs @@ -1,198 +1,78 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Services.Token { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resource { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resource() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Services.Token.Resource", typeof(Resource).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Jwt key is not found. Maybe you forget to do the migration.. - /// - internal static string ExceptionJwtKeyNotExist { - get { - return ResourceManager.GetString("ExceptionJwtKeyNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token didn't pass verification because {0}.. - /// - internal static string ExceptionJwtUserTokenBadFormat { - get { - return ResourceManager.GetString("ExceptionJwtUserTokenBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to id claim is not a number. - /// - internal static string ExceptionJwtUserTokenBadFormatReasonIdBadFormat { - get { - return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonIdBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to id claim does not exist. - /// - internal static string ExceptionJwtUserTokenBadFormatReasonIdMissing { - get { - return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonIdMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to other error, see inner exception for information. - /// - internal static string ExceptionJwtUserTokenBadFormatReasonOthers { - get { - return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonOthers", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to unknown error. - /// - internal static string ExceptionJwtUserTokenBadFormatReasonUnknown { - get { - return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonUnknown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to version claim is not a number.. - /// - internal static string ExceptionJwtUserTokenBadFormatReasonVersionBadFormat { - get { - return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonVersionBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to version claim does not exist.. - /// - internal static string ExceptionJwtUserTokenBadFormatReasonVersionMissing { - get { - return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonVersionMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token is of bad format, which means it may not be created by the server.. - /// - internal static string ExceptionUserTokenBadFormat { - get { - return ResourceManager.GetString("ExceptionUserTokenBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token is expired because its expiration time has passed.. - /// - internal static string ExceptionUserTokenTimeExpired { - get { - return ResourceManager.GetString("ExceptionUserTokenTimeExpired", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The owner user of the token does not exist.. - /// - internal static string ExceptionUserTokenUserNotExist { - get { - return ResourceManager.GetString("ExceptionUserTokenUserNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token is of bad version.. - /// - internal static string ExceptionUserTokenVersionExpired { - get { - return ResourceManager.GetString("ExceptionUserTokenVersionExpired", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A token is created for user with username={0}, id={1}.. - /// - internal static string LogTokenCreate { - get { - return ResourceManager.GetString("LogTokenCreate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A token of user with username = {0}, id = {1} is verified successfully.. - /// - internal static string LogTokenVerified { - get { - return ResourceManager.GetString("LogTokenVerified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A token fails to be verified.. - /// - internal static string LogTokenVerifiedFail { - get { - return ResourceManager.GetString("LogTokenVerifiedFail", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Timeline.Services.Token { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// This class was generated by MSBuild using the GenerateResource task. + /// To add or remove a member, edit your .resx file then rerun MSBuild. + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Build.Tasks.StronglyTypedResourceBuilder", "15.1.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Services.Token.Resource", typeof(Resource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The token is expired because its expiration time has passed.. + /// + internal static string ExceptionUserTokenExpired { + get { + return ResourceManager.GetString("ExceptionUserTokenExpired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The token is invalid.. + /// + internal static string ExceptionUserTokenInvalid { + get { + return ResourceManager.GetString("ExceptionUserTokenInvalid", resourceCulture); + } + } + } +} diff --git a/BackEnd/Timeline/Services/Token/Resource.resx b/BackEnd/Timeline/Services/Token/Resource.resx index 06bf03f6..9ea2e63a 100644 --- a/BackEnd/Timeline/Services/Token/Resource.resx +++ b/BackEnd/Timeline/Services/Token/Resource.resx @@ -1,6 +1,6 @@  - - - - - - - + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Jwt key is not found. Maybe you forget to do the migration. - - - The token didn't pass verification because {0}. - - - id claim is not a number - - - id claim does not exist - - - other error, see inner exception for information - - - unknown error - - - version claim is not a number. - - - version claim does not exist. - - - The token is of bad format, which means it may not be created by the server. - - - The token is expired because its expiration time has passed. - - - The owner user of the token does not exist. - - - The token is of bad version. - - - A token is created for user with username={0}, id={1}. - - - A token of user with username = {0}, id = {1} is verified successfully. - - - A token fails to be verified. - + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The token is invalid. + + + The token is expired because its expiration time has passed. + \ No newline at end of file diff --git a/BackEnd/Timeline/Services/Token/SecureRandomUserTokenService.cs b/BackEnd/Timeline/Services/Token/SecureRandomUserTokenService.cs new file mode 100644 index 00000000..404862d4 --- /dev/null +++ b/BackEnd/Timeline/Services/Token/SecureRandomUserTokenService.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Timeline.Configs; +using Timeline.Entities; + +namespace Timeline.Services.Token +{ + public class SecureRandomUserTokenService : IUserTokenService, IDisposable + { + private DatabaseContext _databaseContext; + private ILogger _logger; + private RandomNumberGenerator _secureRandom; + private IOptionsMonitor _optionMonitor; + private IClock _clock; + + public SecureRandomUserTokenService(DatabaseContext databaseContext, ILogger logger, IOptionsMonitor optionMonitor, IClock clock) + { + _databaseContext = databaseContext; + _logger = logger; + _secureRandom = RandomNumberGenerator.Create(); + _optionMonitor = optionMonitor; + _clock = clock; + } + + public void Dispose() + { + _secureRandom.Dispose(); + } + + private string GenerateSecureRandomTokenString() + { + var option = _optionMonitor.CurrentValue; + var tokenLength = option.TokenLength ?? 32; + var buffer = new byte[tokenLength]; + _secureRandom.GetBytes(buffer); + return Convert.ToHexString(buffer); + } + + /// + public async Task CreateTokenAsync(long userId, DateTime? expireTime) + { + var currentTime = _clock.GetCurrentTime(); + + if (expireTime is not null && expireTime > currentTime) + { + _logger.LogWarning("The expire time of the token has already passed."); + } + + UserTokenEntity entity = new UserTokenEntity + { + UserId = userId, + Token = GenerateSecureRandomTokenString(), + ExpireAt = expireTime, + CreateAt = currentTime, + Deleted = false + }; + + _databaseContext.UserTokens.Add(entity); + await _databaseContext.SaveChangesAsync(); + + _logger.LogInformation("A user token is created with user id {}.", userId); + + return entity.Token; + } + + /// + public async Task ValidateTokenAsync(string token) + { + var entity = await _databaseContext.UserTokens.Where(t => t.Token == token && !t.Deleted).SingleOrDefaultAsync(); + + if (entity is null) + { + throw new UserTokenException(token, Resource.ExceptionUserTokenInvalid); + } + + var currentTime = _clock.GetCurrentTime(); + + if (entity.ExpireAt.HasValue && entity.ExpireAt > currentTime) + { + throw new UserTokenExpiredException(token, entity.ExpireAt.Value, currentTime); + } + + return new UserTokenInfo() + { + UserId = entity.UserId, + ExpireAt = entity.ExpireAt, + CreateAt = entity.CreateAt + }; + } + + /// + public async Task RevokeTokenAsync(string token) + { + var entity = await _databaseContext.UserTokens.Where(t => t.Token == token && t.Deleted == false).SingleOrDefaultAsync(); + if (entity is not null) + { + entity.Deleted = true; + await _databaseContext.SaveChangesAsync(); + + _logger.LogInformation("A token is revoked with user id {}.", entity.UserId); + + return entity.ExpireAt <= _clock.GetCurrentTime(); + } + return false; + } + + /// + public async Task RevokeAllTokenByUserIdAsync(long userId) + { + List entities = await _databaseContext.UserTokens.Where(t => t.UserId == userId && t.Deleted == false).ToListAsync(); + foreach (var entity in entities) + { + entity.Deleted = true; + } + await _databaseContext.SaveChangesAsync(); + _logger.LogInformation("All tokens of user with id {} are revoked.", userId); + } + } +} \ No newline at end of file diff --git a/BackEnd/Timeline/Services/Token/TokenServicesServiceColletionExtensions.cs b/BackEnd/Timeline/Services/Token/TokenServicesServiceColletionExtensions.cs index 1ad84311..cf4eeb11 100644 --- a/BackEnd/Timeline/Services/Token/TokenServicesServiceColletionExtensions.cs +++ b/BackEnd/Timeline/Services/Token/TokenServicesServiceColletionExtensions.cs @@ -9,9 +9,7 @@ namespace Timeline.Services.Token public static IServiceCollection AddTokenServices(this IServiceCollection services, IConfiguration configuration) { services.Configure(configuration.GetSection("Token")); - services.Configure(configuration.GetSection("Jwt")); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); return services; } } diff --git a/BackEnd/Timeline/Services/Token/UserTokenBadFormatException.cs b/BackEnd/Timeline/Services/Token/UserTokenBadFormatException.cs deleted file mode 100644 index 39ed1be4..00000000 --- a/BackEnd/Timeline/Services/Token/UserTokenBadFormatException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Timeline.Services.Token -{ - [Serializable] - public class UserTokenBadFormatException : UserTokenException - { - public UserTokenBadFormatException() : base(Resource.ExceptionUserTokenBadFormat) { } - public UserTokenBadFormatException(string token) : base(token, Resource.ExceptionUserTokenBadFormat) { } - public UserTokenBadFormatException(string token, string message) : base(token, message) { } - public UserTokenBadFormatException(string token, Exception inner) : base(token, Resource.ExceptionUserTokenBadFormat, inner) { } - public UserTokenBadFormatException(string token, string message, Exception inner) : base(token, message, inner) { } - protected UserTokenBadFormatException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - } -} diff --git a/BackEnd/Timeline/Services/Token/UserTokenExpiredException.cs b/BackEnd/Timeline/Services/Token/UserTokenExpiredException.cs new file mode 100644 index 00000000..5e91ca6c --- /dev/null +++ b/BackEnd/Timeline/Services/Token/UserTokenExpiredException.cs @@ -0,0 +1,21 @@ +using System; + +namespace Timeline.Services.Token +{ + [Serializable] + public class UserTokenExpiredException : UserTokenException + { + public UserTokenExpiredException() : base(Resource.ExceptionUserTokenExpired) { } + public UserTokenExpiredException(string message) : base(message) { } + public UserTokenExpiredException(string message, Exception inner) : base(message, inner) { } + public UserTokenExpiredException(string token, DateTime expireTime, DateTime verifyTime) : base(token, Resource.ExceptionUserTokenExpired) { ExpireTime = expireTime; VerifyTime = verifyTime; } + public UserTokenExpiredException(string token, DateTime expireTime, DateTime verifyTime, Exception inner) : base(token, Resource.ExceptionUserTokenExpired, inner) { ExpireTime = expireTime; VerifyTime = verifyTime; } + protected UserTokenExpiredException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + + public DateTime ExpireTime { get; private set; } + + public DateTime VerifyTime { get; private set; } + } +} diff --git a/BackEnd/Timeline/Services/Token/UserTokenHandler.cs b/BackEnd/Timeline/Services/Token/UserTokenHandler.cs deleted file mode 100644 index 03b07b53..00000000 --- a/BackEnd/Timeline/Services/Token/UserTokenHandler.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Globalization; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using Timeline.Configs; -using Timeline.Entities; - -namespace Timeline.Services.Token -{ - public class JwtUserTokenHandler : IUserTokenHandler - { - private const string VersionClaimType = "timeline_version"; - - private readonly IOptionsMonitor _jwtConfig; - private readonly IClock _clock; - - private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler(); - private SymmetricSecurityKey _tokenSecurityKey; - - public JwtUserTokenHandler(IOptionsMonitor jwtConfig, IClock clock, DatabaseContext database) - { - _jwtConfig = jwtConfig; - _clock = clock; - - var key = database.JwtToken.Select(t => t.Key).SingleOrDefault(); - - if (key == null) - { - throw new InvalidOperationException(Resource.ExceptionJwtKeyNotExist); - } - - _tokenSecurityKey = new SymmetricSecurityKey(key); - } - - public Task GenerateTokenAsync(UserTokenInfo tokenInfo) - { - if (tokenInfo == null) - throw new ArgumentNullException(nameof(tokenInfo)); - - var config = _jwtConfig.CurrentValue; - - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tokenInfo.Id.ToString(CultureInfo.InvariantCulture.NumberFormat), ClaimValueTypes.Integer64)); - identity.AddClaim(new Claim(VersionClaimType, tokenInfo.Version.ToString(CultureInfo.InvariantCulture.NumberFormat), ClaimValueTypes.Integer64)); - - var tokenDescriptor = new SecurityTokenDescriptor() - { - Subject = identity, - Issuer = config.Issuer, - Audience = config.Audience, - SigningCredentials = new SigningCredentials(_tokenSecurityKey, SecurityAlgorithms.HmacSha384), - IssuedAt = _clock.GetCurrentTime(), - Expires = tokenInfo.ExpireAt, - NotBefore = _clock.GetCurrentTime() // I must explicitly set this or it will use the current time by default and mock is not work in which case test will not pass. - }; - - var token = _tokenHandler.CreateToken(tokenDescriptor); - var tokenString = _tokenHandler.WriteToken(token); - - return Task.FromResult(tokenString); - } - - - public Task ValidateTokenAsync(string token) - { - if (token == null) - throw new ArgumentNullException(nameof(token)); - - var config = _jwtConfig.CurrentValue; - try - { - var principal = _tokenHandler.ValidateToken(token, new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidateIssuerSigningKey = true, - ValidateLifetime = false, - ValidIssuer = config.Issuer, - ValidAudience = config.Audience, - IssuerSigningKey = _tokenSecurityKey - }, out var t); - - var idClaim = principal.FindFirstValue(ClaimTypes.NameIdentifier); - if (idClaim == null) - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.NoIdClaim); - if (!long.TryParse(idClaim, out var id)) - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.IdClaimBadFormat); - - var versionClaim = principal.FindFirstValue(VersionClaimType); - if (versionClaim == null) - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.NoVersionClaim); - if (!long.TryParse(versionClaim, out var version)) - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.VersionClaimBadFormat); - - var decodedToken = (JwtSecurityToken)t; - var exp = decodedToken.Payload.Exp; - if (exp is null) - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.NoExp); - - return Task.FromResult(new UserTokenInfo - { - Id = id, - Version = version, - ExpireAt = EpochTime.DateTime(exp.Value) - }); - } - catch (Exception e) when (e is SecurityTokenException || e is ArgumentException) - { - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.Other, e); - } - } - } -} diff --git a/BackEnd/Timeline/Services/Token/UserTokenInfo.cs b/BackEnd/Timeline/Services/Token/UserTokenInfo.cs index 547f5ba6..b1a386d1 100644 --- a/BackEnd/Timeline/Services/Token/UserTokenInfo.cs +++ b/BackEnd/Timeline/Services/Token/UserTokenInfo.cs @@ -4,8 +4,8 @@ namespace Timeline.Services.Token { public class UserTokenInfo { - public long Id { get; set; } - public long Version { get; set; } - public DateTime ExpireAt { get; set; } + public long UserId { get; set; } + public DateTime? ExpireAt { get; set; } + public DateTime? CreateAt { get; set; } } } diff --git a/BackEnd/Timeline/Services/Token/UserTokenManager.cs b/BackEnd/Timeline/Services/Token/UserTokenManager.cs deleted file mode 100644 index bdb229f0..00000000 --- a/BackEnd/Timeline/Services/Token/UserTokenManager.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; -using System.Threading.Tasks; -using Timeline.Configs; -using Timeline.Entities; -using Timeline.Helpers; -using Timeline.Services.User; - -namespace Timeline.Services.Token -{ - public class UserTokenManager : IUserTokenManager - { - private readonly ILogger _logger; - private readonly IOptionsMonitor _tokenOptionsMonitor; - private readonly IUserService _userService; - private readonly IUserTokenHandler _userTokenService; - private readonly IClock _clock; - - public UserTokenManager(ILogger logger, IOptionsMonitor tokenOptionsMonitor, IUserService userService, IUserTokenHandler userTokenService, IClock clock) - { - _logger = logger; - _tokenOptionsMonitor = tokenOptionsMonitor; - _userService = userService; - _userTokenService = userTokenService; - _clock = clock; - } - - public async Task CreateTokenAsync(string username, string password, DateTime? expireAt = null) - { - expireAt = expireAt?.MyToUtc(); - - if (username == null) - throw new ArgumentNullException(nameof(username)); - if (password == null) - throw new ArgumentNullException(nameof(password)); - - var userId = await _userService.VerifyCredential(username, password); - var user = await _userService.GetUserAsync(userId); - - var token = await _userTokenService.GenerateTokenAsync(new UserTokenInfo - { - Id = user.Id, - Version = user.Version, - ExpireAt = expireAt ?? _clock.GetCurrentTime() + TimeSpan.FromSeconds(_tokenOptionsMonitor.CurrentValue.DefaultExpireSeconds) - }); - - _logger.LogInformation(Resource.LogTokenCreate, user.Username, userId); - - return new UserTokenCreateResult { Token = token, User = user }; - } - - - public async Task VerifyTokenAsync(string token) - { - if (token == null) - throw new ArgumentNullException(nameof(token)); - - UserTokenInfo tokenInfo; - - try - { - tokenInfo = await _userTokenService.ValidateTokenAsync(token); - } - catch (UserTokenBadFormatException e) - { - _logger.LogInformation(e, Resource.LogTokenVerifiedFail); - throw; - } - - var currentTime = _clock.GetCurrentTime(); - if (tokenInfo.ExpireAt < currentTime) - { - var e = new UserTokenTimeExpiredException(token, tokenInfo.ExpireAt, currentTime); - _logger.LogInformation(e, Resource.LogTokenVerifiedFail); - throw e; - } - - try - { - var user = await _userService.GetUserAsync(tokenInfo.Id); - - if (tokenInfo.Version < user.Version) - { - var e = new UserTokenVersionExpiredException(token, tokenInfo.Version, user.Version); - _logger.LogInformation(e, Resource.LogTokenVerifiedFail); - throw e; - } - - _logger.LogInformation(Resource.LogTokenVerified, user.Username, user.Id); - - return user; - } - catch (EntityNotExistException e) - { - var exception = new UserTokenUserNotExistException(token, e); - _logger.LogInformation(exception, Resource.LogTokenVerifiedFail); - throw exception; - } - } - } -} diff --git a/BackEnd/Timeline/Services/Token/UserTokenTimeExpiredException.cs b/BackEnd/Timeline/Services/Token/UserTokenTimeExpiredException.cs deleted file mode 100644 index 6e33ab4d..00000000 --- a/BackEnd/Timeline/Services/Token/UserTokenTimeExpiredException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Timeline.Services.Token -{ - [Serializable] - public class UserTokenTimeExpiredException : UserTokenException - { - public UserTokenTimeExpiredException() : base(Resource.ExceptionUserTokenTimeExpired) { } - public UserTokenTimeExpiredException(string message) : base(message) { } - public UserTokenTimeExpiredException(string message, Exception inner) : base(message, inner) { } - public UserTokenTimeExpiredException(string token, DateTime expireTime, DateTime verifyTime) : base(token, Resource.ExceptionUserTokenTimeExpired) { ExpireTime = expireTime; VerifyTime = verifyTime; } - public UserTokenTimeExpiredException(string token, DateTime expireTime, DateTime verifyTime, Exception inner) : base(token, Resource.ExceptionUserTokenTimeExpired, inner) { ExpireTime = expireTime; VerifyTime = verifyTime; } - protected UserTokenTimeExpiredException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public DateTime ExpireTime { get; private set; } - - public DateTime VerifyTime { get; private set; } - } -} diff --git a/BackEnd/Timeline/Services/Token/UserTokenUserNotExistException.cs b/BackEnd/Timeline/Services/Token/UserTokenUserNotExistException.cs deleted file mode 100644 index 28f56938..00000000 --- a/BackEnd/Timeline/Services/Token/UserTokenUserNotExistException.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Timeline.Services.Token -{ - [Serializable] - public class UserTokenUserNotExistException : UserTokenException - { - public UserTokenUserNotExistException() : base(Resource.ExceptionUserTokenUserNotExist) { } - public UserTokenUserNotExistException(string token) : base(token, Resource.ExceptionUserTokenUserNotExist) { } - public UserTokenUserNotExistException(string token, Exception inner) : base(token, Resource.ExceptionUserTokenUserNotExist, inner) { } - - protected UserTokenUserNotExistException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - } -} diff --git a/BackEnd/Timeline/Services/Token/UserTokenVersionExpiredException.cs b/BackEnd/Timeline/Services/Token/UserTokenVersionExpiredException.cs deleted file mode 100644 index db6b4669..00000000 --- a/BackEnd/Timeline/Services/Token/UserTokenVersionExpiredException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Timeline.Services.Token -{ - [Serializable] - public class UserTokenVersionExpiredException : UserTokenException - { - public UserTokenVersionExpiredException() : base(Resource.ExceptionUserTokenVersionExpired) { } - public UserTokenVersionExpiredException(string message) : base(message) { } - public UserTokenVersionExpiredException(string message, Exception inner) : base(message, inner) { } - public UserTokenVersionExpiredException(string token, long tokenVersion, long requiredVersion) : base(token, Resource.ExceptionUserTokenVersionExpired) { TokenVersion = tokenVersion; RequiredVersion = requiredVersion; } - public UserTokenVersionExpiredException(string token, long tokenVersion, long requiredVersion, Exception inner) : base(token, Resource.ExceptionUserTokenVersionExpired, inner) { TokenVersion = tokenVersion; RequiredVersion = requiredVersion; } - protected UserTokenVersionExpiredException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public long TokenVersion { get; set; } - - public long RequiredVersion { get; set; } - } -} -- cgit v1.2.3