aboutsummaryrefslogtreecommitdiff
path: root/BackEnd
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2022-03-23 21:30:14 +0800
committercrupest <crupest@outlook.com>2022-03-23 21:30:31 +0800
commitda9139b7bab95f6e5ba5f4bb2d99011c2d6db03a (patch)
tree051fd4ca4bc511db7e04b019a33fddaab2d0cc6b /BackEnd
parent3d6c9fd916e18c99b3a5497b8313672680571b5e (diff)
downloadtimeline-da9139b7bab95f6e5ba5f4bb2d99011c2d6db03a.tar.gz
timeline-da9139b7bab95f6e5ba5f4bb2d99011c2d6db03a.tar.bz2
timeline-da9139b7bab95f6e5ba5f4bb2d99011c2d6db03a.zip
Diffstat (limited to 'BackEnd')
-rw-r--r--BackEnd/Timeline.Tests/Helpers/TestDatabase.cs7
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs42
-rw-r--r--BackEnd/Timeline.Tests/Services/ServiceTestBase.cs10
-rw-r--r--BackEnd/Timeline/Auth/MyAuthenticationHandler.cs29
-rw-r--r--BackEnd/Timeline/Auth/Resource.Designer.cs294
-rw-r--r--BackEnd/Timeline/Auth/Resource.resx17
-rw-r--r--BackEnd/Timeline/Configs/JwtOptions.cs8
-rw-r--r--BackEnd/Timeline/Configs/TokenOptions.cs9
-rw-r--r--BackEnd/Timeline/Controllers/BookmarkTimelineController.cs8
-rw-r--r--BackEnd/Timeline/Controllers/HighlightTimelineController.cs4
-rw-r--r--BackEnd/Timeline/Controllers/MyControllerBase.cs33
-rw-r--r--BackEnd/Timeline/Controllers/Resource.Designer.cs393
-rw-r--r--BackEnd/Timeline/Controllers/Resource.resx14
-rw-r--r--BackEnd/Timeline/Controllers/TimelineController.cs10
-rw-r--r--BackEnd/Timeline/Controllers/TimelinePostController.cs12
-rw-r--r--BackEnd/Timeline/Controllers/TokenController.cs37
-rw-r--r--BackEnd/Timeline/Controllers/UserAvatarController.cs4
-rw-r--r--BackEnd/Timeline/Controllers/UserController.cs7
-rw-r--r--BackEnd/Timeline/Entities/UserTokenEntity.cs3
-rw-r--r--BackEnd/Timeline/ErrorCodes.cs11
-rw-r--r--BackEnd/Timeline/Migrations/20220323073853_AddDeletedToToken.Designer.cs628
-rw-r--r--BackEnd/Timeline/Migrations/20220323073853_AddDeletedToToken.cs26
-rw-r--r--BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs4
-rw-r--r--BackEnd/Timeline/Services/Token/DatabaseUserTokenHandler.cs0
-rw-r--r--BackEnd/Timeline/Services/Token/IUserTokenHandler.cs29
-rw-r--r--BackEnd/Timeline/Services/Token/IUserTokenManager.cs35
-rw-r--r--BackEnd/Timeline/Services/Token/IUserTokenService.cs45
-rw-r--r--BackEnd/Timeline/Services/Token/JwtUserTokenBadFormatException.cs47
-rw-r--r--BackEnd/Timeline/Services/Token/Resource.Designer.cs276
-rw-r--r--BackEnd/Timeline/Services/Token/Resource.resx165
-rw-r--r--BackEnd/Timeline/Services/Token/SecureRandomUserTokenService.cs125
-rw-r--r--BackEnd/Timeline/Services/Token/TokenServicesServiceColletionExtensions.cs4
-rw-r--r--BackEnd/Timeline/Services/Token/UserTokenBadFormatException.cs17
-rw-r--r--BackEnd/Timeline/Services/Token/UserTokenExpiredException.cs21
-rw-r--r--BackEnd/Timeline/Services/Token/UserTokenHandler.cs117
-rw-r--r--BackEnd/Timeline/Services/Token/UserTokenInfo.cs6
-rw-r--r--BackEnd/Timeline/Services/Token/UserTokenManager.cs102
-rw-r--r--BackEnd/Timeline/Services/Token/UserTokenTimeExpiredException.cs21
-rw-r--r--BackEnd/Timeline/Services/Token/UserTokenUserNotExistException.cs16
-rw-r--r--BackEnd/Timeline/Services/Token/UserTokenVersionExpiredException.cs21
-rw-r--r--BackEnd/Timeline/Services/User/CreateTokenResult.cs12
-rw-r--r--BackEnd/Timeline/Services/User/UserService.cs15
-rw-r--r--BackEnd/Timeline/Timeline.csproj3
-rw-r--r--BackEnd/Timeline/appsettings.json4
44 files changed, 1448 insertions, 1243 deletions
diff --git a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs
index 9bd690a2..5752b8cd 100644
--- a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs
+++ b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs
@@ -1,9 +1,11 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Services;
+using Timeline.Services.Token;
using Timeline.Services.User;
using Xunit;
using Xunit.Abstractions;
@@ -24,7 +26,10 @@ namespace Timeline.Tests.Helpers
using var context = CreateContext();
await context.Database.MigrateAsync();
- var userService = new UserService(NullLogger<UserService>.Instance, context, new PasswordService(), new Clock());
+ var mockUserTokenManager = new Mock<IUserTokenService>();
+ mockUserTokenManager.SetReturnsDefault(Task.CompletedTask);
+
+ var userService = new UserService(NullLogger<UserService>.Instance, context, new PasswordService(), mockUserTokenManager.Object, new Clock());
await userService.ModifyUserAsync(
await userService.GetUserIdByUsernameAsync("administrator"),
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs
index 68681b61..555ab4da 100644
--- a/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs
+++ b/BackEnd/Timeline.Tests/IntegratedTests/TokenTest.cs
@@ -1,10 +1,8 @@
using FluentAssertions;
-using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Timeline.Models.Http;
-using Timeline.Services.User;
using Xunit;
using Xunit.Abstractions;
@@ -80,48 +78,12 @@ namespace Timeline.Tests.IntegratedTests
}
[Fact]
- public async Task VerifyToken_BadFormat()
+ public async Task VerifyToken_Invalid()
{
using var client = await CreateDefaultClient();
await client.TestPostAssertErrorAsync(VerifyTokenUrl,
new HttpVerifyTokenRequest { Token = "bad token hahaha" },
- errorCode: ErrorCodes.TokenController.VerifyBadFormat);
- }
-
- [Fact]
- public async Task VerifyToken_OldVersion()
- {
- using var client = await CreateDefaultClient();
- var token = (await CreateUserTokenAsync(client, "user1", "user1pw")).Token;
-
- using (var scope = TestApp.Host.Services.CreateScope()) // UserService is scoped.
- {
- // create a user for test
- var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
- var id = await userService.GetUserIdByUsernameAsync("user1");
- await userService.ModifyUserAsync(id, new ModifyUserParams { Password = "user1pw" });
- }
-
- await client.TestPostAssertErrorAsync(VerifyTokenUrl,
- new HttpVerifyTokenRequest { Token = token },
- errorCode: ErrorCodes.TokenController.VerifyOldVersion);
- }
-
- [Fact]
- public async Task VerifyToken_UserNotExist()
- {
- using var client = await CreateDefaultClient();
- var token = (await CreateUserTokenAsync(client, "user1", "user1pw")).Token;
-
- using (var scope = TestApp.Host.Services.CreateScope()) // UserDeleteService is scoped.
- {
- var userService = scope.ServiceProvider.GetRequiredService<IUserDeleteService>();
- await userService.DeleteUserAsync("user1");
- }
-
- await client.TestPostAssertErrorAsync(VerifyTokenUrl,
- new HttpVerifyTokenRequest { Token = token },
- errorCode: ErrorCodes.TokenController.VerifyUserNotExist);
+ errorCode: ErrorCodes.TokenController.VerifyInvalid);
}
//[Fact]
diff --git a/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs b/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs
index 7153e99b..fea31d0a 100644
--- a/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs
+++ b/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs
@@ -1,7 +1,9 @@
using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Services.Timeline;
+using Timeline.Services.Token;
using Timeline.Services.User;
using Timeline.Tests.Helpers;
using Xunit;
@@ -19,6 +21,8 @@ namespace Timeline.Tests.Services
protected TestClock Clock { get; } = new TestClock();
protected UserService UserService { get; private set; } = default!;
protected TimelineService TimelineService { get; private set; } = default!;
+ protected Mock<IUserTokenService> UserTokenServiceMock { get; private set; } = default!;
+ protected IUserTokenService UserTokenService { get; private set; } = default!;
protected long UserId { get; private set; }
protected long AdminId { get; private set; }
@@ -34,7 +38,11 @@ namespace Timeline.Tests.Services
await TestDatabase.InitializeAsync();
Database = TestDatabase.CreateContext(_testOutputHelper);
- UserService = new UserService(NullLogger<UserService>.Instance, Database, new PasswordService(), Clock);
+ UserTokenServiceMock = new();
+ UserTokenServiceMock.SetReturnsDefault(Task.CompletedTask);
+ UserTokenService = UserTokenServiceMock.Object;
+
+ UserService = new UserService(NullLogger<UserService>.Instance, Database, new PasswordService(), UserTokenService, Clock);
TimelineService = new TimelineService(NullLoggerFactory.Instance, Database, UserService, Clock);
UserId = await UserService.GetUserIdByUsernameAsync("user");
diff --git a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs
index 016cc938..740f5aee 100644
--- a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs
+++ b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs
@@ -41,11 +41,8 @@ namespace Timeline.Auth
{
return 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
+ UserTokenExpiredException => ErrorCodes.Common.Token.TimeExpired,
+ _ => ErrorCodes.Common.Token.Invalid
};
}
@@ -53,25 +50,22 @@ namespace Timeline.Auth
{
return errorCode switch
{
- ErrorCodes.Common.Token.TimeExpired => Resource.MessageTokenTimeExpired,
- ErrorCodes.Common.Token.VersionExpired => Resource.MessageTokenVersionExpired,
- ErrorCodes.Common.Token.BadFormat => Resource.MessageTokenBadFormat,
- ErrorCodes.Common.Token.UserNotExist => Resource.MessageTokenUserNotExist,
- _ => Resource.MessageTokenUnknownError
+ ErrorCodes.Common.Token.TimeExpired => Resource.MessageTokenExpired,
+ _ => Resource.MessageTokenInvalid
};
}
private readonly ILogger<MyAuthenticationHandler> _logger;
- private readonly IUserTokenManager _userTokenManager;
+ private readonly IUserTokenService _userTokenService;
private readonly 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)
+ public MyAuthenticationHandler(IOptionsMonitor<MyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserTokenService userTokenService, IUserPermissionService userPermissionService, IOptionsMonitor<JsonOptions> jsonOptions)
: base(options, logger, encoder, clock)
{
_logger = logger.CreateLogger<MyAuthenticationHandler>();
- _userTokenManager = userTokenManager;
+ _userTokenService = userTokenService;
_userPermissionService = userPermissionService;
_jsonOptions = jsonOptions;
}
@@ -126,13 +120,12 @@ namespace Timeline.Auth
try
{
- var user = await _userTokenManager.VerifyTokenAsync(token);
+ var userTokenInfo = await _userTokenService.ValidateTokenAsync(token);
var identity = new ClaimsIdentity(AuthenticationConstants.Scheme);
- identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64));
- identity.AddClaim(new Claim(identity.NameClaimType, user.Username, ClaimValueTypes.String));
+ identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userTokenInfo.UserId.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64));
- var permissions = await _userPermissionService.GetPermissionsOfUserAsync(user.Id);
+ var permissions = await _userPermissionService.GetPermissionsOfUserAsync(userTokenInfo.UserId);
identity.AddClaims(permissions.Select(permission => new Claim(AuthenticationConstants.PermissionClaimName, permission.ToString(), ClaimValueTypes.String)));
var principal = new ClaimsPrincipal();
@@ -161,7 +154,7 @@ namespace Timeline.Auth
if (properties.Items.TryGetValue(TokenErrorCodeKey, out var tokenErrorCode))
{
if (!int.TryParse(tokenErrorCode, out var errorCode))
- errorCode = ErrorCodes.Common.Token.Unknown;
+ throw new Exception("A logic error: failed to parse token error code.");
body = new CommonResponse(errorCode, GetTokenErrorMessageFromErrorCode(errorCode));
}
else
diff --git a/BackEnd/Timeline/Auth/Resource.Designer.cs b/BackEnd/Timeline/Auth/Resource.Designer.cs
index 05394551..6175b266 100644
--- a/BackEnd/Timeline/Auth/Resource.Designer.cs
+++ b/BackEnd/Timeline/Auth/Resource.Designer.cs
@@ -1,162 +1,132 @@
-//------------------------------------------------------------------------------
-// <auto-generated>
-// 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.
-// </auto-generated>
-//------------------------------------------------------------------------------
-
-namespace Timeline.Auth {
- using System;
-
-
- /// <summary>
- /// A strongly-typed resource class, for looking up localized strings, etc.
- /// </summary>
- // 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() {
- }
-
- /// <summary>
- /// Returns the cached ResourceManager instance used by this class.
- /// </summary>
- [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.Auth.Resource", typeof(Resource).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- /// <summary>
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- /// </summary>
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to User identitifier claim is of bad format..
- /// </summary>
- internal static string ExceptionUserIdentifierClaimBadFormat {
- get {
- return ResourceManager.GetString("ExceptionUserIdentifierClaimBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Token is found in authorization header. Token is {0} ..
- /// </summary>
- internal static string LogTokenFoundInHeader {
- get {
- return ResourceManager.GetString("LogTokenFoundInHeader", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Token is found in query param with key &quot;{0}&quot;. Token is {1} ..
- /// </summary>
- internal static string LogTokenFoundInQuery {
- get {
- return ResourceManager.GetString("LogTokenFoundInQuery", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to No jwt token is found..
- /// </summary>
- internal static string LogTokenNotFound {
- get {
- return ResourceManager.GetString("LogTokenNotFound", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A jwt token validation failed. Error reason: {0}.
- /// </summary>
- internal static string LogTokenValidationFail {
- get {
- return ResourceManager.GetString("LogTokenValidationFail", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to You must use a token to authenticate to access this resource..
- /// </summary>
- internal static string MessageNoToken {
- get {
- return ResourceManager.GetString("MessageNoToken", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token is of bad format. It might not be created by this server..
- /// </summary>
- internal static string MessageTokenBadFormat {
- get {
- return ResourceManager.GetString("MessageTokenBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token is out of date and expired. Please create a new one..
- /// </summary>
- internal static string MessageTokenTimeExpired {
- get {
- return ResourceManager.GetString("MessageTokenTimeExpired", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A unknown error occured when verify token..
- /// </summary>
- internal static string MessageTokenUnknownError {
- get {
- return ResourceManager.GetString("MessageTokenUnknownError", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The owner of the token does not exist. It might have been deleted..
- /// </summary>
- internal static string MessageTokenUserNotExist {
- get {
- return ResourceManager.GetString("MessageTokenUserNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token is of old version and expired. Please create a new one..
- /// </summary>
- internal static string MessageTokenVersionExpired {
- get {
- return ResourceManager.GetString("MessageTokenVersionExpired", resourceCulture);
- }
- }
- }
-}
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Timeline.Auth {
+ using System;
+
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ [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() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [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.Auth.Resource", typeof(Resource).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to User identitifier claim is of bad format..
+ /// </summary>
+ internal static string ExceptionUserIdentifierClaimBadFormat {
+ get {
+ return ResourceManager.GetString("ExceptionUserIdentifierClaimBadFormat", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Token is found in authorization header. Token is {0} ..
+ /// </summary>
+ internal static string LogTokenFoundInHeader {
+ get {
+ return ResourceManager.GetString("LogTokenFoundInHeader", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Token is found in query param with key &quot;{0}&quot;. Token is {1} ..
+ /// </summary>
+ internal static string LogTokenFoundInQuery {
+ get {
+ return ResourceManager.GetString("LogTokenFoundInQuery", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No jwt token is found..
+ /// </summary>
+ internal static string LogTokenNotFound {
+ get {
+ return ResourceManager.GetString("LogTokenNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A jwt token validation failed. Error reason: {0}.
+ /// </summary>
+ internal static string LogTokenValidationFail {
+ get {
+ return ResourceManager.GetString("LogTokenValidationFail", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You must use a token to authenticate to access this resource..
+ /// </summary>
+ internal static string MessageNoToken {
+ get {
+ return ResourceManager.GetString("MessageNoToken", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The token is expired. Please create a new one..
+ /// </summary>
+ internal static string MessageTokenExpired {
+ get {
+ return ResourceManager.GetString("MessageTokenExpired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The token is invalid..
+ /// </summary>
+ internal static string MessageTokenInvalid {
+ get {
+ return ResourceManager.GetString("MessageTokenInvalid", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Auth/Resource.resx b/BackEnd/Timeline/Auth/Resource.resx
index 88cdbd6b..a2183290 100644
--- a/BackEnd/Timeline/Auth/Resource.resx
+++ b/BackEnd/Timeline/Auth/Resource.resx
@@ -135,19 +135,10 @@
<data name="MessageNoToken" xml:space="preserve">
<value>You must use a token to authenticate to access this resource.</value>
</data>
- <data name="MessageTokenBadFormat" xml:space="preserve">
- <value>The token is of bad format. It might not be created by this server.</value>
+ <data name="MessageTokenInvalid" xml:space="preserve">
+ <value>The token is invalid.</value>
</data>
- <data name="MessageTokenTimeExpired" xml:space="preserve">
- <value>The token is out of date and expired. Please create a new one.</value>
- </data>
- <data name="MessageTokenUnknownError" xml:space="preserve">
- <value>A unknown error occured when verify token.</value>
- </data>
- <data name="MessageTokenUserNotExist" xml:space="preserve">
- <value>The owner of the token does not exist. It might have been deleted.</value>
- </data>
- <data name="MessageTokenVersionExpired" xml:space="preserve">
- <value>The token is of old version and expired. Please create a new one.</value>
+ <data name="MessageTokenExpired" xml:space="preserve">
+ <value>The token is expired. Please create a new one.</value>
</data>
</root> \ No newline at end of file
diff --git a/BackEnd/Timeline/Configs/JwtOptions.cs b/BackEnd/Timeline/Configs/JwtOptions.cs
deleted file mode 100644
index c400b8a6..00000000
--- a/BackEnd/Timeline/Configs/JwtOptions.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Timeline.Configs
-{
- public class JwtOptions
- {
- public string Issuer { get; set; } = default!;
- public string Audience { get; set; } = default!;
- }
-}
diff --git a/BackEnd/Timeline/Configs/TokenOptions.cs b/BackEnd/Timeline/Configs/TokenOptions.cs
index e7d4d9e7..d8e968c7 100644
--- a/BackEnd/Timeline/Configs/TokenOptions.cs
+++ b/BackEnd/Timeline/Configs/TokenOptions.cs
@@ -2,10 +2,11 @@
{
public class TokenOptions
{
- /// <summary>
- /// Set the default value of expire offset of jwt token.
- /// Unit is second. Default is 3600 * 24 seconds, aka 1 day.
+ /// <summary>
+ /// The length of the generated secure random token counted in byte.
+ /// Note the byte will be converted to hex form when used.
+ /// Default is 32 byte long.
/// </summary>
- public long DefaultExpireSeconds { get; set; } = 3600 * 24;
+ public long? TokenLength { get; set; }
}
}
diff --git a/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs b/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs
index 551a41e2..a1fa511c 100644
--- a/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs
+++ b/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs
@@ -44,7 +44,7 @@ namespace Timeline.Controllers
[ProducesResponseType(401)]
public async Task<ActionResult<List<HttpTimeline>>> List()
{
- var ids = await _service.GetBookmarksAsync(GetUserId());
+ var ids = await _service.GetBookmarksAsync(GetAuthUserId());
var timelines = await _timelineService.GetTimelineList(ids);
return await Map(timelines);
}
@@ -61,7 +61,7 @@ namespace Timeline.Controllers
public async Task<ActionResult<CommonPutResponse>> Put([GeneralTimelineName] string timeline)
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(timeline);
- var create = await _service.AddBookmarkAsync(GetUserId(), timelineId);
+ var create = await _service.AddBookmarkAsync(GetAuthUserId(), timelineId);
return CommonPutResponse.Create(create);
}
@@ -77,7 +77,7 @@ namespace Timeline.Controllers
public async Task<ActionResult<CommonDeleteResponse>> Delete([GeneralTimelineName] string timeline)
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(timeline);
- var delete = await _service.RemoveBookmarkAsync(GetUserId(), timelineId);
+ var delete = await _service.RemoveBookmarkAsync(GetAuthUserId(), timelineId);
return CommonDeleteResponse.Create(delete);
}
@@ -93,7 +93,7 @@ namespace Timeline.Controllers
public async Task<ActionResult> Move([FromBody] HttpBookmarkTimelineMoveRequest request)
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(request.Timeline);
- await _service.MoveBookmarkAsync(GetUserId(), timelineId, request.NewPosition!.Value);
+ await _service.MoveBookmarkAsync(GetAuthUserId(), timelineId, request.NewPosition!.Value);
return OkWithCommonResponse();
}
}
diff --git a/BackEnd/Timeline/Controllers/HighlightTimelineController.cs b/BackEnd/Timeline/Controllers/HighlightTimelineController.cs
index 127392db..e30cf720 100644
--- a/BackEnd/Timeline/Controllers/HighlightTimelineController.cs
+++ b/BackEnd/Timeline/Controllers/HighlightTimelineController.cs
@@ -61,7 +61,7 @@ namespace Timeline.Controllers
public async Task<ActionResult<CommonPutResponse>> Put([GeneralTimelineName] string timeline)
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(timeline);
- var create = await _service.AddHighlightTimelineAsync(timelineId, GetUserId());
+ var create = await _service.AddHighlightTimelineAsync(timelineId, GetAuthUserId());
return CommonPutResponse.Create(create);
}
@@ -78,7 +78,7 @@ namespace Timeline.Controllers
public async Task<ActionResult<CommonDeleteResponse>> Delete([GeneralTimelineName] string timeline)
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(timeline);
- var delete = await _service.RemoveHighlightTimelineAsync(timelineId, GetUserId());
+ var delete = await _service.RemoveHighlightTimelineAsync(timelineId, GetAuthUserId());
return CommonDeleteResponse.Create(delete);
}
diff --git a/BackEnd/Timeline/Controllers/MyControllerBase.cs b/BackEnd/Timeline/Controllers/MyControllerBase.cs
index d4ee9d3e..b74193f4 100644
--- a/BackEnd/Timeline/Controllers/MyControllerBase.cs
+++ b/BackEnd/Timeline/Controllers/MyControllerBase.cs
@@ -1,8 +1,11 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
using System;
+using System.Threading.Tasks;
using Timeline.Auth;
using Timeline.Models.Http;
+using Timeline.Services;
using Timeline.Services.User;
namespace Timeline.Controllers
@@ -15,24 +18,30 @@ namespace Timeline.Controllers
return User.HasPermission(permission);
}
- protected string? GetOptionalUsername()
+ protected long? GetOptionalAuthUserId()
{
- return User.GetOptionalName();
- }
-
- protected string GetUsername()
- {
- return GetOptionalUsername() ?? throw new InvalidOperationException(Resource.ExceptionNoUsername);
+ return User.GetOptionalUserId();
}
- protected long? GetOptionalUserId()
+ protected long GetAuthUserId()
{
- return User.GetOptionalUserId();
+ return GetOptionalAuthUserId() ?? throw new InvalidOperationException(Resource.ExceptionNoUserId);
}
- protected long GetUserId()
- {
- return GetOptionalUserId() ?? throw new InvalidOperationException(Resource.ExceptionNoUserId);
+ protected async Task<bool> CheckIsSelf(string username)
+ {
+ var authUserId = GetOptionalAuthUserId();
+ if (!authUserId.HasValue) return false;
+ try
+ {
+ var userService = HttpContext.RequestServices.GetRequiredService<IUserService>();
+ var id = await userService.GetUserIdByUsernameAsync(username);
+ return authUserId == id;
+ }
+ catch (EntityNotExistException)
+ {
+ return false;
+ }
}
#endregion auth
diff --git a/BackEnd/Timeline/Controllers/Resource.Designer.cs b/BackEnd/Timeline/Controllers/Resource.Designer.cs
index a647558a..eeb2f0fa 100644
--- a/BackEnd/Timeline/Controllers/Resource.Designer.cs
+++ b/BackEnd/Timeline/Controllers/Resource.Designer.cs
@@ -1,207 +1,186 @@
-//------------------------------------------------------------------------------
-// <auto-generated>
-// 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.
-// </auto-generated>
-//------------------------------------------------------------------------------
-
-namespace Timeline.Controllers {
- using System;
-
-
- /// <summary>
- /// A strongly-typed resource class, for looking up localized strings, etc.
- /// </summary>
- // 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() {
- }
-
- /// <summary>
- /// Returns the cached ResourceManager instance used by this class.
- /// </summary>
- [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.Controllers.Resource", typeof(Resource).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- /// <summary>
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- /// </summary>
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Can&apos;t get user id..
- /// </summary>
- internal static string ExceptionNoUserId {
- get {
- return ResourceManager.GetString("ExceptionNoUserId", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Can&apos;t get username..
- /// </summary>
- internal static string ExceptionNoUsername {
- get {
- return ResourceManager.GetString("ExceptionNoUsername", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to You have no permission to access this..
- /// </summary>
- internal static string MessageForbid {
- get {
- return ResourceManager.GetString("MessageForbid", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to You can&apos;t do this unless you are administrator..
- /// </summary>
- internal static string MessageForbidNotAdministrator {
- get {
- return ResourceManager.GetString("MessageForbidNotAdministrator", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to You can&apos;t do this unless you are administrator or resource owner..
- /// </summary>
- internal static string MessageForbidNotAdministratorOrOwner {
- get {
- return ResourceManager.GetString("MessageForbidNotAdministratorOrOwner", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to You can&apos;t do this because it is the root user..
- /// </summary>
- internal static string MessageInvalidOperationOnRootUser {
- get {
- return ResourceManager.GetString("MessageInvalidOperationOnRootUser", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The old password is wrong..
- /// </summary>
- internal static string MessageOldPasswordWrong {
- get {
- return ResourceManager.GetString("MessageOldPasswordWrong", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Operation succeeded..
- /// </summary>
- internal static string MessageOperationSucceeded {
- get {
- return ResourceManager.GetString("MessageOperationSucceeded", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The user specified by query param &quot;relate&quot; does not exist..
- /// </summary>
- internal static string MessageTimelineListQueryRelateNotExist {
- get {
- return ResourceManager.GetString("MessageTimelineListQueryRelateNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to &apos;{0}&apos; is an unkown visibility in the query parameter &apos;visibility&apos;. .
- /// </summary>
- internal static string MessageTimelineListQueryVisibilityUnknown {
- get {
- return ResourceManager.GetString("MessageTimelineListQueryVisibilityUnknown", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Username or password is invalid..
- /// </summary>
- internal static string MessageTokenCreateBadCredential {
- get {
- return ResourceManager.GetString("MessageTokenCreateBadCredential", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token is of bad format. It might not be created by the server..
- /// </summary>
- internal static string MessageTokenVerifyBadFormat {
- get {
- return ResourceManager.GetString("MessageTokenVerifyBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Token has an old version. User might have update some info..
- /// </summary>
- internal static string MessageTokenVerifyOldVersion {
- get {
- return ResourceManager.GetString("MessageTokenVerifyOldVersion", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token is expired..
- /// </summary>
- internal static string MessageTokenVerifyTimeExpired {
- get {
- return ResourceManager.GetString("MessageTokenVerifyTimeExpired", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to User does not exist. Administrator might have deleted this user..
- /// </summary>
- internal static string MessageTokenVerifyUserNotExist {
- get {
- return ResourceManager.GetString("MessageTokenVerifyUserNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A user with given username already exists..
- /// </summary>
- internal static string MessageUsernameConflict {
- get {
- return ResourceManager.GetString("MessageUsernameConflict", resourceCulture);
- }
- }
- }
-}
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Timeline.Controllers {
+ using System;
+
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ [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() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [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.Controllers.Resource", typeof(Resource).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Can&apos;t get user id..
+ /// </summary>
+ internal static string ExceptionNoUserId {
+ get {
+ return ResourceManager.GetString("ExceptionNoUserId", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Can&apos;t get username..
+ /// </summary>
+ internal static string ExceptionNoUsername {
+ get {
+ return ResourceManager.GetString("ExceptionNoUsername", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You have no permission to access this..
+ /// </summary>
+ internal static string MessageForbid {
+ get {
+ return ResourceManager.GetString("MessageForbid", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You can&apos;t do this unless you are administrator..
+ /// </summary>
+ internal static string MessageForbidNotAdministrator {
+ get {
+ return ResourceManager.GetString("MessageForbidNotAdministrator", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You can&apos;t do this unless you are administrator or resource owner..
+ /// </summary>
+ internal static string MessageForbidNotAdministratorOrOwner {
+ get {
+ return ResourceManager.GetString("MessageForbidNotAdministratorOrOwner", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You can&apos;t do this because it is the root user..
+ /// </summary>
+ internal static string MessageInvalidOperationOnRootUser {
+ get {
+ return ResourceManager.GetString("MessageInvalidOperationOnRootUser", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The old password is wrong..
+ /// </summary>
+ internal static string MessageOldPasswordWrong {
+ get {
+ return ResourceManager.GetString("MessageOldPasswordWrong", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Operation succeeded..
+ /// </summary>
+ internal static string MessageOperationSucceeded {
+ get {
+ return ResourceManager.GetString("MessageOperationSucceeded", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The user specified by query param &quot;relate&quot; does not exist..
+ /// </summary>
+ internal static string MessageTimelineListQueryRelateNotExist {
+ get {
+ return ResourceManager.GetString("MessageTimelineListQueryRelateNotExist", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to &apos;{0}&apos; is an unkown visibility in the query parameter &apos;visibility&apos;. .
+ /// </summary>
+ internal static string MessageTimelineListQueryVisibilityUnknown {
+ get {
+ return ResourceManager.GetString("MessageTimelineListQueryVisibilityUnknown", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Username or password is invalid..
+ /// </summary>
+ internal static string MessageTokenCreateBadCredential {
+ get {
+ return ResourceManager.GetString("MessageTokenCreateBadCredential", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The token is expired. Please create a new one..
+ /// </summary>
+ internal static string MessageTokenVerifyExpired {
+ get {
+ return ResourceManager.GetString("MessageTokenVerifyExpired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The token is invalid..
+ /// </summary>
+ internal static string MessageTokenVerifyInvalid {
+ get {
+ return ResourceManager.GetString("MessageTokenVerifyInvalid", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A user with given username already exists..
+ /// </summary>
+ internal static string MessageUsernameConflict {
+ get {
+ return ResourceManager.GetString("MessageUsernameConflict", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Controllers/Resource.resx b/BackEnd/Timeline/Controllers/Resource.resx
index 47b7a329..b70e5230 100644
--- a/BackEnd/Timeline/Controllers/Resource.resx
+++ b/BackEnd/Timeline/Controllers/Resource.resx
@@ -150,17 +150,11 @@
<data name="MessageTokenCreateBadCredential" xml:space="preserve">
<value>Username or password is invalid.</value>
</data>
- <data name="MessageTokenVerifyBadFormat" xml:space="preserve">
- <value>The token is of bad format. It might not be created by the server.</value>
+ <data name="MessageTokenVerifyInvalid" xml:space="preserve">
+ <value>The token is invalid.</value>
</data>
- <data name="MessageTokenVerifyOldVersion" xml:space="preserve">
- <value>Token has an old version. User might have update some info.</value>
- </data>
- <data name="MessageTokenVerifyTimeExpired" xml:space="preserve">
- <value>The token is expired.</value>
- </data>
- <data name="MessageTokenVerifyUserNotExist" xml:space="preserve">
- <value>User does not exist. Administrator might have deleted this user.</value>
+ <data name="MessageTokenVerifyExpired" xml:space="preserve">
+ <value>The token is expired. Please create a new one.</value>
</data>
<data name="MessageUsernameConflict" xml:space="preserve">
<value>A user with given username already exists.</value>
diff --git a/BackEnd/Timeline/Controllers/TimelineController.cs b/BackEnd/Timeline/Controllers/TimelineController.cs
index f98ff3e0..42b8f210 100644
--- a/BackEnd/Timeline/Controllers/TimelineController.cs
+++ b/BackEnd/Timeline/Controllers/TimelineController.cs
@@ -142,7 +142,7 @@ namespace Timeline.Controllers
{
var timelineId = await _service.GetTimelineIdByNameAsync(timeline);
- if (!UserHasAllTimelineManagementPermission && !await _service.HasManagePermissionAsync(timelineId, GetUserId()))
+ if (!UserHasAllTimelineManagementPermission && !await _service.HasManagePermissionAsync(timelineId, GetAuthUserId()))
{
return ForbidWithCommonResponse();
}
@@ -168,7 +168,7 @@ namespace Timeline.Controllers
{
var timelineId = await _service.GetTimelineIdByNameAsync(timeline);
- if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermissionAsync(timelineId, GetUserId())))
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermissionAsync(timelineId, GetAuthUserId())))
{
return ForbidWithCommonResponse();
}
@@ -194,7 +194,7 @@ namespace Timeline.Controllers
{
var timelineId = await _service.GetTimelineIdByNameAsync(timeline);
- if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermissionAsync(timelineId, GetUserId())))
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermissionAsync(timelineId, GetAuthUserId())))
{
return ForbidWithCommonResponse();
}
@@ -216,7 +216,7 @@ namespace Timeline.Controllers
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<HttpTimeline>> TimelineCreate([FromBody] HttpTimelineCreateRequest body)
{
- var userId = GetUserId();
+ var userId = GetAuthUserId();
var timeline = await _service.CreateTimelineAsync(body.Name, userId);
var result = await Map(timeline);
@@ -240,7 +240,7 @@ namespace Timeline.Controllers
{
var timelineId = await _service.GetTimelineIdByNameAsync(timeline);
- if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermissionAsync(timelineId, GetUserId())))
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermissionAsync(timelineId, GetAuthUserId())))
{
return ForbidWithCommonResponse();
}
diff --git a/BackEnd/Timeline/Controllers/TimelinePostController.cs b/BackEnd/Timeline/Controllers/TimelinePostController.cs
index f00a689c..c49c95fc 100644
--- a/BackEnd/Timeline/Controllers/TimelinePostController.cs
+++ b/BackEnd/Timeline/Controllers/TimelinePostController.cs
@@ -77,7 +77,7 @@ namespace Timeline.Controllers
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(timeline);
- if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalUserId()))
+ if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalAuthUserId()))
{
return ForbidWithCommonResponse();
}
@@ -102,7 +102,7 @@ namespace Timeline.Controllers
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(timeline);
- if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalUserId()))
+ if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalAuthUserId()))
{
return ForbidWithCommonResponse();
}
@@ -148,7 +148,7 @@ namespace Timeline.Controllers
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(timeline);
- if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalUserId()))
+ if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalAuthUserId()))
{
return ForbidWithCommonResponse();
}
@@ -182,7 +182,7 @@ namespace Timeline.Controllers
public async Task<ActionResult<HttpTimelinePost>> Post([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePostCreateRequest body)
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(timeline);
- var userId = GetUserId();
+ var userId = GetAuthUserId();
if (!UserHasAllTimelineManagementPermission && !await _timelineService.IsMemberOfAsync(timelineId, userId))
{
@@ -247,7 +247,7 @@ namespace Timeline.Controllers
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(timeline);
- if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermissionAsync(timelineId, post, GetUserId(), true))
+ if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermissionAsync(timelineId, post, GetAuthUserId(), true))
{
return ForbidWithCommonResponse();
}
@@ -274,7 +274,7 @@ namespace Timeline.Controllers
{
var timelineId = await _timelineService.GetTimelineIdByNameAsync(timeline);
- if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermissionAsync(timelineId, post, GetUserId(), true))
+ if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermissionAsync(timelineId, post, GetAuthUserId(), true))
{
return ForbidWithCommonResponse();
}
diff --git a/BackEnd/Timeline/Controllers/TokenController.cs b/BackEnd/Timeline/Controllers/TokenController.cs
index ae3e1b94..9ee5a09f 100644
--- a/BackEnd/Timeline/Controllers/TokenController.cs
+++ b/BackEnd/Timeline/Controllers/TokenController.cs
@@ -19,13 +19,15 @@ namespace Timeline.Controllers
[ProducesErrorResponseType(typeof(CommonResponse))]
public class TokenController : MyControllerBase
{
- private readonly IUserTokenManager _userTokenManager;
+ private readonly IUserService _userService;
+ private readonly IUserTokenService _userTokenService;
private readonly IGenericMapper _mapper;
private readonly IClock _clock;
- public TokenController(IUserTokenManager userTokenManager, IGenericMapper mapper, IClock clock)
+ public TokenController(IUserService userService, IUserTokenService userTokenService, IGenericMapper mapper, IClock clock)
{
- _userTokenManager = userTokenManager;
+ _userService = userService;
+ _userTokenService = userTokenService;
_mapper = mapper;
_clock = clock;
}
@@ -47,12 +49,14 @@ namespace Timeline.Controllers
if (request.Expire is not null)
expireTime = _clock.GetCurrentTime().AddDays(request.Expire.Value);
- var result = await _userTokenManager.CreateTokenAsync(request.Username, request.Password, expireTime);
+ var userId = await _userService.VerifyCredential(request.Username, request.Password);
+ var token = await _userTokenService.CreateTokenAsync(userId, expireTime);
+ var user = await _userService.GetUserAsync(userId);
return new HttpCreateTokenResponse
{
- Token = result.Token,
- User = await _mapper.MapAsync<HttpUser>(result.User, Url, User)
+ Token = token,
+ User = await _mapper.MapAsync<HttpUser>(user, Url, User)
};
}
catch (EntityNotExistException)
@@ -77,27 +81,20 @@ namespace Timeline.Controllers
{
try
{
- var result = await _userTokenManager.VerifyTokenAsync(request.Token);
+ var tokenInfo = await _userTokenService.ValidateTokenAsync(request.Token);
+ var user = await _userService.GetUserAsync(tokenInfo.UserId);
return new HttpVerifyTokenResponse
{
- User = await _mapper.MapAsync<HttpUser>(result, Url, User)
+ User = await _mapper.MapAsync<HttpUser>(user, Url, User)
};
}
- catch (UserTokenTimeExpiredException)
+ catch (UserTokenExpiredException)
{
- return BadRequestWithCommonResponse(ErrorCodes.TokenController.VerifyTimeExpired, Resource.MessageTokenVerifyTimeExpired);
+ return BadRequestWithCommonResponse(ErrorCodes.TokenController.VerifyExpired, Resource.MessageTokenVerifyExpired);
}
- catch (UserTokenVersionExpiredException)
+ catch (UserTokenException)
{
- return BadRequestWithCommonResponse(ErrorCodes.TokenController.VerifyOldVersion, Resource.MessageTokenVerifyOldVersion);
- }
- catch (UserTokenBadFormatException)
- {
- return BadRequestWithCommonResponse(ErrorCodes.TokenController.VerifyBadFormat, Resource.MessageTokenVerifyBadFormat);
- }
- catch (UserTokenUserNotExistException)
- {
- return BadRequestWithCommonResponse(ErrorCodes.TokenController.VerifyUserNotExist, Resource.MessageTokenVerifyUserNotExist);
+ return BadRequestWithCommonResponse(ErrorCodes.TokenController.VerifyInvalid, Resource.MessageTokenVerifyInvalid);
}
}
}
diff --git a/BackEnd/Timeline/Controllers/UserAvatarController.cs b/BackEnd/Timeline/Controllers/UserAvatarController.cs
index 5b8c5cdf..072ab621 100644
--- a/BackEnd/Timeline/Controllers/UserAvatarController.cs
+++ b/BackEnd/Timeline/Controllers/UserAvatarController.cs
@@ -61,7 +61,7 @@ namespace Timeline.Controllers
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Put([FromRoute][Username] string username, [FromBody] ByteData body)
{
- if (!UserHasPermission(UserPermission.UserManagement) && GetUsername() != username)
+ if (!UserHasPermission(UserPermission.UserManagement) && !await CheckIsSelf(username))
{
return ForbidWithCommonResponse(Resource.MessageForbidNotAdministratorOrOwner);
}
@@ -91,7 +91,7 @@ namespace Timeline.Controllers
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Delete([FromRoute][Username] string username)
{
- if (!UserHasPermission(UserPermission.UserManagement) && User.Identity!.Name != username)
+ if (!UserHasPermission(UserPermission.UserManagement) && !await CheckIsSelf(username))
{
return ForbidWithCommonResponse(Resource.MessageForbidNotAdministratorOrOwner);
}
diff --git a/BackEnd/Timeline/Controllers/UserController.cs b/BackEnd/Timeline/Controllers/UserController.cs
index 740bd0ed..95a99a03 100644
--- a/BackEnd/Timeline/Controllers/UserController.cs
+++ b/BackEnd/Timeline/Controllers/UserController.cs
@@ -7,6 +7,7 @@ using Timeline.Auth;
using Timeline.Filters;
using Timeline.Models.Http;
using Timeline.Models.Validation;
+using Timeline.Services;
using Timeline.Services.Mapper;
using Timeline.Services.User;
@@ -103,7 +104,7 @@ namespace Timeline.Controllers
}
else
{
- if (GetUsername() != username)
+ if (!await CheckIsSelf(username))
return ForbidWithCommonResponse(Resource.MessageForbidNotAdministratorOrOwner);
if (body.Username is not null)
@@ -112,7 +113,7 @@ namespace Timeline.Controllers
if (body.Password is not null)
return ForbidWithCommonResponse(Resource.MessageForbidNotAdministrator);
- var user = await _userService.ModifyUserAsync(GetUserId(), _mapper.AutoMapperMap<ModifyUserParams>(body));
+ var user = await _userService.ModifyUserAsync(GetAuthUserId(), _mapper.AutoMapperMap<ModifyUserParams>(body));
return await _mapper.MapAsync<HttpUser>(user, Url, User);
}
}
@@ -152,7 +153,7 @@ namespace Timeline.Controllers
{
try
{
- await _userService.ChangePassword(GetUserId(), request.OldPassword, request.NewPassword);
+ await _userService.ChangePassword(GetAuthUserId(), request.OldPassword, request.NewPassword);
return OkWithCommonResponse();
}
catch (BadPasswordException)
diff --git a/BackEnd/Timeline/Entities/UserTokenEntity.cs b/BackEnd/Timeline/Entities/UserTokenEntity.cs
index 0d8bce7d..d5bae7f9 100644
--- a/BackEnd/Timeline/Entities/UserTokenEntity.cs
+++ b/BackEnd/Timeline/Entities/UserTokenEntity.cs
@@ -24,5 +24,8 @@ namespace Timeline.Entities
[Column("create_at")]
public DateTime? CreateAt { get; set; }
+
+ [Column("deleted")]
+ public bool Deleted { get; set; }
}
}
diff --git a/BackEnd/Timeline/ErrorCodes.cs b/BackEnd/Timeline/ErrorCodes.cs
index be532c0c..9201979f 100644
--- a/BackEnd/Timeline/ErrorCodes.cs
+++ b/BackEnd/Timeline/ErrorCodes.cs
@@ -29,10 +29,7 @@
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 const int Invalid = 1_000_21_02;
}
}
@@ -61,10 +58,8 @@
public static class TokenController
{
public const int CreateBadCredential = 1_101_01_01;
- public const int VerifyBadFormat = 1_101_02_01;
- public const int VerifyUserNotExist = 1_101_02_02;
- public const int VerifyOldVersion = 1_101_02_03;
- public const int VerifyTimeExpired = 1_101_02_04;
+ public const int VerifyInvalid = 1_101_02_02;
+ public const int VerifyExpired = 1_101_02_03;
}
public static class UserController
diff --git a/BackEnd/Timeline/Migrations/20220323073853_AddDeletedToToken.Designer.cs b/BackEnd/Timeline/Migrations/20220323073853_AddDeletedToToken.Designer.cs
new file mode 100644
index 00000000..89f2454c
--- /dev/null
+++ b/BackEnd/Timeline/Migrations/20220323073853_AddDeletedToToken.Designer.cs
@@ -0,0 +1,628 @@
+// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Timeline.Entities;
+
+#nullable disable
+
+namespace Timeline.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20220323073853_AddDeletedToToken")]
+ partial class AddDeletedToToken
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.3");
+
+ modelBuilder.Entity("Timeline.Entities.BookmarkTimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long>("Rank")
+ .HasColumnType("INTEGER")
+ .HasColumnName("rank");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("bookmark_timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.DataEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("data");
+
+ b.Property<int>("Ref")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ref");
+
+ b.Property<string>("Tag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("tag");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tag")
+ .IsUnique();
+
+ b.ToTable("data");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("AddTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("add_time");
+
+ b.Property<long?>("OperatorId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("operator_id");
+
+ b.Property<long>("Order")
+ .HasColumnType("INTEGER")
+ .HasColumnName("order");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OperatorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("highlight_timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<byte[]>("Key")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("key");
+
+ b.HasKey("Id");
+
+ b.ToTable("jwt_token");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.MigrationEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("migrations");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("Color")
+ .HasColumnType("TEXT")
+ .HasColumnName("color");
+
+ b.Property<DateTime>("CreateTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time");
+
+ b.Property<long>("CurrentPostLocalId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("current_post_local_id");
+
+ b.Property<string>("Description")
+ .HasColumnType("TEXT")
+ .HasColumnName("description");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
+
+ b.Property<string>("Name")
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.Property<DateTime>("NameLastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("name_last_modified");
+
+ b.Property<long>("OwnerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("owner");
+
+ b.Property<string>("Title")
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("unique_id")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<int>("Visibility")
+ .HasColumnType("INTEGER")
+ .HasColumnName("visibility");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerId");
+
+ b.ToTable("timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("timeline_members");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostDataEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("DataTag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("data_tag");
+
+ b.Property<long>("Index")
+ .HasColumnType("INTEGER")
+ .HasColumnName("index");
+
+ b.Property<string>("Kind")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("kind");
+
+ b.Property<DateTime>("LastUpdated")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_updated");
+
+ b.Property<long>("PostId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("post");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PostId");
+
+ b.ToTable("timeline_post_data");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long?>("AuthorId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("author");
+
+ b.Property<string>("Color")
+ .HasColumnType("TEXT")
+ .HasColumnName("color");
+
+ b.Property<string>("Content")
+ .HasColumnType("TEXT")
+ .HasColumnName("content");
+
+ b.Property<string>("ContentType")
+ .HasColumnType("TEXT")
+ .HasColumnName("content_type");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<string>("ExtraContent")
+ .HasColumnType("TEXT")
+ .HasColumnName("extra_content");
+
+ b.Property<DateTime>("LastUpdated")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_updated");
+
+ b.Property<long>("LocalId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("local_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("timeline_posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("DataTag")
+ .HasColumnType("TEXT")
+ .HasColumnName("data_tag");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
+
+ b.Property<string>("Type")
+ .HasColumnType("TEXT")
+ .HasColumnName("type");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("user_avatars");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("CreateTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<DateTime>("LastModified")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<string>("Nickname")
+ .HasColumnType("TEXT")
+ .HasColumnName("nickname");
+
+ b.Property<string>("Password")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("password");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("unique_id")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("username");
+
+ b.Property<DateTime>("UsernameChangeTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("username_change_time")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<long>("Version")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0L)
+ .HasColumnName("version");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Username")
+ .IsUnique();
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("Permission")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("permission");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_permission");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserTokenEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime?>("CreateAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("create_at");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("ExpireAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("expire_at");
+
+ b.Property<string>("Token")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("token");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Token")
+ .IsUnique();
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_token");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.BookmarkTimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany()
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Timeline");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Operator")
+ .WithMany()
+ .HasForeignKey("OperatorId");
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany()
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Operator");
+
+ b.Navigation("Timeline");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Owner")
+ .WithMany("Timelines")
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Owner");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Members")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("TimelinesJoined")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Timeline");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostDataEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelinePostEntity", "Post")
+ .WithMany("DataList")
+ .HasForeignKey("PostId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Post");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Author")
+ .WithMany("TimelinePosts")
+ .HasForeignKey("AuthorId");
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Posts")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Author");
+
+ b.Navigation("Timeline");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithOne("Avatar")
+ .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("Permissions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserTokenEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Navigation("Members");
+
+ b.Navigation("Posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.Navigation("DataList");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Navigation("Avatar");
+
+ b.Navigation("Permissions");
+
+ b.Navigation("TimelinePosts");
+
+ b.Navigation("Timelines");
+
+ b.Navigation("TimelinesJoined");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Migrations/20220323073853_AddDeletedToToken.cs b/BackEnd/Timeline/Migrations/20220323073853_AddDeletedToToken.cs
new file mode 100644
index 00000000..f16d3522
--- /dev/null
+++ b/BackEnd/Timeline/Migrations/20220323073853_AddDeletedToToken.cs
@@ -0,0 +1,26 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Timeline.Migrations
+{
+ public partial class AddDeletedToToken : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn<bool>(
+ name: "deleted",
+ table: "user_token",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: false);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "deleted",
+ table: "user_token");
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs
index f1e9b6ab..f510d983 100644
--- a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs
+++ b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs
@@ -442,6 +442,10 @@ namespace Timeline.Migrations
.HasColumnType("TEXT")
.HasColumnName("create_at");
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
b.Property<DateTime?>("ExpireAt")
.HasColumnType("TEXT")
.HasColumnName("expire_at");
diff --git a/BackEnd/Timeline/Services/Token/DatabaseUserTokenHandler.cs b/BackEnd/Timeline/Services/Token/DatabaseUserTokenHandler.cs
deleted file mode 100644
index e69de29b..00000000
--- a/BackEnd/Timeline/Services/Token/DatabaseUserTokenHandler.cs
+++ /dev/null
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
- {
- /// <summary>
- /// Create a token for a given token info.
- /// </summary>
- /// <param name="tokenInfo">The info to generate token.</param>
- /// <returns>Return the generated token.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="tokenInfo"/> is null.</exception>
- Task<string> GenerateTokenAsync(UserTokenInfo tokenInfo);
-
- /// <summary>
- /// Verify a token and get the saved info. Do not validate lifetime!!!
- /// </summary>
- /// <param name="token">The token to verify.</param>
- /// <returns>The saved info in token.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
- /// <exception cref="UserTokenBadFormatException">Thrown when the token is of bad format.</exception>
- /// <remarks>
- /// If this method throw <see cref="UserTokenBadFormatException"/>, 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.
- /// </remarks>
- Task<UserTokenInfo> 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
- {
- /// <summary>
- /// Try to create a token for given username and password.
- /// </summary>
- /// <param name="username">The username.</param>
- /// <param name="password">The password.</param>
- /// <param name="expireAt">The expire time of the token.</param>
- /// <returns>The created token and the user info.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
- /// <exception cref="EntityNotExistException">Thrown when the user with <paramref name="username"/> does not exist.</exception>
- /// <exception cref="BadPasswordException">Thrown when <paramref name="password"/> is wrong.</exception>
- public Task<UserTokenCreateResult> CreateTokenAsync(string username, string password, DateTime? expireAt = null);
-
- /// <summary>
- /// Verify a token and get the saved user info. This also check the database for existence of the user.
- /// </summary>
- /// <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="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="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> 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
+ {
+ /// <summary>
+ /// Create a token for a user. Please ensure the user id exists!
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <param name="expireTime">The expire time of the token.</param>
+ /// <returns>Return the generated token.</returns>
+ Task<string> CreateTokenAsync(long userId, DateTime? expireTime);
+
+ /// <summary>
+ /// Verify a token and get the info of the token.
+ /// </summary>
+ /// <param name="token">The token to verify.</param>
+ /// <returns>The info of the token.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
+ /// <exception cref="UserTokenException">Thrown when the token is not valid for reasons other than expired.</exception>
+ /// <exception cref="UserTokenExpiredException">Thrown when the token is expired.</exception>
+ Task<UserTokenInfo> ValidateTokenAsync(string token);
+
+ /// <summary>
+ /// Revoke a token to make it no longer valid.
+ /// </summary>
+ /// <param name="token">The token to revoke.</param>
+ /// <returns>Return true if a token is revoked.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ Task<bool> RevokeTokenAsync(string token);
+
+ /// <summary>
+ /// Revoke all tokens of a user.
+ /// </summary>
+ /// <param name="userId">User id of tokens.</param>
+ /// <returns>Return the task.</returns>
+ 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 @@
-//------------------------------------------------------------------------------
-// <auto-generated>
-// 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.
-// </auto-generated>
-//------------------------------------------------------------------------------
-
-namespace Timeline.Services.Token {
- using System;
-
-
- /// <summary>
- /// A strongly-typed resource class, for looking up localized strings, etc.
- /// </summary>
- // 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() {
- }
-
- /// <summary>
- /// Returns the cached ResourceManager instance used by this class.
- /// </summary>
- [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;
- }
- }
-
- /// <summary>
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- /// </summary>
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Jwt key is not found. Maybe you forget to do the migration..
- /// </summary>
- internal static string ExceptionJwtKeyNotExist {
- get {
- return ResourceManager.GetString("ExceptionJwtKeyNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token didn&apos;t pass verification because {0}..
- /// </summary>
- internal static string ExceptionJwtUserTokenBadFormat {
- get {
- return ResourceManager.GetString("ExceptionJwtUserTokenBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to id claim is not a number.
- /// </summary>
- internal static string ExceptionJwtUserTokenBadFormatReasonIdBadFormat {
- get {
- return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonIdBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to id claim does not exist.
- /// </summary>
- internal static string ExceptionJwtUserTokenBadFormatReasonIdMissing {
- get {
- return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonIdMissing", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to other error, see inner exception for information.
- /// </summary>
- internal static string ExceptionJwtUserTokenBadFormatReasonOthers {
- get {
- return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonOthers", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to unknown error.
- /// </summary>
- internal static string ExceptionJwtUserTokenBadFormatReasonUnknown {
- get {
- return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonUnknown", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to version claim is not a number..
- /// </summary>
- internal static string ExceptionJwtUserTokenBadFormatReasonVersionBadFormat {
- get {
- return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonVersionBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to version claim does not exist..
- /// </summary>
- internal static string ExceptionJwtUserTokenBadFormatReasonVersionMissing {
- get {
- return ResourceManager.GetString("ExceptionJwtUserTokenBadFormatReasonVersionMissing", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token is of bad format, which means it may not be created by the server..
- /// </summary>
- internal static string ExceptionUserTokenBadFormat {
- get {
- return ResourceManager.GetString("ExceptionUserTokenBadFormat", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token is expired because its expiration time has passed..
- /// </summary>
- internal static string ExceptionUserTokenTimeExpired {
- get {
- return ResourceManager.GetString("ExceptionUserTokenTimeExpired", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The owner user of the token does not exist..
- /// </summary>
- internal static string ExceptionUserTokenUserNotExist {
- get {
- return ResourceManager.GetString("ExceptionUserTokenUserNotExist", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to The token is of bad version..
- /// </summary>
- internal static string ExceptionUserTokenVersionExpired {
- get {
- return ResourceManager.GetString("ExceptionUserTokenVersionExpired", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A token is created for user with username={0}, id={1}..
- /// </summary>
- internal static string LogTokenCreate {
- get {
- return ResourceManager.GetString("LogTokenCreate", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A token of user with username = {0}, id = {1} is verified successfully..
- /// </summary>
- internal static string LogTokenVerified {
- get {
- return ResourceManager.GetString("LogTokenVerified", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to A token fails to be verified..
- /// </summary>
- internal static string LogTokenVerifiedFail {
- get {
- return ResourceManager.GetString("LogTokenVerifiedFail", resourceCulture);
- }
- }
- }
-}
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Timeline.Services.Token {
+ using System;
+
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ [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() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [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;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The token is expired because its expiration time has passed..
+ /// </summary>
+ internal static string ExceptionUserTokenExpired {
+ get {
+ return ResourceManager.GetString("ExceptionUserTokenExpired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The token is invalid..
+ /// </summary>
+ 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 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
- <!--
+ <!--
Microsoft ResX Schema
Version 2.0
@@ -59,107 +59,68 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
+ <xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string"/>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="mimetype" type="xsd:string"/>
+ <xsd:attribute ref="xml:space"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string"/>
+ <xsd:attribute name="name" type="xsd:string"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
+ <xsd:attribute ref="xml:space"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
</xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="ExceptionJwtKeyNotExist" xml:space="preserve">
- <value>Jwt key is not found. Maybe you forget to do the migration.</value>
- </data>
- <data name="ExceptionJwtUserTokenBadFormat" xml:space="preserve">
- <value>The token didn't pass verification because {0}.</value>
- </data>
- <data name="ExceptionJwtUserTokenBadFormatReasonIdBadFormat" xml:space="preserve">
- <value>id claim is not a number</value>
- </data>
- <data name="ExceptionJwtUserTokenBadFormatReasonIdMissing" xml:space="preserve">
- <value>id claim does not exist</value>
- </data>
- <data name="ExceptionJwtUserTokenBadFormatReasonOthers" xml:space="preserve">
- <value>other error, see inner exception for information</value>
- </data>
- <data name="ExceptionJwtUserTokenBadFormatReasonUnknown" xml:space="preserve">
- <value>unknown error</value>
- </data>
- <data name="ExceptionJwtUserTokenBadFormatReasonVersionBadFormat" xml:space="preserve">
- <value>version claim is not a number.</value>
- </data>
- <data name="ExceptionJwtUserTokenBadFormatReasonVersionMissing" xml:space="preserve">
- <value>version claim does not exist.</value>
- </data>
- <data name="ExceptionUserTokenBadFormat" xml:space="preserve">
- <value>The token is of bad format, which means it may not be created by the server.</value>
- </data>
- <data name="ExceptionUserTokenTimeExpired" xml:space="preserve">
- <value>The token is expired because its expiration time has passed.</value>
- </data>
- <data name="ExceptionUserTokenUserNotExist" xml:space="preserve">
- <value>The owner user of the token does not exist.</value>
- </data>
- <data name="ExceptionUserTokenVersionExpired" xml:space="preserve">
- <value>The token is of bad version.</value>
- </data>
- <data name="LogTokenCreate" xml:space="preserve">
- <value>A token is created for user with username={0}, id={1}.</value>
- </data>
- <data name="LogTokenVerified" xml:space="preserve">
- <value>A token of user with username = {0}, id = {1} is verified successfully.</value>
- </data>
- <data name="LogTokenVerifiedFail" xml:space="preserve">
- <value>A token fails to be verified.</value>
- </data>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="ExceptionUserTokenInvalid" xml:space="preserve">
+ <value>The token is invalid.</value>
+ </data>
+ <data name="ExceptionUserTokenExpired" xml:space="preserve">
+ <value>The token is expired because its expiration time has passed.</value>
+ </data>
</root> \ 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<SecureRandomUserTokenService> _logger;
+ private RandomNumberGenerator _secureRandom;
+ private IOptionsMonitor<TokenOptions> _optionMonitor;
+ private IClock _clock;
+
+ public SecureRandomUserTokenService(DatabaseContext databaseContext, ILogger<SecureRandomUserTokenService> logger, IOptionsMonitor<TokenOptions> 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);
+ }
+
+ /// <inheritdoc/>
+ public async Task<string> 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;
+ }
+
+ /// <inheritdoc/>
+ public async Task<UserTokenInfo> 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
+ };
+ }
+
+ /// <inheritdoc/>
+ public async Task<bool> 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;
+ }
+
+ /// <inheritdoc/>
+ public async Task RevokeAllTokenByUserIdAsync(long userId)
+ {
+ List<UserTokenEntity> 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<TokenOptions>(configuration.GetSection("Token"));
- services.Configure<JwtOptions>(configuration.GetSection("Jwt"));
- services.AddScoped<IUserTokenHandler, JwtUserTokenHandler>();
- services.AddScoped<IUserTokenManager, UserTokenManager>();
+ services.AddScoped<IUserTokenService, SecureRandomUserTokenService>();
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<JwtOptions> _jwtConfig;
- private readonly IClock _clock;
-
- private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler();
- private SymmetricSecurityKey _tokenSecurityKey;
-
- public JwtUserTokenHandler(IOptionsMonitor<JwtOptions> 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<string> 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<UserTokenInfo> 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<UserTokenManager> _logger;
- private readonly IOptionsMonitor<TokenOptions> _tokenOptionsMonitor;
- private readonly IUserService _userService;
- private readonly IUserTokenHandler _userTokenService;
- private readonly IClock _clock;
-
- public UserTokenManager(ILogger<UserTokenManager> logger, IOptionsMonitor<TokenOptions> tokenOptionsMonitor, IUserService userService, IUserTokenHandler userTokenService, IClock clock)
- {
- _logger = logger;
- _tokenOptionsMonitor = tokenOptionsMonitor;
- _userService = userService;
- _userTokenService = userTokenService;
- _clock = clock;
- }
-
- public async Task<UserTokenCreateResult> 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<UserEntity> 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; }
- }
-}
diff --git a/BackEnd/Timeline/Services/User/CreateTokenResult.cs b/BackEnd/Timeline/Services/User/CreateTokenResult.cs
new file mode 100644
index 00000000..b71a9e9e
--- /dev/null
+++ b/BackEnd/Timeline/Services/User/CreateTokenResult.cs
@@ -0,0 +1,12 @@
+using System;
+using Timeline.Entities;
+
+namespace Timeline.Services.User
+{
+ public class CreateTokenResult
+ {
+ public string Token { get; set; } = default!;
+ public UserEntity User { get; set; } = default!;
+ }
+}
+
diff --git a/BackEnd/Timeline/Services/User/UserService.cs b/BackEnd/Timeline/Services/User/UserService.cs
index a47bc860..1ad74bec 100644
--- a/BackEnd/Timeline/Services/User/UserService.cs
+++ b/BackEnd/Timeline/Services/User/UserService.cs
@@ -7,7 +7,8 @@ using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Models.Validation;
-
+using Timeline.Services.Token;
+
namespace Timeline.Services.User
{
public class UserService : BasicUserService, IUserService
@@ -19,14 +20,17 @@ namespace Timeline.Services.User
private readonly IPasswordService _passwordService;
+ private readonly IUserTokenService _userTokenService;
+
private readonly UsernameValidator _usernameValidator = new UsernameValidator();
private readonly NicknameValidator _nicknameValidator = new NicknameValidator();
- public UserService(ILogger<UserService> logger, DatabaseContext databaseContext, IPasswordService passwordService, IClock clock) : base(databaseContext)
+ public UserService(ILogger<UserService> logger, DatabaseContext databaseContext, IPasswordService passwordService, IUserTokenService userTokenService, IClock clock) : base(databaseContext)
{
_logger = logger;
_databaseContext = databaseContext;
_passwordService = passwordService;
+ _userTokenService = userTokenService;
_clock = clock;
}
@@ -162,6 +166,11 @@ namespace Timeline.Services.User
await _databaseContext.SaveChangesAsync();
_logger.LogInformation(Resource.LogUserModified, entity.Username, id);
+
+ if (password is not null)
+ {
+ await _userTokenService.RevokeAllTokenByUserIdAsync(id);
+ }
}
return entity;
@@ -214,6 +223,8 @@ namespace Timeline.Services.User
entity.Version += 1;
await _databaseContext.SaveChangesAsync();
_logger.LogInformation(Resource.LogChangePassowrd, entity.Username, id);
+
+ await _userTokenService.RevokeAllTokenByUserIdAsync(id);
}
}
}
diff --git a/BackEnd/Timeline/Timeline.csproj b/BackEnd/Timeline/Timeline.csproj
index 95376b9c..48cd7f10 100644
--- a/BackEnd/Timeline/Timeline.csproj
+++ b/BackEnd/Timeline/Timeline.csproj
@@ -18,6 +18,9 @@
<NoWarn>1591</NoWarn>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(RunConfiguration)' == 'Dev' " />
+ <PropertyGroup Condition=" '$(RunConfiguration)' == 'Staging' " />
+ <PropertyGroup Condition=" '$(RunConfiguration)' == 'Dev-Mock' " />
<ItemGroup>
<Content Include="default-avatar.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
diff --git a/BackEnd/Timeline/appsettings.json b/BackEnd/Timeline/appsettings.json
index 81c01bf6..0804371b 100644
--- a/BackEnd/Timeline/appsettings.json
+++ b/BackEnd/Timeline/appsettings.json
@@ -3,9 +3,5 @@
"LogLevel": {
"Default": "Warning"
}
- },
- "Jwt": {
- "Issuer": "crupest.space",
- "Audience": "crupest.space"
}
}