aboutsummaryrefslogtreecommitdiff
path: root/Timeline
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline')
-rw-r--r--Timeline/Controllers/TokenController.cs131
-rw-r--r--Timeline/Controllers/UserController.cs8
-rw-r--r--Timeline/ErrorCodes.cs29
-rw-r--r--Timeline/Helpers/Log.cs19
-rw-r--r--Timeline/Models/Http/Common.cs74
-rw-r--r--Timeline/Models/Http/Token.cs2
6 files changed, 166 insertions, 97 deletions
diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs
index 3c166448..2e661695 100644
--- a/Timeline/Controllers/TokenController.cs
+++ b/Timeline/Controllers/TokenController.cs
@@ -3,39 +3,42 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
-using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Models.Http;
using Timeline.Services;
-using static Timeline.Helpers.MyLogHelper;
+using Timeline.Helpers;
-namespace Timeline.Controllers
+namespace Timeline
{
- [Route("token")]
- [ApiController]
- public class TokenController : Controller
+ public static partial class ErrorCodes
{
- private static class LoggingEventIds
- {
- public const int CreateSucceeded = 1000;
- public const int CreateFailed = 1001;
-
- public const int VerifySucceeded = 2000;
- public const int VerifyFailed = 2001;
- }
-
- public static class ErrorCodes
+ public static partial class Http
{
- public const int Create_UserNotExist = -1001;
- public const int Create_BadPassword = -1002;
- public const int Create_BadExpireOffset = -1003;
+ public static class Token // bbb = 001
+ {
+ public static class Create // cc = 01
+ {
+ public const int BadCredential = 10010101;
+ }
- public const int Verify_BadToken = -2001;
- public const int Verify_UserNotExist = -2002;
- public const int Verify_BadVersion = -2003;
- public const int Verify_Expired = -2004;
+ public static class Verify // cc = 02
+ {
+ public const int BadFormat = 10010201;
+ public const int UserNotExist = 10010202;
+ public const int OldVersion = 10010203;
+ public const int Expired = 10010204;
+ }
+ }
}
+ }
+}
+namespace Timeline.Controllers
+{
+ [Route("token")]
+ [ApiController]
+ public class TokenController : Controller
+ {
private readonly IUserService _userService;
private readonly ILogger<TokenController> _logger;
private readonly IClock _clock;
@@ -51,23 +54,28 @@ namespace Timeline.Controllers
[AllowAnonymous]
public async Task<IActionResult> Create([FromBody] CreateTokenRequest request)
{
- void LogFailure(string reason, int code, Exception e = null)
+ void LogFailure(string reason, Exception e = null)
{
- _logger.LogInformation(LoggingEventIds.CreateFailed, e, FormatLogMessage("Attemp to login failed.",
- Pair("Reason", reason),
- Pair("Code", code),
- Pair("Username", request.Username),
- Pair("Password", request.Password),
- Pair("Expire Offset (in days)", request.ExpireOffset)));
+ _logger.LogInformation(e, Log.Format("Attemp to login failed.",
+ ("Reason", reason),
+ ("Username", request.Username),
+ ("Password", request.Password),
+ ("Expire (in days)", request.Expire)
+ ));
}
try
{
- var expiredTime = request.ExpireOffset == null ? null : (DateTime?)(_clock.GetCurrentTime().AddDays(request.ExpireOffset.Value));
- var result = await _userService.CreateToken(request.Username, request.Password, expiredTime);
- _logger.LogInformation(LoggingEventIds.CreateSucceeded, FormatLogMessage("Attemp to login succeeded.",
- Pair("Username", request.Username),
- Pair("Expire Time", expiredTime == null ? "default" : expiredTime.Value.ToString())));
+ DateTime? expireTime = null;
+ if (request.Expire != null)
+ expireTime = _clock.GetCurrentTime().AddDays(request.Expire.Value);
+
+ var result = await _userService.CreateToken(request.Username, request.Password, expireTime);
+
+ _logger.LogInformation(Log.Format("Attemp to login succeeded.",
+ ("Username", request.Username),
+ ("Expire At", expireTime?.ToString() ?? "default")
+ ));
return Ok(new CreateTokenResponse
{
Token = result.Token,
@@ -76,15 +84,15 @@ namespace Timeline.Controllers
}
catch (UserNotExistException e)
{
- var code = ErrorCodes.Create_UserNotExist;
- LogFailure("User does not exist.", code, e);
- return BadRequest(new CommonResponse(code, "Bad username or password."));
+ LogFailure("User does not exist.", e);
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Create.BadCredential,
+ "Bad username or password."));
}
catch (BadPasswordException e)
{
- var code = ErrorCodes.Create_BadPassword;
- LogFailure("Password is wrong.", code, e);
- return BadRequest(new CommonResponse(code, "Bad username or password."));
+ LogFailure("Password is wrong.", e);
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Create.BadCredential,
+ "Bad username or password."));
}
}
@@ -92,22 +100,20 @@ namespace Timeline.Controllers
[AllowAnonymous]
public async Task<IActionResult> Verify([FromBody] VerifyTokenRequest request)
{
- void LogFailure(string reason, int code, Exception e = null, params KeyValuePair<string, object>[] otherProperties)
+ void LogFailure(string reason, Exception e = null, params (string, object)[] otherProperties)
{
- var properties = new KeyValuePair<string, object>[3 + otherProperties.Length];
- properties[0] = Pair("Reason", reason);
- properties[1] = Pair("Code", code);
- properties[2] = Pair("Token", request.Token);
- otherProperties.CopyTo(properties, 3);
- _logger.LogInformation(LoggingEventIds.VerifyFailed, e, FormatLogMessage("Token verification failed.", properties));
+ var properties = new (string, object)[2 + otherProperties.Length];
+ properties[0] = ("Reason", reason);
+ properties[1] = ("Token", request.Token);
+ otherProperties.CopyTo(properties, 2);
+ _logger.LogInformation(e, Log.Format("Token verification failed.", properties));
}
try
{
var result = await _userService.VerifyToken(request.Token);
- _logger.LogInformation(LoggingEventIds.VerifySucceeded,
- FormatLogMessage("Token verification succeeded.",
- Pair("Username", result.Username), Pair("Token", request.Token)));
+ _logger.LogInformation(Log.Format("Token verification succeeded.",
+ ("Username", result.Username), ("Token", request.Token)));
return Ok(new VerifyTokenResponse
{
User = result
@@ -118,33 +124,28 @@ namespace Timeline.Controllers
if (e.ErrorCode == JwtTokenVerifyException.ErrorCodes.Expired)
{
const string message = "Token is expired.";
- var code = ErrorCodes.Verify_Expired;
var innerException = e.InnerException as SecurityTokenExpiredException;
- LogFailure(message, code, e, Pair("Expires", innerException.Expires));
- return BadRequest(new CommonResponse(code, message));
+ LogFailure(message, e, ("Expires", innerException.Expires), ("Current Time", _clock.GetCurrentTime()));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Verify.Expired, message));
}
else
{
const string message = "Token is of bad format.";
- var code = ErrorCodes.Verify_BadToken;
- LogFailure(message, code, e);
- return BadRequest(new CommonResponse(code, message));
+ LogFailure(message, e);
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Verify.BadFormat, message));
}
}
catch (UserNotExistException e)
{
const string message = "User does not exist. Administrator might have deleted this user.";
- var code = ErrorCodes.Verify_UserNotExist;
- LogFailure(message, code, e);
- return BadRequest(new CommonResponse(code, message));
+ LogFailure(message, e);
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Verify.UserNotExist, message));
}
catch (BadTokenVersionException e)
{
- const string message = "Token has a old version.";
- var code = ErrorCodes.Verify_BadVersion;
- LogFailure(message, code, e);
- _logger.LogInformation(LoggingEventIds.VerifyFailed, e, "Attemp to verify a bad token because version is old. Code: {} Token: {}.", code, request.Token);
- return BadRequest(new CommonResponse(code, message));
+ const string message = "Token has an old version.";
+ LogFailure(message, e, ("Token Version", e.TokenVersion), ("Required Version", e.RequiredVersion));
+ return BadRequest(new CommonResponse(ErrorCodes.Http.Token.Verify.OldVersion, message));
}
}
}
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs
index bd13f0a3..c0cd3cdb 100644
--- a/Timeline/Controllers/UserController.cs
+++ b/Timeline/Controllers/UserController.cs
@@ -65,10 +65,10 @@ namespace Timeline.Controllers
{
case PutResult.Created:
_logger.LogInformation(FormatLogMessage("A user is created.", Pair("Username", username)));
- return CreatedAtAction("Get", new { username }, CommonPutResponse.Created);
+ return CreatedAtAction("Get", new { username }, CommonPutResponse.Create());
case PutResult.Modified:
_logger.LogInformation(FormatLogMessage("A user is modified.", Pair("Username", username)));
- return Ok(CommonPutResponse.Modified);
+ return Ok(CommonPutResponse.Modify());
default:
throw new Exception("Unreachable code.");
}
@@ -102,12 +102,12 @@ namespace Timeline.Controllers
{
await _userService.DeleteUser(username);
_logger.LogInformation(FormatLogMessage("A user is deleted.", Pair("Username", username)));
- return Ok(CommonDeleteResponse.Deleted);
+ return Ok(CommonDeleteResponse.Delete());
}
catch (UserNotExistException e)
{
_logger.LogInformation(e, FormatLogMessage("Attempt to delete a non-existent user.", Pair("Username", username)));
- return Ok(CommonDeleteResponse.NotExists);
+ return Ok(CommonDeleteResponse.NotExist());
}
}
diff --git a/Timeline/ErrorCodes.cs b/Timeline/ErrorCodes.cs
new file mode 100644
index 00000000..0b325e27
--- /dev/null
+++ b/Timeline/ErrorCodes.cs
@@ -0,0 +1,29 @@
+namespace Timeline
+{
+ /// <summary>
+ /// All error code constants.
+ /// </summary>
+ /// <remarks>
+ /// Scheme:
+ /// abbbccdd
+ /// </remarks>
+ public static partial class ErrorCodes
+ {
+ public static partial class Http // a = 1
+ {
+ public static class Common // bbb = 000
+ {
+ public const int InvalidModel = 10000000;
+
+ public static class Header // cc = 01
+ {
+ public const int Missing_ContentType = 10010101; // dd = 01
+ public const int Missing_ContentLength = 10010102; // dd = 02
+ public const int Zero_ContentLength = 10010103; // dd = 03
+ public const int BadFormat_IfNonMatch = 10010104; // dd = 04
+ }
+ }
+ }
+
+ }
+}
diff --git a/Timeline/Helpers/Log.cs b/Timeline/Helpers/Log.cs
index 123e8a8e..64391cd1 100644
--- a/Timeline/Helpers/Log.cs
+++ b/Timeline/Helpers/Log.cs
@@ -3,6 +3,7 @@ using System.Text;
namespace Timeline.Helpers
{
+ // TODO! Remember to remove this after refactor.
public static class MyLogHelper
{
public static KeyValuePair<string, object> Pair(string key, object value) => new KeyValuePair<string, object>(key, value);
@@ -21,4 +22,22 @@ namespace Timeline.Helpers
return builder.ToString();
}
}
+
+ public static class Log
+ {
+ public static string Format(string summary, params (string, object)[] properties)
+ {
+ var builder = new StringBuilder();
+ builder.Append(summary);
+ foreach (var property in properties)
+ {
+ var (key, value) = property;
+ builder.AppendLine();
+ builder.Append(key);
+ builder.Append(" : ");
+ builder.Append(value);
+ }
+ return builder.ToString();
+ }
+ }
}
diff --git a/Timeline/Models/Http/Common.cs b/Timeline/Models/Http/Common.cs
index a72f187c..af185e85 100644
--- a/Timeline/Models/Http/Common.cs
+++ b/Timeline/Models/Http/Common.cs
@@ -2,43 +2,29 @@ namespace Timeline.Models.Http
{
public class CommonResponse
{
- public static class ErrorCodes
- {
- /// <summary>
- /// Used when the model is invaid.
- /// For example a required field is null.
- /// </summary>
- public const int InvalidModel = -100;
-
- public const int Header_Missing_ContentType = -111;
- public const int Header_Missing_ContentLength = -112;
- public const int Header_Zero_ContentLength = -113;
- public const int Header_BadFormat_IfNonMatch = -114;
- }
-
public static CommonResponse InvalidModel(string message)
{
- return new CommonResponse(ErrorCodes.InvalidModel, message);
+ return new CommonResponse(ErrorCodes.Http.Common.InvalidModel, message);
}
public static CommonResponse MissingContentType()
{
- return new CommonResponse(ErrorCodes.Header_Missing_ContentType, "Header Content-Type is required.");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Missing_ContentType, "Header Content-Type is required.");
}
public static CommonResponse MissingContentLength()
{
- return new CommonResponse(ErrorCodes.Header_Missing_ContentLength, "Header Content-Length is missing or of bad format.");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Missing_ContentLength, "Header Content-Length is missing or of bad format.");
}
public static CommonResponse ZeroContentLength()
{
- return new CommonResponse(ErrorCodes.Header_Zero_ContentLength, "Header Content-Length must not be 0.");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.Zero_ContentLength, "Header Content-Length must not be 0.");
}
public static CommonResponse BadIfNonMatch()
{
- return new CommonResponse(ErrorCodes.Header_BadFormat_IfNonMatch, "Header If-Non-Match is of bad format.");
+ return new CommonResponse(ErrorCodes.Http.Common.Header.BadFormat_IfNonMatch, "Header If-Non-Match is of bad format.");
}
public CommonResponse()
@@ -56,21 +42,55 @@ namespace Timeline.Models.Http
public string Message { get; set; }
}
+ public class CommonDataResponse<T> : CommonResponse
+ {
+ public CommonDataResponse()
+ {
+
+ }
+
+ public CommonDataResponse(int code, string message, T data)
+ : base(code, message)
+ {
+ Data = data;
+ }
+
+ public T Data { get; set; }
+ }
+
public static class CommonPutResponse
{
- public const int CreatedCode = 0;
- public const int ModifiedCode = 1;
+ public class ResponseData
+ {
+ public ResponseData(bool create)
+ {
+ Create = create;
+ }
- public static CommonResponse Created { get; } = new CommonResponse(CreatedCode, "A new item is created.");
- public static CommonResponse Modified { get; } = new CommonResponse(ModifiedCode, "An existent item is modified.");
+ public bool Create { get; set; }
+ }
+
+ public static CommonDataResponse<ResponseData> Create() =>
+ new CommonDataResponse<ResponseData>(0, "A new item is created.", new ResponseData(true));
+ public static CommonDataResponse<ResponseData> Modify() =>
+ new CommonDataResponse<ResponseData>(0, "An existent item is modified.", new ResponseData(false));
}
public static class CommonDeleteResponse
{
- public const int DeletedCode = 0;
- public const int NotExistsCode = 1;
+ public class ResponseData
+ {
+ public ResponseData(bool delete)
+ {
+ Delete = delete;
+ }
+
+ public bool Delete { get; set; }
+ }
- public static CommonResponse Deleted { get; } = new CommonResponse(DeletedCode, "An existent item is deleted.");
- public static CommonResponse NotExists { get; } = new CommonResponse(NotExistsCode, "The item does not exist.");
+ public static CommonDataResponse<ResponseData> Delete() =>
+ new CommonDataResponse<ResponseData>(0, "An existent item is deleted.", new ResponseData(true));
+ public static CommonDataResponse<ResponseData> NotExist() =>
+ new CommonDataResponse<ResponseData>(0, "The item does not exist.", new ResponseData(false));
}
}
diff --git a/Timeline/Models/Http/Token.cs b/Timeline/Models/Http/Token.cs
index 68a66d0a..615b6d8a 100644
--- a/Timeline/Models/Http/Token.cs
+++ b/Timeline/Models/Http/Token.cs
@@ -10,7 +10,7 @@ namespace Timeline.Models.Http
public string Password { get; set; }
// in days, optional
[Range(1, 365)]
- public int? ExpireOffset { get; set; }
+ public int? Expire { get; set; }
}
public class CreateTokenResponse