aboutsummaryrefslogtreecommitdiff
path: root/BackEnd
diff options
context:
space:
mode:
Diffstat (limited to 'BackEnd')
-rw-r--r--BackEnd/Timeline.ErrorCodes/ErrorCodes.cs10
-rw-r--r--BackEnd/Timeline/Auth/MyAuthenticationHandler.cs63
-rw-r--r--BackEnd/Timeline/Controllers/TokenController.cs6
-rw-r--r--BackEnd/Timeline/Services/UserTokenException.cs43
-rw-r--r--BackEnd/Timeline/Services/UserTokenHandler.cs (renamed from BackEnd/Timeline/Services/UserTokenService.cs)6
-rw-r--r--BackEnd/Timeline/Services/UserTokenManager.cs28
-rw-r--r--BackEnd/Timeline/Startup.cs2
7 files changed, 124 insertions, 34 deletions
diff --git a/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs b/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs
index 4c3b6cd8..87d451f2 100644
--- a/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs
+++ b/BackEnd/Timeline.ErrorCodes/ErrorCodes.cs
@@ -13,6 +13,7 @@
public const int InvalidModel = 1_000_0001;
public const int Forbid = 1_000_0002;
public const int UnknownEndpoint = 1_000_0003;
+ public const int Unauthorized = 1_000_0004;
public static class Header
{
@@ -24,6 +25,15 @@
{
public const int TooBig = 1_000_11_01;
}
+
+ public static class Token
+ {
+ public const int TimeExpired = 1_000_21_01;
+ public const int VersionExpired = 1_000_21_02;
+ public const int BadFormat = 1_000_21_03;
+ public const int UserNotExist = 1_000_21_04;
+ public const int Unknown = 1_000_21_05;
+ }
}
public static class UserCommon
diff --git a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs
index e4122c65..f1f71b20 100644
--- a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs
+++ b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs
@@ -1,13 +1,18 @@
using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using System;
+using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
+using System.Text.Json;
using System.Threading.Tasks;
+using Timeline.Models;
+using Timeline.Models.Http;
using Timeline.Services;
using static Timeline.Resources.Authentication.AuthHandler;
@@ -30,16 +35,33 @@ namespace Timeline.Auth
public class MyAuthenticationHandler : AuthenticationHandler<MyAuthenticationOptions>
{
+ private const string TokenErrorCodeKey = "TokenErrorCode";
+
+ private static CommonResponse CreateChallengeResponseBody(int errorCode)
+ {
+ return new CommonResponse(errorCode, errorCode switch
+ {
+ ErrorCodes.Common.Token.TimeExpired => "The token is out of date and expired. Please create a new one.",
+ ErrorCodes.Common.Token.VersionExpired => "The token is of old version and expired. Please create a new one.",
+ ErrorCodes.Common.Token.BadFormat => "The token is of bad format. It might not be created by this server.",
+ ErrorCodes.Common.Token.UserNotExist => "The owner of the token does not exist. It might have been deleted.",
+ _ => "Unknown error."
+ });
+ }
+
private readonly ILogger<MyAuthenticationHandler> _logger;
private readonly IUserTokenManager _userTokenManager;
private readonly IUserPermissionService _userPermissionService;
- public MyAuthenticationHandler(IOptionsMonitor<MyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserTokenManager userTokenManager, IUserPermissionService userPermissionService)
+ private readonly IOptionsMonitor<JsonOptions> _jsonOptions;
+
+ public MyAuthenticationHandler(IOptionsMonitor<MyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserTokenManager userTokenManager, IUserPermissionService userPermissionService, IOptionsMonitor<JsonOptions> jsonOptions)
: base(options, logger, encoder, clock)
{
_logger = logger.CreateLogger<MyAuthenticationHandler>();
_userTokenManager = userTokenManager;
_userPermissionService = userPermissionService;
+ _jsonOptions = jsonOptions;
}
// return null if no token is found
@@ -49,7 +71,7 @@ namespace Timeline.Auth
string header = Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
- var token = header.Substring("Bearer ".Length).Trim();
+ var token = header["Bearer ".Length..].Trim();
_logger.LogInformation(LogTokenFoundInHeader, token);
return token;
}
@@ -98,8 +120,43 @@ namespace Timeline.Auth
catch (Exception e) when (!(e is ArgumentException))
{
_logger.LogInformation(e, LogTokenValidationFail);
- return AuthenticateResult.Fail(e);
+ return AuthenticateResult.Fail(e, new AuthenticationProperties(new Dictionary<string, string?>()
+ {
+ [TokenErrorCodeKey] = (e switch
+ {
+ UserTokenTimeExpiredException => ErrorCodes.Common.Token.TimeExpired,
+ UserTokenVersionExpiredException => ErrorCodes.Common.Token.VersionExpired,
+ UserTokenBadFormatException => ErrorCodes.Common.Token.BadFormat,
+ UserTokenUserNotExistException => ErrorCodes.Common.Token.UserNotExist,
+ _ => ErrorCodes.Common.Token.Unknown
+ }).ToString(CultureInfo.InvariantCulture)
+ }));
}
}
+
+ protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
+ {
+ Response.StatusCode = 401;
+
+ CommonResponse body;
+
+ if (properties.Items.TryGetValue(TokenErrorCodeKey, out var tokenErrorCode))
+ {
+ if (!int.TryParse(tokenErrorCode, out var errorCode))
+ errorCode = ErrorCodes.Common.Token.Unknown;
+ body = CreateChallengeResponseBody(errorCode);
+ }
+ else
+ {
+ body = new CommonResponse(ErrorCodes.Common.Unauthorized, "You must use a token to authenticate.");
+ }
+
+
+ var bodyData = JsonSerializer.SerializeToUtf8Bytes(body, typeof(CommonResponse), _jsonOptions.CurrentValue.JsonSerializerOptions);
+
+ Response.ContentType = MimeTypes.ApplicationJson;
+ Response.ContentLength = bodyData.Length;
+ await Response.Body.WriteAsync(bodyData);
+ }
}
}
diff --git a/BackEnd/Timeline/Controllers/TokenController.cs b/BackEnd/Timeline/Controllers/TokenController.cs
index b3675aad..3ff8acf5 100644
--- a/BackEnd/Timeline/Controllers/TokenController.cs
+++ b/BackEnd/Timeline/Controllers/TokenController.cs
@@ -117,12 +117,12 @@ namespace Timeline.Controllers
User = await _userMapper.MapToHttp(result, Url)
};
}
- catch (UserTokenTimeExpireException e)
+ catch (UserTokenTimeExpiredException e)
{
LogFailure(LogVerifyExpire, e, ("Expire Time", e.ExpireTime), ("Verify Time", e.VerifyTime));
return BadRequest(ErrorResponse.TokenController.Verify_TimeExpired());
}
- catch (UserTokenBadVersionException e)
+ catch (UserTokenVersionExpiredException e)
{
LogFailure(LogVerifyOldVersion, e, ("Token Version", e.TokenVersion), ("Required Version", e.RequiredVersion));
return BadRequest(ErrorResponse.TokenController.Verify_OldVersion());
@@ -133,7 +133,7 @@ namespace Timeline.Controllers
LogFailure(LogVerifyBadFormat, e);
return BadRequest(ErrorResponse.TokenController.Verify_BadFormat());
}
- catch (UserNotExistException e)
+ catch (UserTokenUserNotExistException e)
{
LogFailure(LogVerifyUserNotExist, e);
return BadRequest(ErrorResponse.TokenController.Verify_UserNotExist());
diff --git a/BackEnd/Timeline/Services/UserTokenException.cs b/BackEnd/Timeline/Services/UserTokenException.cs
index d25fabb3..398da41f 100644
--- a/BackEnd/Timeline/Services/UserTokenException.cs
+++ b/BackEnd/Timeline/Services/UserTokenException.cs
@@ -20,14 +20,14 @@ namespace Timeline.Services
[Serializable]
- public class UserTokenTimeExpireException : UserTokenException
+ public class UserTokenTimeExpiredException : UserTokenException
{
- public UserTokenTimeExpireException() : base(Resources.Services.Exception.UserTokenTimeExpireException) { }
- public UserTokenTimeExpireException(string message) : base(message) { }
- public UserTokenTimeExpireException(string message, Exception inner) : base(message, inner) { }
- public UserTokenTimeExpireException(string token, DateTime expireTime, DateTime verifyTime) : base(token, Resources.Services.Exception.UserTokenTimeExpireException) { ExpireTime = expireTime; VerifyTime = verifyTime; }
- public UserTokenTimeExpireException(string token, DateTime expireTime, DateTime verifyTime, Exception inner) : base(token, Resources.Services.Exception.UserTokenTimeExpireException, inner) { ExpireTime = expireTime; VerifyTime = verifyTime; }
- protected UserTokenTimeExpireException(
+ public UserTokenTimeExpiredException() : base(Resources.Services.Exception.UserTokenTimeExpireException) { }
+ 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, Resources.Services.Exception.UserTokenTimeExpireException) { ExpireTime = expireTime; VerifyTime = verifyTime; }
+ public UserTokenTimeExpiredException(string token, DateTime expireTime, DateTime verifyTime, Exception inner) : base(token, Resources.Services.Exception.UserTokenTimeExpireException, inner) { ExpireTime = expireTime; VerifyTime = verifyTime; }
+ protected UserTokenTimeExpiredException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
@@ -37,14 +37,14 @@ namespace Timeline.Services
}
[Serializable]
- public class UserTokenBadVersionException : UserTokenException
+ public class UserTokenVersionExpiredException : UserTokenException
{
- public UserTokenBadVersionException() : base(Resources.Services.Exception.UserTokenBadVersionException) { }
- public UserTokenBadVersionException(string message) : base(message) { }
- public UserTokenBadVersionException(string message, Exception inner) : base(message, inner) { }
- public UserTokenBadVersionException(string token, long tokenVersion, long requiredVersion) : base(token, Resources.Services.Exception.UserTokenBadVersionException) { TokenVersion = tokenVersion; RequiredVersion = requiredVersion; }
- public UserTokenBadVersionException(string token, long tokenVersion, long requiredVersion, Exception inner) : base(token, Resources.Services.Exception.UserTokenBadVersionException, inner) { TokenVersion = tokenVersion; RequiredVersion = requiredVersion; }
- protected UserTokenBadVersionException(
+ public UserTokenVersionExpiredException() : base(Resources.Services.Exception.UserTokenBadVersionException) { }
+ 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, Resources.Services.Exception.UserTokenBadVersionException) { TokenVersion = tokenVersion; RequiredVersion = requiredVersion; }
+ public UserTokenVersionExpiredException(string token, long tokenVersion, long requiredVersion, Exception inner) : base(token, Resources.Services.Exception.UserTokenBadVersionException, inner) { TokenVersion = tokenVersion; RequiredVersion = requiredVersion; }
+ protected UserTokenVersionExpiredException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
@@ -53,6 +53,21 @@ namespace Timeline.Services
public long RequiredVersion { get; set; }
}
+
+ [Serializable]
+ public class UserTokenUserNotExistException : UserTokenException
+ {
+ const string message = "The owner of the token does not exist.";
+
+ public UserTokenUserNotExistException() : base(message) { }
+ public UserTokenUserNotExistException(string token) : base(token, message) { }
+ public UserTokenUserNotExistException(string token, Exception inner) : base(token, message, inner) { }
+
+ protected UserTokenUserNotExistException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+ }
+
[Serializable]
public class UserTokenBadFormatException : UserTokenException
{
diff --git a/BackEnd/Timeline/Services/UserTokenService.cs b/BackEnd/Timeline/Services/UserTokenHandler.cs
index 86f3a0f7..c24a8d47 100644
--- a/BackEnd/Timeline/Services/UserTokenService.cs
+++ b/BackEnd/Timeline/Services/UserTokenHandler.cs
@@ -17,7 +17,7 @@ namespace Timeline.Services
public DateTime? ExpireAt { get; set; }
}
- public interface IUserTokenService
+ public interface IUserTokenHandler
{
/// <summary>
/// Create a token for a given token info.
@@ -40,7 +40,7 @@ namespace Timeline.Services
UserTokenInfo VerifyToken(string token);
}
- public class JwtUserTokenService : IUserTokenService
+ public class JwtUserTokenHandler : IUserTokenHandler
{
private const string VersionClaimType = "timeline_version";
@@ -50,7 +50,7 @@ namespace Timeline.Services
private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler();
private SymmetricSecurityKey _tokenSecurityKey;
- public JwtUserTokenService(IOptionsMonitor<JwtConfiguration> jwtConfig, IClock clock, DatabaseContext database)
+ public JwtUserTokenHandler(IOptionsMonitor<JwtConfiguration> jwtConfig, IClock clock, DatabaseContext database)
{
_jwtConfig = jwtConfig;
_clock = clock;
diff --git a/BackEnd/Timeline/Services/UserTokenManager.cs b/BackEnd/Timeline/Services/UserTokenManager.cs
index 78aa0b1f..898e4d6d 100644
--- a/BackEnd/Timeline/Services/UserTokenManager.cs
+++ b/BackEnd/Timeline/Services/UserTokenManager.cs
@@ -34,10 +34,10 @@ namespace Timeline.Services
/// <param name="token">The token.</param>
/// <returns>The user stored in token.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
- /// <exception cref="UserTokenTimeExpireException">Thrown when the token is expired.</exception>
- /// <exception cref="UserTokenBadVersionException">Thrown when the token is of bad version.</exception>
+ /// <exception cref="UserTokenTimeExpiredException">Thrown when the token is expired.</exception>
+ /// <exception cref="UserTokenVersionExpiredException">Thrown when the token is of bad version.</exception>
/// <exception cref="UserTokenBadFormatException">Thrown when the token is of bad format.</exception>
- /// <exception cref="UserNotExistException">Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued.</exception>
+ /// <exception cref="UserTokenUserNotExistException">Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued.</exception>
public Task<UserEntity> VerifyToken(string token);
}
@@ -46,10 +46,10 @@ namespace Timeline.Services
private readonly ILogger<UserTokenManager> _logger;
private readonly IUserService _userService;
private readonly IUserCredentialService _userCredentialService;
- private readonly IUserTokenService _userTokenService;
+ private readonly IUserTokenHandler _userTokenService;
private readonly IClock _clock;
- public UserTokenManager(ILogger<UserTokenManager> logger, IUserService userService, IUserCredentialService userCredentialService, IUserTokenService userTokenService, IClock clock)
+ public UserTokenManager(ILogger<UserTokenManager> logger, IUserService userService, IUserCredentialService userCredentialService, IUserTokenHandler userTokenService, IClock clock)
{
_logger = logger;
_userService = userService;
@@ -86,15 +86,23 @@ namespace Timeline.Services
{
var currentTime = _clock.GetCurrentTime();
if (tokenInfo.ExpireAt < currentTime)
- throw new UserTokenTimeExpireException(token, tokenInfo.ExpireAt.Value, currentTime);
+ throw new UserTokenTimeExpiredException(token, tokenInfo.ExpireAt.Value, currentTime);
}
- var user = await _userService.GetUser(tokenInfo.Id);
+ try
+ {
+ var user = await _userService.GetUser(tokenInfo.Id);
+
+ if (tokenInfo.Version < user.Version)
+ throw new UserTokenVersionExpiredException(token, tokenInfo.Version, user.Version);
- if (tokenInfo.Version < user.Version)
- throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version);
+ return user;
- return user;
+ }
+ catch (UserNotExistException e)
+ {
+ throw new UserTokenUserNotExistException(token, e);
+ }
}
}
}
diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs
index 932e03c3..c2134a94 100644
--- a/BackEnd/Timeline/Startup.cs
+++ b/BackEnd/Timeline/Startup.cs
@@ -111,7 +111,7 @@ namespace Timeline
services.AddScoped<IUserService, UserService>();
services.AddScoped<IUserCredentialService, UserCredentialService>();
services.AddScoped<IUserDeleteService, UserDeleteService>();
- services.AddScoped<IUserTokenService, JwtUserTokenService>();
+ services.AddScoped<IUserTokenHandler, JwtUserTokenHandler>();
services.AddScoped<IUserTokenManager, UserTokenManager>();
services.AddScoped<IUserPermissionService, UserPermissionService>();
services.AddUserAvatarService();