diff options
Diffstat (limited to 'Timeline/Services')
-rw-r--r-- | Timeline/Services/BadPasswordException.cs | 27 | ||||
-rw-r--r-- | Timeline/Services/JwtBadVersionException.cs | 36 | ||||
-rw-r--r-- | Timeline/Services/JwtService.cs | 76 | ||||
-rw-r--r-- | Timeline/Services/JwtVerifyException.cs | 59 | ||||
-rw-r--r-- | Timeline/Services/UserDetailService.cs | 135 | ||||
-rw-r--r-- | Timeline/Services/UserNotExistException.cs | 41 | ||||
-rw-r--r-- | Timeline/Services/UserService.cs | 195 | ||||
-rw-r--r-- | Timeline/Services/UsernameBadFormatException.cs | 27 | ||||
-rw-r--r-- | Timeline/Services/UsernameConfictException.cs | 25 |
9 files changed, 247 insertions, 374 deletions
diff --git a/Timeline/Services/BadPasswordException.cs b/Timeline/Services/BadPasswordException.cs new file mode 100644 index 00000000..ee8a42db --- /dev/null +++ b/Timeline/Services/BadPasswordException.cs @@ -0,0 +1,27 @@ +using System;
+using Timeline.Helpers;
+
+namespace Timeline.Services
+{
+ [Serializable]
+ public class BadPasswordException : Exception
+ {
+ public BadPasswordException() : base(Resources.Services.Exception.UserNotExistException) { }
+ public BadPasswordException(string message, Exception inner) : base(message, inner) { }
+
+ public BadPasswordException(string badPassword)
+ : base(Log.Format(Resources.Services.Exception.UserNotExistException, ("Bad Password", badPassword)))
+ {
+ Password = badPassword;
+ }
+
+ protected BadPasswordException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ /// <summary>
+ /// The wrong password.
+ /// </summary>
+ public string? Password { get; set; }
+ }
+}
diff --git a/Timeline/Services/JwtBadVersionException.cs b/Timeline/Services/JwtBadVersionException.cs new file mode 100644 index 00000000..4ce17710 --- /dev/null +++ b/Timeline/Services/JwtBadVersionException.cs @@ -0,0 +1,36 @@ +using System;
+using Timeline.Helpers;
+
+namespace Timeline.Services
+{
+ [Serializable]
+ public class JwtBadVersionException : Exception
+ {
+ public JwtBadVersionException() : base(Resources.Services.Exception.JwtBadVersionException) { }
+ public JwtBadVersionException(string message) : base(message) { }
+ public JwtBadVersionException(string message, Exception inner) : base(message, inner) { }
+
+ public JwtBadVersionException(long tokenVersion, long requiredVersion)
+ : base(Log.Format(Resources.Services.Exception.JwtBadVersionException,
+ ("Token Version", tokenVersion),
+ ("Required Version", requiredVersion)))
+ {
+ TokenVersion = tokenVersion;
+ RequiredVersion = requiredVersion;
+ }
+
+ protected JwtBadVersionException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ /// <summary>
+ /// The version in the token.
+ /// </summary>
+ public long? TokenVersion { get; set; }
+
+ /// <summary>
+ /// The version required.
+ /// </summary>
+ public long? RequiredVersion { get; set; }
+ }
+}
diff --git a/Timeline/Services/JwtService.cs b/Timeline/Services/JwtService.cs index 90d0c217..bf92966a 100644 --- a/Timeline/Services/JwtService.cs +++ b/Timeline/Services/JwtService.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
+using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
@@ -14,63 +15,6 @@ namespace Timeline.Services public long Version { get; set; }
}
- [Serializable]
- public class JwtTokenVerifyException : Exception
- {
- public static class ErrorCodes
- {
- // Codes in -1000 ~ -1999 usually means the user provides a token that is not created by this server.
-
- public const int Others = -1001;
- public const int NoIdClaim = -1002;
- public const int IdClaimBadFormat = -1003;
- public const int NoVersionClaim = -1004;
- public const int VersionClaimBadFormat = -1005;
-
- /// <summary>
- /// Corresponds to <see cref="SecurityTokenExpiredException"/>.
- /// </summary>
- public const int Expired = -2001;
- }
-
- private const string message = "Jwt token is bad.";
-
- public JwtTokenVerifyException() : base(message) { }
- public JwtTokenVerifyException(string message) : base(message) { }
- public JwtTokenVerifyException(string message, Exception inner) : base(message, inner) { }
-
- public JwtTokenVerifyException(int code) : base(GetErrorMessage(code)) { ErrorCode = code; }
- public JwtTokenVerifyException(string message, int code) : base(message) { ErrorCode = code; }
- public JwtTokenVerifyException(Exception inner, int code) : base(GetErrorMessage(code), inner) { ErrorCode = code; }
- public JwtTokenVerifyException(string message, Exception inner, int code) : base(message, inner) { ErrorCode = code; }
- protected JwtTokenVerifyException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- public int ErrorCode { get; set; }
-
- private static string GetErrorMessage(int errorCode)
- {
- switch (errorCode)
- {
- case ErrorCodes.Others:
- return "Uncommon error, see inner exception for more information.";
- case ErrorCodes.NoIdClaim:
- return "Id claim does not exist.";
- case ErrorCodes.IdClaimBadFormat:
- return "Id claim is not a number.";
- case ErrorCodes.NoVersionClaim:
- return "Version claim does not exist.";
- case ErrorCodes.VersionClaimBadFormat:
- return "Version claim is not a number";
- case ErrorCodes.Expired:
- return "Token is expired.";
- default:
- return "Unknown error code.";
- }
- }
- }
-
public interface IJwtService
{
/// <summary>
@@ -89,7 +33,7 @@ namespace Timeline.Services /// <param name="token">The token string to verify.</param>
/// <returns>Return the saved info in token.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
- /// <exception cref="JwtTokenVerifyException">Thrown when the token is invalid.</exception>
+ /// <exception cref="JwtVerifyException">Thrown when the token is invalid.</exception>
TokenInfo VerifyJwtToken(string token);
}
@@ -116,8 +60,8 @@ namespace Timeline.Services var config = _jwtConfig.CurrentValue;
var identity = new ClaimsIdentity();
- identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tokenInfo.Id.ToString(), ClaimValueTypes.Integer64));
- identity.AddClaim(new Claim(VersionClaimType, tokenInfo.Version.ToString(), ClaimValueTypes.Integer64));
+ 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()
{
@@ -159,15 +103,15 @@ namespace Timeline.Services var idClaim = principal.FindFirstValue(ClaimTypes.NameIdentifier);
if (idClaim == null)
- throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.NoIdClaim);
+ throw new JwtVerifyException(JwtVerifyException.ErrorCodes.NoIdClaim);
if (!long.TryParse(idClaim, out var id))
- throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.IdClaimBadFormat);
+ throw new JwtVerifyException(JwtVerifyException.ErrorCodes.IdClaimBadFormat);
var versionClaim = principal.FindFirstValue(VersionClaimType);
if (versionClaim == null)
- throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.NoVersionClaim);
+ throw new JwtVerifyException(JwtVerifyException.ErrorCodes.NoVersionClaim);
if (!long.TryParse(versionClaim, out var version))
- throw new JwtTokenVerifyException(JwtTokenVerifyException.ErrorCodes.VersionClaimBadFormat);
+ throw new JwtVerifyException(JwtVerifyException.ErrorCodes.VersionClaimBadFormat);
return new TokenInfo
{
@@ -177,11 +121,11 @@ namespace Timeline.Services }
catch (SecurityTokenExpiredException e)
{
- throw new JwtTokenVerifyException(e, JwtTokenVerifyException.ErrorCodes.Expired);
+ throw new JwtVerifyException(e, JwtVerifyException.ErrorCodes.Expired);
}
catch (Exception e)
{
- throw new JwtTokenVerifyException(e, JwtTokenVerifyException.ErrorCodes.Others);
+ throw new JwtVerifyException(e, JwtVerifyException.ErrorCodes.Others);
}
}
}
diff --git a/Timeline/Services/JwtVerifyException.cs b/Timeline/Services/JwtVerifyException.cs new file mode 100644 index 00000000..a915b51a --- /dev/null +++ b/Timeline/Services/JwtVerifyException.cs @@ -0,0 +1,59 @@ +using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Globalization;
+using static Timeline.Resources.Services.Exception;
+
+namespace Timeline.Services
+{
+ [Serializable]
+ public class JwtVerifyException : Exception
+ {
+ public static class ErrorCodes
+ {
+ // Codes in -1000 ~ -1999 usually means the user provides a token that is not created by this server.
+
+ public const int Others = -1001;
+ public const int NoIdClaim = -1002;
+ public const int IdClaimBadFormat = -1003;
+ public const int NoVersionClaim = -1004;
+ public const int VersionClaimBadFormat = -1005;
+
+ /// <summary>
+ /// Corresponds to <see cref="SecurityTokenExpiredException"/>.
+ /// </summary>
+ public const int Expired = -2001;
+ public const int OldVersion = -2002;
+ }
+
+ public JwtVerifyException() : base(GetErrorMessage(0)) { }
+ public JwtVerifyException(string message) : base(message) { }
+ public JwtVerifyException(string message, Exception inner) : base(message, inner) { }
+
+ public JwtVerifyException(int code) : base(GetErrorMessage(code)) { ErrorCode = code; }
+ public JwtVerifyException(string message, int code) : base(message) { ErrorCode = code; }
+ public JwtVerifyException(Exception inner, int code) : base(GetErrorMessage(code), inner) { ErrorCode = code; }
+ public JwtVerifyException(string message, Exception inner, int code) : base(message, inner) { ErrorCode = code; }
+ protected JwtVerifyException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ public int ErrorCode { get; set; }
+
+ private static string GetErrorMessage(int errorCode)
+ {
+ var reason = errorCode switch
+ {
+ ErrorCodes.Others => JwtVerifyExceptionOthers,
+ ErrorCodes.NoIdClaim => JwtVerifyExceptionNoIdClaim,
+ ErrorCodes.IdClaimBadFormat => JwtVerifyExceptionIdClaimBadFormat,
+ ErrorCodes.NoVersionClaim => JwtVerifyExceptionNoVersionClaim,
+ ErrorCodes.VersionClaimBadFormat => JwtVerifyExceptionVersionClaimBadFormat,
+ ErrorCodes.Expired => JwtVerifyExceptionExpired,
+ ErrorCodes.OldVersion => JwtVerifyExceptionOldVersion,
+ _ => JwtVerifyExceptionUnknown
+ };
+
+ return string.Format(CultureInfo.InvariantCulture, Resources.Services.Exception.JwtVerifyException, reason);
+ }
+ }
+}
diff --git a/Timeline/Services/UserDetailService.cs b/Timeline/Services/UserDetailService.cs deleted file mode 100644 index 5e049435..00000000 --- a/Timeline/Services/UserDetailService.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Timeline.Entities;
-using Timeline.Models;
-
-namespace Timeline.Services
-{
- public interface IUserDetailService
- {
- /// <summary>
- /// Get the nickname of user.
- /// </summary>
- /// <param name="username">The username to get nickname of.</param>
- /// <returns>The user's nickname. Null if not set.</returns>
- /// <exception cref="ArgumentException">Thrown if <paramref name="username"/> is null or empty.</exception>
- /// <exception cref="UserNotExistException">Thrown if user doesn't exist.</exception>
- Task<string> GetUserNickname(string username);
-
- /// <summary>
- /// Get the detail of user.
- /// </summary>
- /// <param name="username">The username to get user detail of.</param>
- /// <returns>The user detail.</returns>
- /// <exception cref="ArgumentException">Thrown if <paramref name="username"/> is null or empty.</exception>
- /// <exception cref="UserNotExistException">Thrown if user doesn't exist.</exception>
- Task<UserDetail> GetUserDetail(string username);
-
- /// <summary>
- /// Update the detail of user. This function does not do data check.
- /// </summary>
- /// <param name="username">The username to get user detail of.</param>
- /// <param name="detail">The detail to update. Can't be null. Any null member means not set.</param>
- /// <exception cref="ArgumentException">Thrown if <paramref name="username"/> is null or empty or <paramref name="detail"/> is null.</exception>
- /// <exception cref="UserNotExistException">Thrown if user doesn't exist.</exception>
- Task UpdateUserDetail(string username, UserDetail detail);
- }
-
- public class UserDetailService : IUserDetailService
- {
- private readonly ILogger<UserDetailService> _logger;
-
- private readonly DatabaseContext _databaseContext;
-
- public UserDetailService(ILogger<UserDetailService> logger, DatabaseContext databaseContext)
- {
- _logger = logger;
- _databaseContext = databaseContext;
- }
-
- private async Task<UserDetailEntity> CreateEntity(long userId)
- {
- var entity = new UserDetailEntity()
- {
- UserId = userId
- };
- _databaseContext.UserDetails.Add(entity);
- await _databaseContext.SaveChangesAsync();
- _logger.LogInformation("An entity is created in user_details.");
- return entity;
- }
-
- // Check the existence of user detail entry
- private async Task<UserDetailEntity> CheckAndInit(long userId)
- {
- var detail = await _databaseContext.UserDetails.Where(e => e.UserId == userId).SingleOrDefaultAsync();
- if (detail == null)
- {
- detail = await CreateEntity(userId);
- }
- return detail;
- }
-
- public async Task<string> GetUserNickname(string username)
- {
- var userId = await DatabaseExtensions.CheckAndGetUser(_databaseContext.Users, username);
- var detail = await _databaseContext.UserDetails.Where(e => e.UserId == userId).Select(e => new { e.Nickname }).SingleOrDefaultAsync();
- if (detail == null)
- {
- var entity = await CreateEntity(userId);
- return null;
- }
- else
- {
- var nickname = detail.Nickname;
- return string.IsNullOrEmpty(nickname) ? null : nickname;
- }
- }
-
- public async Task<UserDetail> GetUserDetail(string username)
- {
- var userId = await DatabaseExtensions.CheckAndGetUser(_databaseContext.Users, username);
- var detailEntity = await CheckAndInit(userId);
- return UserDetail.From(detailEntity);
- }
-
- public async Task UpdateUserDetail(string username, UserDetail detail)
- {
- if (detail == null)
- throw new ArgumentNullException(nameof(detail));
-
- var userId = await DatabaseExtensions.CheckAndGetUser(_databaseContext.Users, username);
- var detailEntity = await CheckAndInit(userId);
-
- if (detail.Nickname != null)
- detailEntity.Nickname = detail.Nickname;
-
- if (detail.QQ != null)
- detailEntity.QQ = detail.QQ;
-
- if (detail.Email != null)
- detailEntity.Email = detail.Email;
-
- if (detail.PhoneNumber != null)
- detailEntity.PhoneNumber = detail.PhoneNumber;
-
- if (detail.Description != null)
- detailEntity.Description = detail.Description;
-
- await _databaseContext.SaveChangesAsync();
- _logger.LogInformation("An entity is updated in user_details.");
- }
- }
-
- public static class UserDetailServiceCollectionExtensions
- {
- public static void AddUserDetailService(this IServiceCollection services)
- {
- services.AddScoped<IUserDetailService, UserDetailService>();
- }
- }
-}
diff --git a/Timeline/Services/UserNotExistException.cs b/Timeline/Services/UserNotExistException.cs new file mode 100644 index 00000000..c7317f56 --- /dev/null +++ b/Timeline/Services/UserNotExistException.cs @@ -0,0 +1,41 @@ +using System;
+using Timeline.Helpers;
+
+namespace Timeline.Services
+{
+ /// <summary>
+ /// The user requested does not exist.
+ /// </summary>
+ [Serializable]
+ public class UserNotExistException : Exception
+ {
+ public UserNotExistException() : base(Resources.Services.Exception.UserNotExistException) { }
+ public UserNotExistException(string message, Exception inner) : base(message, inner) { }
+
+ public UserNotExistException(string username)
+ : base(Log.Format(Resources.Services.Exception.UserNotExistException, ("Username", username)))
+ {
+ Username = username;
+ }
+
+ public UserNotExistException(long id)
+ : base(Log.Format(Resources.Services.Exception.UserNotExistException, ("Id", id)))
+ {
+ Id = id;
+ }
+
+ protected UserNotExistException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ /// <summary>
+ /// The username of the user that does not exist.
+ /// </summary>
+ public string? Username { get; set; }
+
+ /// <summary>
+ /// The id of the user that does not exist.
+ /// </summary>
+ public long? Id { get; set; }
+ }
+}
diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 9564b34b..aad4a806 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -1,11 +1,11 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
-using Timeline.Helpers;
using Timeline.Models;
using Timeline.Models.Validation;
using static Timeline.Helpers.MyLogHelper;
@@ -15,163 +15,8 @@ namespace Timeline.Services {
public class CreateTokenResult
{
- public string Token { get; set; }
- public UserInfo User { get; set; }
- }
-
- [Serializable]
- public class UserNotExistException : Exception
- {
- private const string message = "The user does not exist.";
-
- public UserNotExistException()
- : base(message)
- {
-
- }
-
- public UserNotExistException(string username)
- : base(Log.Format(message, ("Username", username)))
- {
- Username = username;
- }
-
- public UserNotExistException(long id)
- : base(Log.Format(message, ("Id", id)))
- {
- Id = id;
- }
-
- public UserNotExistException(string message, Exception inner) : base(message, inner) { }
-
- protected UserNotExistException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The username that does not exist.
- /// </summary>
- public string Username { get; set; }
-
- /// <summary>
- /// The id that does not exist.
- /// </summary>
- public long? Id { get; set; }
- }
-
- [Serializable]
- public class BadPasswordException : Exception
- {
- private const string message = "Password is wrong.";
-
- public BadPasswordException()
- : base(message)
- {
-
- }
-
- public BadPasswordException(string badPassword)
- : base(Log.Format(message, ("Bad Password", badPassword)))
- {
- Password = badPassword;
- }
-
- public BadPasswordException(string message, Exception inner) : base(message, inner) { }
-
- protected BadPasswordException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The wrong password.
- /// </summary>
- public string Password { get; set; }
- }
-
-
- [Serializable]
- public class BadTokenVersionException : Exception
- {
- private const string message = "Token version is expired.";
-
- public BadTokenVersionException()
- : base(message)
- {
-
- }
-
- public BadTokenVersionException(long tokenVersion, long requiredVersion)
- : base(Log.Format(message,
- ("Token Version", tokenVersion),
- ("Required Version", requiredVersion)))
- {
- TokenVersion = tokenVersion;
- RequiredVersion = requiredVersion;
- }
-
- public BadTokenVersionException(string message) : base(message) { }
- public BadTokenVersionException(string message, Exception inner) : base(message, inner) { }
-
- protected BadTokenVersionException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The version in the token.
- /// </summary>
- public long? TokenVersion { get; set; }
-
- /// <summary>
- /// The version required.
- /// </summary>
- public long? RequiredVersion { get; set; }
- }
-
- /// <summary>
- /// Thrown when username is of bad format.
- /// </summary>
- [Serializable]
- public class UsernameBadFormatException : Exception
- {
- private const string message = "Username is of bad format.";
-
- public UsernameBadFormatException() : base(message) { }
- public UsernameBadFormatException(string message) : base(message) { }
- public UsernameBadFormatException(string message, Exception inner) : base(message, inner) { }
-
- public UsernameBadFormatException(string username, string message) : base(message) { Username = username; }
- public UsernameBadFormatException(string username, string message, Exception inner) : base(message, inner) { Username = username; }
- protected UsernameBadFormatException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// Username of bad format.
- /// </summary>
- public string Username { get; private set; }
- }
-
-
- /// <summary>
- /// Thrown when the user already exists.
- /// </summary>
- [Serializable]
- public class UserAlreadyExistException : Exception
- {
- private const string message = "User already exists.";
-
- public UserAlreadyExistException() : base(message) { }
- public UserAlreadyExistException(string username) : base(Log.Format(message, ("Username", username))) { Username = username; }
- public UserAlreadyExistException(string username, string message) : base(message) { Username = username; }
- public UserAlreadyExistException(string message, Exception inner) : base(message, inner) { }
- protected UserAlreadyExistException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- /// <summary>
- /// The username that already exists.
- /// </summary>
- public string Username { get; set; }
+ public string Token { get; set; } = default!;
+ public UserInfo User { get; set; } = default!;
}
public interface IUserService
@@ -196,9 +41,8 @@ namespace Timeline.Services /// <param name="token">The token to verify.</param>
/// <returns>The user info specified by the token.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
- /// <exception cref="JwtTokenVerifyException">Thrown when the token is of bad format. Thrown by <see cref="JwtService.VerifyJwtToken(string)"/>.</exception>
+ /// <exception cref="JwtVerifyException">Thrown when the token is of bad format. Thrown by <see cref="JwtService.VerifyJwtToken(string)"/>.</exception>
/// <exception cref="UserNotExistException">Thrown when the user specified by the token does not exist. Usually it has been deleted after the token was issued.</exception>
- /// <exception cref="BadTokenVersionException">Thrown when the version in the token is expired. User needs to recreate the token.</exception>
Task<UserInfo> VerifyToken(string token);
/// <summary>
@@ -221,10 +65,12 @@ namespace Timeline.Services /// <param name="username">Username of user.</param>
/// <param name="password">Password of user.</param>
/// <param name="administrator">Whether the user is administrator.</param>
- /// <returns>Return <see cref="PutResult.Created"/> if a new user is created.
- /// Return <see cref="PutResult.Modified"/> if a existing user is modified.</returns>
- /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
+ /// <returns>
+ /// Return <see cref="PutResult.Create"/> if a new user is created.
+ /// Return <see cref="PutResult.Modify"/> if a existing user is modified.
+ /// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
+ /// <exception cref="UsernameBadFormatException">Thrown when <paramref name="username"/> is of bad format.</exception>
Task<PutResult> PutUser(string username, string password, bool administrator);
/// <summary>
@@ -237,7 +83,7 @@ namespace Timeline.Services /// <param name="administrator">Whether the user is administrator. Null if not modify.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="username"/> is null.</exception>
/// <exception cref="UserNotExistException">Thrown if the user with given username does not exist.</exception>
- Task PatchUser(string username, string password, bool? administrator);
+ Task PatchUser(string username, string? password, bool? administrator);
/// <summary>
/// Delete a user of given username.
@@ -266,13 +112,13 @@ namespace Timeline.Services /// <exception cref="ArgumentException">Thrown if <paramref name="oldUsername"/> or <paramref name="newUsername"/> is null or empty.</exception>
/// <exception cref="UserNotExistException">Thrown if the user with old username does not exist.</exception>
/// <exception cref="UsernameBadFormatException">Thrown if the new username is not accepted because of bad format.</exception>
- /// <exception cref="UserAlreadyExistException">Thrown if user with the new username already exists.</exception>
+ /// <exception cref="UsernameConfictException">Thrown if user with the new username already exists.</exception>
Task ChangeUsername(string oldUsername, string newUsername);
}
internal class UserCache
{
- public string Username { get; set; }
+ public string Username { get; set; } = default!;
public bool Administrator { get; set; }
public long Version { get; set; }
@@ -294,13 +140,16 @@ namespace Timeline.Services private readonly UsernameValidator _usernameValidator;
- public UserService(ILogger<UserService> logger, IMemoryCache memoryCache, DatabaseContext databaseContext, IJwtService jwtService, IPasswordService passwordService)
+ private readonly IStringLocalizerFactory _localizerFactory;
+
+ public UserService(ILogger<UserService> logger, IMemoryCache memoryCache, IStringLocalizerFactory localizerFactory, DatabaseContext databaseContext, IJwtService jwtService, IPasswordService passwordService)
{
_logger = logger;
_memoryCache = memoryCache;
_databaseContext = databaseContext;
_jwtService = jwtService;
_passwordService = passwordService;
+ _localizerFactory = localizerFactory;
_usernameValidator = new UsernameValidator();
}
@@ -368,7 +217,7 @@ namespace Timeline.Services }
if (tokenInfo.Version != cache.Version)
- throw new BadTokenVersionException(tokenInfo.Version, cache.Version);
+ throw new JwtVerifyException(new JwtBadVersionException(tokenInfo.Version, cache.Version), JwtVerifyException.ErrorCodes.OldVersion);
return cache.ToUserInfo();
}
@@ -395,7 +244,7 @@ namespace Timeline.Services if (password == null)
throw new ArgumentNullException(nameof(password));
- if (!_usernameValidator.Validate(username, out var message))
+ if (!_usernameValidator.Validate(username, _localizerFactory, out var message))
{
throw new UsernameBadFormatException(username, message);
}
@@ -414,7 +263,7 @@ namespace Timeline.Services await _databaseContext.AddAsync(newUser);
await _databaseContext.SaveChangesAsync();
_logger.LogInformation(FormatLogMessage("A new user entry is added to the database.", Pair("Id", newUser.Id)));
- return PutResult.Created;
+ return PutResult.Create;
}
user.EncryptedPassword = _passwordService.HashPassword(password);
@@ -426,7 +275,7 @@ namespace Timeline.Services //clear cache
RemoveCache(user.Id);
- return PutResult.Modified;
+ return PutResult.Modify;
}
public async Task PatchUser(string username, string password, bool? administrator)
@@ -504,7 +353,7 @@ namespace Timeline.Services if (string.IsNullOrEmpty(newUsername))
throw new ArgumentException("New username is null or empty", nameof(newUsername));
- if (!_usernameValidator.Validate(newUsername, out var message))
+ if (!_usernameValidator.Validate(newUsername, _localizerFactory, out var message))
throw new UsernameBadFormatException(newUsername, $"New username is of bad format. {message}");
var user = await _databaseContext.Users.Where(u => u.Name == oldUsername).SingleOrDefaultAsync();
@@ -513,7 +362,7 @@ namespace Timeline.Services var conflictUser = await _databaseContext.Users.Where(u => u.Name == newUsername).SingleOrDefaultAsync();
if (conflictUser != null)
- throw new UserAlreadyExistException(newUsername);
+ throw new UsernameConfictException(newUsername);
user.Name = newUsername;
user.Version += 1;
diff --git a/Timeline/Services/UsernameBadFormatException.cs b/Timeline/Services/UsernameBadFormatException.cs new file mode 100644 index 00000000..04354d22 --- /dev/null +++ b/Timeline/Services/UsernameBadFormatException.cs @@ -0,0 +1,27 @@ +using System;
+
+namespace Timeline.Services
+{
+ /// <summary>
+ /// Thrown when username is of bad format.
+ /// </summary>
+ [Serializable]
+ public class UsernameBadFormatException : Exception
+ {
+ public UsernameBadFormatException() : base(Resources.Services.Exception.UsernameBadFormatException) { }
+ public UsernameBadFormatException(string message) : base(message) { }
+ public UsernameBadFormatException(string message, Exception inner) : base(message, inner) { }
+
+ public UsernameBadFormatException(string username, string message) : base(message) { Username = username; }
+ public UsernameBadFormatException(string username, string message, Exception inner) : base(message, inner) { Username = username; }
+
+ protected UsernameBadFormatException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ /// <summary>
+ /// Username of bad format.
+ /// </summary>
+ public string? Username { get; private set; }
+ }
+}
diff --git a/Timeline/Services/UsernameConfictException.cs b/Timeline/Services/UsernameConfictException.cs new file mode 100644 index 00000000..fde1eda6 --- /dev/null +++ b/Timeline/Services/UsernameConfictException.cs @@ -0,0 +1,25 @@ +using System;
+using Timeline.Helpers;
+
+namespace Timeline.Services
+{
+ /// <summary>
+ /// Thrown when the user already exists.
+ /// </summary>
+ [Serializable]
+ public class UsernameConfictException : Exception
+ {
+ public UsernameConfictException() : base(Resources.Services.Exception.UsernameConfictException) { }
+ public UsernameConfictException(string username) : base(Log.Format(Resources.Services.Exception.UsernameConfictException, ("Username", username))) { Username = username; }
+ public UsernameConfictException(string username, string message) : base(message) { Username = username; }
+ public UsernameConfictException(string message, Exception inner) : base(message, inner) { }
+ protected UsernameConfictException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ /// <summary>
+ /// The username that already exists.
+ /// </summary>
+ public string? Username { get; set; }
+ }
+}
|