aboutsummaryrefslogtreecommitdiffstats
path: root/code
diff options
context:
space:
mode:
Diffstat (limited to 'code')
-rw-r--r--code/api/src/Data/Models/RequestTimeZoneInfo.cs8
-rw-r--r--code/api/src/Endpoints/EndpointBase.cs12
-rw-r--r--code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs17
-rw-r--r--code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs35
-rw-r--r--code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs5
-rw-r--r--code/api/src/Endpoints/Internal/Account/GetAccountRoute.cs (renamed from code/api/src/Endpoints/Internal/Account/GetRoute.cs)5
-rw-r--r--code/api/src/Endpoints/Internal/Account/LoginPayload.cs18
-rw-r--r--code/api/src/Endpoints/Internal/Account/LoginRoute.cs29
-rw-r--r--code/api/src/Endpoints/Internal/Account/UpdateAccountPayload.cs17
-rw-r--r--code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs37
-rw-r--r--code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestPayload.cs6
-rw-r--r--code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs19
-rw-r--r--code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestPayload.cs14
-rw-r--r--code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs10
-rw-r--r--code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs2
15 files changed, 91 insertions, 143 deletions
diff --git a/code/api/src/Data/Models/RequestTimeZoneInfo.cs b/code/api/src/Data/Models/RequestTimeZoneInfo.cs
new file mode 100644
index 0000000..4c5aa13
--- /dev/null
+++ b/code/api/src/Data/Models/RequestTimeZoneInfo.cs
@@ -0,0 +1,8 @@
+namespace IOL.GreatOffice.Api.Data.Models;
+
+public class RequestTimeZoneInfo
+{
+ public TimeZoneInfo TimeZoneInfo { get; set; }
+ public int Offset { get; set; }
+ public DateTime LocalDateTime { get; set; }
+} \ No newline at end of file
diff --git a/code/api/src/Endpoints/EndpointBase.cs b/code/api/src/Endpoints/EndpointBase.cs
index a5b0931..6850755 100644
--- a/code/api/src/Endpoints/EndpointBase.cs
+++ b/code/api/src/Endpoints/EndpointBase.cs
@@ -24,6 +24,11 @@ public class EndpointBase : ControllerBase
}
[NonAction]
+ protected ActionResult KnownProblem(KnownProblemModel problem) {
+ return BadRequest(problem);
+ }
+
+ [NonAction]
protected RequestTimeZoneInfo GetRequestTimeZone(ILogger logger = default) {
Request.Headers.TryGetValue(AppHeaders.BROWSER_TIME_ZONE, out var timeZoneHeader);
var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneHeader.ToString().HasValue() ? timeZoneHeader.ToString() : "UTC");
@@ -43,11 +48,4 @@ public class EndpointBase : ControllerBase
LocalDateTime = TimeZoneInfo.ConvertTimeFromUtc(AppDateTime.UtcNow, tz)
};
}
-
- public class RequestTimeZoneInfo
- {
- public TimeZoneInfo TimeZoneInfo { get; set; }
- public int Offset { get; set; }
- public DateTime LocalDateTime { get; set; }
- }
} \ No newline at end of file
diff --git a/code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs b/code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs
deleted file mode 100644
index 1161af3..0000000
--- a/code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace IOL.GreatOffice.Api.Endpoints.Internal.Account;
-
-/// <summary>
-/// Payload for creating new user accounts.
-/// </summary>
-public class CreateAccountPayload
-{
- /// <summary>
- /// Username for the new account.
- /// </summary>
- public string Username { get; set; }
-
- /// <summary>
- /// Password for the new account.
- /// </summary>
- public string Password { get; set; }
-} \ No newline at end of file
diff --git a/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs
index f34056d..6b6e7bc 100644
--- a/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs
+++ b/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs
@@ -1,35 +1,44 @@
+using Microsoft.Extensions.Localization;
+
namespace IOL.GreatOffice.Api.Endpoints.Internal.Account;
-public class CreateAccountRoute : RouteBaseAsync.WithRequest<CreateAccountPayload>.WithActionResult
+public class CreateAccountRoute : RouteBaseAsync.WithRequest<CreateAccountRoute.Payload>.WithActionResult
{
private readonly MainAppDatabase _database;
private readonly UserService _userService;
+ private readonly IStringLocalizer<SharedResources> _localizer;
- public CreateAccountRoute(UserService userService, MainAppDatabase database) {
+ public CreateAccountRoute(UserService userService, MainAppDatabase database, IStringLocalizer<SharedResources> localizer) {
_userService = userService;
_database = database;
+ _localizer = localizer;
+ }
+
+ public class Payload
+ {
+ public string Username { get; set; }
+ public string Password { get; set; }
}
- /// <summary>
- /// Create a new user account.
- /// </summary>
- /// <param name="request"></param>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
[AllowAnonymous]
[HttpPost("~/_/account/create")]
- public override async Task<ActionResult> HandleAsync(CreateAccountPayload request, CancellationToken cancellationToken = default) {
+ public override async Task<ActionResult> HandleAsync(Payload request, CancellationToken cancellationToken = default) {
+ var errors = new Dictionary<string, string>();
if (request.Username.IsValidEmailAddress() == false) {
- return BadRequest(new KnownProblemModel("Invalid form", request.Username + " does not look like a valid email"));
+ errors.Add("username", _localizer["{0} does not look like a valid email", request.Username]);
}
if (request.Password.Length < 6) {
- return BadRequest(new KnownProblemModel("Invalid form", "The password requires 6 or more characters."));
+ errors.Add("password", _localizer["The password requires 6 or more characters."]);
}
var username = request.Username.Trim();
- if (_database.Users.Any(c => c.Username == username)) {
- return BadRequest(new KnownProblemModel("Username is not available", "There is already a user registered with email: " + username));
+ if (errors.All(p => p.Key != "username") && _database.Users.Any(c => c.Username == username)) {
+ errors.Add("username", _localizer["There is already a user registered with username: {0}", username]);
+ }
+
+ if (errors.Any()) {
+ return KnownProblem(_localizer["Invalid form"], _localizer["You have invalid values"], errors);
}
var user = new User(username);
diff --git a/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs
index f0e8362..e9fe40d 100644
--- a/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs
+++ b/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs
@@ -10,11 +10,6 @@ public class DeleteAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult
_userService = userService;
}
- /// <summary>
- /// Delete the logged on user's account.
- /// </summary>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
[HttpDelete("~/_/account/delete")]
public override async Task<ActionResult> HandleAsync(CancellationToken cancellationToken = default) {
var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id);
diff --git a/code/api/src/Endpoints/Internal/Account/GetRoute.cs b/code/api/src/Endpoints/Internal/Account/GetAccountRoute.cs
index 8d6c50f..121b40f 100644
--- a/code/api/src/Endpoints/Internal/Account/GetRoute.cs
+++ b/code/api/src/Endpoints/Internal/Account/GetAccountRoute.cs
@@ -8,11 +8,6 @@ public class GetAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult<Lo
_database = database;
}
- /// <summary>
- /// Get the logged on user's session data.
- /// </summary>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
[HttpGet("~/_/account")]
public override async Task<ActionResult<LoggedInUserModel>> HandleAsync(CancellationToken cancellationToken = default) {
var user = _database.Users
diff --git a/code/api/src/Endpoints/Internal/Account/LoginPayload.cs b/code/api/src/Endpoints/Internal/Account/LoginPayload.cs
index 807662c..a5670c6 100644
--- a/code/api/src/Endpoints/Internal/Account/LoginPayload.cs
+++ b/code/api/src/Endpoints/Internal/Account/LoginPayload.cs
@@ -1,22 +1,6 @@
namespace IOL.GreatOffice.Api.Endpoints.Internal.Account;
-/// <summary>
-/// Payload for logging in a user.
-/// </summary>
public class LoginPayload
{
- /// <summary>
- /// Username of the user's account.
- /// </summary>
- public string Username { get; set; }
- /// <summary>
- /// Password of the user's account.
- /// </summary>
- public string Password { get; set; }
-
- /// <summary>
- /// Specify that the created session should be long lived and continually refreshed.
- /// </summary>
- public bool Persist { get; set; }
-}
+} \ No newline at end of file
diff --git a/code/api/src/Endpoints/Internal/Account/LoginRoute.cs b/code/api/src/Endpoints/Internal/Account/LoginRoute.cs
index 696c3c2..eaebc2a 100644
--- a/code/api/src/Endpoints/Internal/Account/LoginRoute.cs
+++ b/code/api/src/Endpoints/Internal/Account/LoginRoute.cs
@@ -1,31 +1,32 @@
+using Microsoft.Extensions.Localization;
+
namespace IOL.GreatOffice.Api.Endpoints.Internal.Account;
-public class LoginRoute : RouteBaseAsync.WithRequest<LoginPayload>.WithActionResult
+public class LoginRoute : RouteBaseAsync.WithRequest<LoginRoute.Payload>.WithActionResult
{
private readonly MainAppDatabase _database;
private readonly UserService _userService;
+ private readonly IStringLocalizer<SharedResources> _localizer;
- public LoginRoute(MainAppDatabase database, UserService userService) {
+ public LoginRoute(MainAppDatabase database, UserService userService, IStringLocalizer<SharedResources> localizer) {
_database = database;
_userService = userService;
+ _localizer = localizer;
+ }
+
+ public class Payload
+ {
+ public string Username { get; set; }
+ public string Password { get; set; }
+ public bool Persist { get; set; }
}
- /// <summary>
- /// Login a user.
- /// </summary>
- /// <param name="request"></param>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
[AllowAnonymous]
[HttpPost("~/_/account/login")]
- public override async Task<ActionResult> HandleAsync(LoginPayload request, CancellationToken cancellationToken = default) {
- if (!ModelState.IsValid) {
- return BadRequest(ModelState);
- }
-
+ public override async Task<ActionResult> HandleAsync(Payload request, CancellationToken cancellationToken = default) {
var user = _database.Users.SingleOrDefault(u => u.Username == request.Username);
if (user == default || !user.VerifyPassword(request.Password)) {
- return BadRequest(new KnownProblemModel("Invalid username or password"));
+ return KnownProblem(_localizer["Invalid username or password"]);
}
await _userService.LogInUser(HttpContext, user, request.Persist);
diff --git a/code/api/src/Endpoints/Internal/Account/UpdateAccountPayload.cs b/code/api/src/Endpoints/Internal/Account/UpdateAccountPayload.cs
deleted file mode 100644
index 88a3237..0000000
--- a/code/api/src/Endpoints/Internal/Account/UpdateAccountPayload.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace IOL.GreatOffice.Api.Endpoints.Internal.Account;
-
-/// <summary>
-/// Payload for updating an account.
-/// </summary>
-public class UpdatePayload
-{
- /// <summary>
- /// Username to set on the logged on user's account.
- /// </summary>
- public string Username { get; set; }
-
- /// <summary>
- /// Password to set on the logged on user's account.
- /// </summary>
- public string Password { get; set; }
-}
diff --git a/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs
index 02dc3f1..c8999e0 100644
--- a/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs
+++ b/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs
@@ -1,21 +1,26 @@
+using Microsoft.Extensions.Localization;
+
namespace IOL.GreatOffice.Api.Endpoints.Internal.Account;
-public class UpdateAccountRoute : RouteBaseAsync.WithRequest<UpdatePayload>.WithActionResult
+public class UpdateAccountRoute : RouteBaseAsync.WithRequest<UpdateAccountRoute.Payload>.WithActionResult
{
private readonly MainAppDatabase _database;
+ private readonly IStringLocalizer<SharedResources> _localizer;
- public UpdateAccountRoute(MainAppDatabase database) {
+ public UpdateAccountRoute(MainAppDatabase database, IStringLocalizer<SharedResources> localizer) {
_database = database;
+ _localizer = localizer;
+ }
+
+ public class Payload
+ {
+ public string Username { get; set; }
+
+ public string Password { get; set; }
}
- /// <summary>
- /// Update the logged on user's data.
- /// </summary>
- /// <param name="request"></param>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
[HttpPost("~/_/account/update")]
- public override async Task<ActionResult> HandleAsync(UpdatePayload request, CancellationToken cancellationToken = default) {
+ public override async Task<ActionResult> HandleAsync(Payload request, CancellationToken cancellationToken = default) {
var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id);
if (user == default) {
await HttpContext.SignOutAsync();
@@ -23,12 +28,13 @@ public class UpdateAccountRoute : RouteBaseAsync.WithRequest<UpdatePayload>.With
}
if (request.Password.IsNullOrWhiteSpace() && request.Username.IsNullOrWhiteSpace()) {
- return BadRequest(new KnownProblemModel("Invalid request", "No data was submitted"));
+ return KnownProblem(_localizer["Invalid request"], _localizer["No data was submitted"]);
}
+ var validationProblems = new Dictionary<string, string>();
+
if (request.Password.HasValue() && request.Password.Length < 6) {
- return BadRequest(new KnownProblemModel("Invalid request",
- "The new password must contain at least 6 characters"));
+ validationProblems.Add("password", _localizer["The new password must contain at least 6 characters"]);
}
if (request.Password.HasValue()) {
@@ -36,8 +42,11 @@ public class UpdateAccountRoute : RouteBaseAsync.WithRequest<UpdatePayload>.With
}
if (request.Username.HasValue() && !request.Username.IsValidEmailAddress()) {
- return BadRequest(new KnownProblemModel("Invalid request",
- "The new username does not look like a valid email address"));
+ validationProblems.Add("username", _localizer["The new username does not look like a valid email address"]);
+ }
+
+ if (validationProblems.Any()) {
+ return KnownProblem(_localizer["Validation problems"], _localizer["Your form has invalid values"], validationProblems);
}
if (request.Username.HasValue()) {
diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestPayload.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestPayload.cs
deleted file mode 100644
index 1adb344..0000000
--- a/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestPayload.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests;
-
-public class CreateResetRequestPayload
-{
- public string Username { get; set; }
-} \ No newline at end of file
diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs
index bb72d38..49df35b 100644
--- a/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs
+++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs
@@ -1,26 +1,25 @@
namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests;
-public class Route : RouteBaseAsync.WithRequest<CreateResetRequestPayload>.WithActionResult
+public class CreateResetRequestRoute : RouteBaseAsync.WithRequest<CreateResetRequestRoute.Payload>.WithActionResult
{
- private readonly ILogger<Route> _logger;
+ private readonly ILogger<CreateResetRequestRoute> _logger;
private readonly PasswordResetService _passwordResetService;
private readonly MainAppDatabase _database;
- public Route(ILogger<Route> logger, PasswordResetService passwordResetService, MainAppDatabase database) {
+ public CreateResetRequestRoute(ILogger<CreateResetRequestRoute> logger, PasswordResetService passwordResetService, MainAppDatabase database) {
_logger = logger;
_passwordResetService = passwordResetService;
_database = database;
}
- /// <summary>
- /// Create a new password reset request.
- /// </summary>
- /// <param name="request"></param>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
+ public class Payload
+ {
+ public string Username { get; set; }
+ }
+
[AllowAnonymous]
[HttpPost("~/_/password-reset-request/create")]
- public override async Task<ActionResult> HandleAsync(CreateResetRequestPayload request, CancellationToken cancellationToken = default) {
+ public override async Task<ActionResult> HandleAsync(Payload request, CancellationToken cancellationToken = default) {
if (!request.Username.IsValidEmailAddress()) {
_logger.LogInformation("Username is invalid, not doing request for password change");
return KnownProblem("Invalid email address", request.Username + " looks like an invalid email address");
diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestPayload.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestPayload.cs
deleted file mode 100644
index f0fb59f..0000000
--- a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestPayload.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests;
-
-public class FulfillResetRequestPayload
-{
- /// <summary>
- /// Id of the password reset request to fulfill
- /// </summary>
- public Guid Id { get; set; }
-
- /// <summary>
- /// New password to set on the relevant account
- /// </summary>
- public string NewPassword { get; set; }
-}
diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs
index 5749242..c831470 100644
--- a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs
+++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs
@@ -1,6 +1,6 @@
namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests;
-public class FulfillResetRequestRoute : RouteBaseAsync.WithRequest<FulfillResetRequestPayload>.WithActionResult
+public class FulfillResetRequestRoute : RouteBaseAsync.WithRequest<FulfillResetRequestRoute.Payload>.WithActionResult
{
private readonly PasswordResetService _passwordResetService;
@@ -8,9 +8,15 @@ public class FulfillResetRequestRoute : RouteBaseAsync.WithRequest<FulfillResetR
_passwordResetService = passwordResetService;
}
+ public class Payload
+ {
+ public Guid Id { get; set; }
+ public string NewPassword { get; set; }
+ }
+
[AllowAnonymous]
[HttpPost("~/_/password-reset-request/fulfill")]
- public override async Task<ActionResult> HandleAsync(FulfillResetRequestPayload request, CancellationToken cancellationToken = default) {
+ public override async Task<ActionResult> HandleAsync(Payload request, CancellationToken cancellationToken = default) {
return await _passwordResetService.FulfillRequestAsync(request.Id, request.NewPassword, cancellationToken) switch {
FulfillPasswordResetRequestResult.REQUEST_NOT_FOUND => NotFound(),
FulfillPasswordResetRequestResult.USER_NOT_FOUND => NotFound(),
diff --git a/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs b/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs
index 6bc2fdc..c4f2ee5 100644
--- a/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs
+++ b/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs
@@ -21,8 +21,6 @@ public class CreateTokenRoute : RouteBaseSync.WithRequest<ApiAccessToken.ApiAcce
/// <returns></returns>
[ApiVersion(ApiSpecV1.VERSION_STRING)]
[HttpPost("~/v{version:apiVersion}/api-tokens/create")]
- [ProducesResponseType(200, Type = typeof(string))]
- [ProducesResponseType(404, Type = typeof(KnownProblemModel))]
public override ActionResult Handle(ApiAccessToken.ApiAccessTokenDto request) {
var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id);
if (user == default) {