diff options
author | crupest <crupest@outlook.com> | 2022-04-20 22:10:48 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2022-04-20 22:10:48 +0800 |
commit | de2dc69400efb217513700b587d1571d31e53c7b (patch) | |
tree | 6424b78f5f4e7e7f381b9c6f16a6ee1e053fe1f1 | |
parent | 7187553a32af6a0011e5246080ee2edc2be8215e (diff) | |
download | timeline-de2dc69400efb217513700b587d1571d31e53c7b.tar.gz timeline-de2dc69400efb217513700b587d1571d31e53c7b.tar.bz2 timeline-de2dc69400efb217513700b587d1571d31e53c7b.zip |
...
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;
}
}
|