aboutsummaryrefslogtreecommitdiff
path: root/BackEnd
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2022-04-20 22:10:48 +0800
committercrupest <crupest@outlook.com>2022-04-20 22:10:48 +0800
commitde2dc69400efb217513700b587d1571d31e53c7b (patch)
tree6424b78f5f4e7e7f381b9c6f16a6ee1e053fe1f1 /BackEnd
parent7187553a32af6a0011e5246080ee2edc2be8215e (diff)
downloadtimeline-de2dc69400efb217513700b587d1571d31e53c7b.tar.gz
timeline-de2dc69400efb217513700b587d1571d31e53c7b.tar.bz2
timeline-de2dc69400efb217513700b587d1571d31e53c7b.zip
...
Diffstat (limited to 'BackEnd')
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs9
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests2/RegisterCodeTest.cs60
-rw-r--r--BackEnd/Timeline/Controllers/V2/RegisterCodeController.cs80
-rw-r--r--BackEnd/Timeline/Models/Http/HttpRegisterCode.cs12
-rw-r--r--BackEnd/Timeline/Models/Http/HttpRegisterCodeRegisterRequest.cs22
-rw-r--r--BackEnd/Timeline/Services/User/UserServicesServiceCollectionExtensions.cs5
6 files changed, 187 insertions, 1 deletions
diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs
index de41181b..d30c807d 100644
--- a/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs
+++ b/BackEnd/Timeline.Tests/IntegratedTests2/IntegratedTestBase.cs
@@ -1,4 +1,5 @@
using System;
+using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.TestHost;
@@ -138,5 +139,13 @@ namespace Timeline.Tests.IntegratedTests2
{
return CreateClientWithToken(NormalUserToken);
}
+
+ public async Task TestOnlySelfAndAdminCanCall(HttpMethod httpMethod, string selfResourceUrl, string otherResourceUrl, object? body)
+ {
+ await DefaultClient.TestJsonSendAsync(httpMethod, selfResourceUrl, body, expectedStatusCode: HttpStatusCode.Unauthorized);
+ await UserClient.TestJsonSendAsync(httpMethod, selfResourceUrl, body);
+ await UserClient.TestJsonSendAsync(httpMethod, otherResourceUrl, body, expectedStatusCode: HttpStatusCode.Forbidden);
+ await AdminClient.TestJsonSendAsync(httpMethod, selfResourceUrl, body);
+ }
}
}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/RegisterCodeTest.cs b/BackEnd/Timeline.Tests/IntegratedTests2/RegisterCodeTest.cs
new file mode 100644
index 00000000..b840183e
--- /dev/null
+++ b/BackEnd/Timeline.Tests/IntegratedTests2/RegisterCodeTest.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Timeline.Models.Http;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Timeline.Tests.IntegratedTests2
+{
+ public class RegisterCodeTest : IntegratedTestBase
+ {
+ public RegisterCodeTest(ITestOutputHelper testOutput) : base(testOutput)
+ {
+ }
+
+ [Fact]
+ public async Task RegisterTest()
+ {
+ await DefaultClient.TestJsonSendAsync(HttpMethod.Post, "v2/register", new HttpRegisterCodeRegisterRequest
+ {
+ Username = "hello",
+ Password = "passwd",
+ }, expectedStatusCode: HttpStatusCode.UnprocessableEntity);
+
+ await DefaultClient.TestJsonSendAsync(HttpMethod.Post, "v2/register", new HttpRegisterCodeRegisterRequest
+ {
+ Username = "hello",
+ Password = "passwd",
+ RegisterCode = "invalidone"
+ }, expectedStatusCode: HttpStatusCode.UnprocessableEntity);
+
+ var b = await UserClient.TestJsonSendAsync<HttpRegisterCode>(HttpMethod.Get, "v2/users/user/registercode");
+ b.RegisterCode.Should().BeNull();
+
+ var a = await UserClient.TestJsonSendAsync<HttpRegisterCode>(HttpMethod.Post, "v2/users/user/renewregistercode");
+ a.RegisterCode.Should().NotBeNull();
+
+ var c = await UserClient.TestJsonSendAsync<HttpRegisterCode>(HttpMethod.Get, "v2/users/user/registercode");
+ c.RegisterCode.Should().NotBeNull().And.Be(a.RegisterCode);
+
+ var d = await DefaultClient.TestJsonSendAsync<HttpUser>(HttpMethod.Post, "v2/register", new HttpRegisterCodeRegisterRequest
+ {
+ Username = "hello",
+ Password = "passwd",
+ RegisterCode = a.RegisterCode!
+ });
+ d.Username.Should().Be("hello");
+ }
+
+ [Fact]
+ public async Task PermissionTest()
+ {
+ await TestOnlySelfAndAdminCanCall(HttpMethod.Get, "v2/users/user/registercode", "v2/users/admin/registercode", null);
+ await TestOnlySelfAndAdminCanCall(HttpMethod.Post, "v2/users/user/renewregistercode", "v2/users/admin/renewregistercode", null);
+ }
+ }
+}
+
diff --git a/BackEnd/Timeline/Controllers/V2/RegisterCodeController.cs b/BackEnd/Timeline/Controllers/V2/RegisterCodeController.cs
new file mode 100644
index 00000000..f3e51ab7
--- /dev/null
+++ b/BackEnd/Timeline/Controllers/V2/RegisterCodeController.cs
@@ -0,0 +1,80 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Timeline.Models.Http;
+using Timeline.Models.Validation;
+using Timeline.Services.User;
+using Timeline.Services.User.RegisterCode;
+
+namespace Timeline.Controllers.V2
+{
+ [ApiController]
+ public class RegisterCodeController : V2ControllerBase
+ {
+ private readonly IUserService _userService;
+ private readonly IRegisterCodeService _registerCodeService;
+
+ public RegisterCodeController(IUserService userService, IRegisterCodeService registerCodeService)
+ {
+ _userService = userService;
+ _registerCodeService = registerCodeService;
+ }
+
+ [HttpPost("v2/register")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
+ public async Task<ActionResult<HttpUser>> RegisterAsync([FromBody] HttpRegisterCodeRegisterRequest body)
+ {
+ try
+ {
+ var user = await _registerCodeService.RegisterUserWithCode(new CreateUserParams(body.Username, body.Password) { Nickname = body.Nickname }, body.RegisterCode);
+ return await MapAsync<HttpUser>(user);
+ }
+ catch (InvalidRegisterCodeException)
+ {
+ return UnprocessableEntity(new ErrorResponse(ErrorResponse.InvalidRequest, "Invalid register code."));
+ }
+ }
+
+ [HttpGet("v2/users/{username}/registercode")]
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
+ public async Task<ActionResult<HttpRegisterCode>> GetRegisterCodeAsync([FromRoute][Username] string username)
+ {
+ var userId = await _userService.GetUserIdByUsernameAsync(username);
+ if (!UserHasPermission(UserPermission.UserManagement) && userId != GetAuthUserId())
+ {
+ return Forbid();
+ }
+ var registerCode = await _registerCodeService.GetCurrentCodeAsync(userId);
+ return new HttpRegisterCode
+ {
+ RegisterCode = registerCode
+ };
+ }
+
+ [HttpPost("v2/users/{username}/renewregistercode")]
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
+ public async Task<ActionResult<HttpRegisterCode>> RenewRegisterCodeAsync([FromRoute][Username] string username)
+ {
+ var userId = await _userService.GetUserIdByUsernameAsync(username);
+ if (!UserHasPermission(UserPermission.UserManagement) && userId != GetAuthUserId())
+ {
+ return Forbid();
+ }
+ var registerCode = await _registerCodeService.CreateNewCodeAsync(userId);
+ return new HttpRegisterCode
+ {
+ RegisterCode = registerCode
+ };
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpRegisterCode.cs b/BackEnd/Timeline/Models/Http/HttpRegisterCode.cs
new file mode 100644
index 00000000..544f0d92
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpRegisterCode.cs
@@ -0,0 +1,12 @@
+using System;
+namespace Timeline.Models.Http
+{
+ public class HttpRegisterCode
+ {
+ /// <summary>
+ /// Register code. May be null.
+ /// </summary>
+ public string? RegisterCode { get; set; }
+ }
+}
+
diff --git a/BackEnd/Timeline/Models/Http/HttpRegisterCodeRegisterRequest.cs b/BackEnd/Timeline/Models/Http/HttpRegisterCodeRegisterRequest.cs
new file mode 100644
index 00000000..8a1b64a6
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpRegisterCodeRegisterRequest.cs
@@ -0,0 +1,22 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using Timeline.Models.Validation;
+
+namespace Timeline.Models.Http
+{
+ public class HttpRegisterCodeRegisterRequest
+ {
+ [Required, Username]
+ public string Username { get; set; } = default!;
+
+ [Required, MinLength(1)]
+ public string Password { get; set; } = default!;
+
+ [Nickname]
+ public string? Nickname { get; set; }
+
+ [Required]
+ public string RegisterCode { get; set; } = default!;
+ }
+}
+
diff --git a/BackEnd/Timeline/Services/User/UserServicesServiceCollectionExtensions.cs b/BackEnd/Timeline/Services/User/UserServicesServiceCollectionExtensions.cs
index 76d101a0..c593b11e 100644
--- a/BackEnd/Timeline/Services/User/UserServicesServiceCollectionExtensions.cs
+++ b/BackEnd/Timeline/Services/User/UserServicesServiceCollectionExtensions.cs
@@ -1,7 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Timeline.Services.User.Avatar;
-
+using Timeline.Services.User.RegisterCode;
+
namespace Timeline.Services.User
{
public static class UserServicesServiceCollectionExtensions
@@ -15,6 +16,8 @@ namespace Timeline.Services.User
services.AddUserAvatarServices();
+ services.TryAddScoped<IRegisterCodeService, RegisterCodeService>();
+
return services;
}
}