diff --git a/API.IntegrationTests/Tests/AccountAuthenticatedTests.cs b/API.IntegrationTests/Tests/AccountAuthenticatedTests.cs index 04a66b76..0d19893b 100644 --- a/API.IntegrationTests/Tests/AccountAuthenticatedTests.cs +++ b/API.IntegrationTests/Tests/AccountAuthenticatedTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Text.Json; using OpenShock.API.IntegrationTests.Helpers; namespace OpenShock.API.IntegrationTests.Tests; @@ -8,6 +9,44 @@ public sealed class AccountAuthenticatedTests [ClassDataSource(Shared = SharedType.PerTestSession)] public required WebApplicationFactory WebApplicationFactory { get; init; } + // --- Get Self --- + + [Test] + public async Task GetSelf_ReturnsCurrentUserInfo_WithoutLegacyWrapper() + { + var user = await TestHelper.CreateAndLoginUser(WebApplicationFactory, "accself", "accself@test.org", "SecurePassword123#"); + using var client = TestHelper.CreateAuthenticatedClient(WebApplicationFactory, user.SessionToken); + + var response = await client.GetAsync("/1/account"); + + await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK); + + using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + var root = doc.RootElement; + await Assert.That(root.TryGetProperty("data", out _)).IsFalse(); + await Assert.That(root.GetProperty("name").GetString()).IsEqualTo("accself"); + await Assert.That(root.GetProperty("email").GetString()).IsEqualTo("accself@test.org"); + await Assert.That(root.GetProperty("hasPassword").GetBoolean()).IsTrue(); + await Assert.That(root.TryGetProperty("id", out _)).IsTrue(); + await Assert.That(root.TryGetProperty("image", out _)).IsTrue(); + await Assert.That(root.TryGetProperty("roles", out _)).IsTrue(); + } + + [Test] + public async Task GetSelf_WithApiToken_ReturnsUserInfo() + { + var userId = await TestHelper.CreateUserInDb(WebApplicationFactory, "accselfapi", "accselfapi@test.org", "SecurePassword123#"); + var (_, rawToken) = await TestHelper.CreateApiTokenInDb(WebApplicationFactory, userId, "AccountSelfApiToken"); + using var client = TestHelper.CreateApiTokenClient(WebApplicationFactory, rawToken); + + var response = await client.GetAsync("/1/account"); + + await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK); + + using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + await Assert.That(doc.RootElement.GetProperty("name").GetString()).IsEqualTo("accselfapi"); + } + // --- Change Password --- [Test] diff --git a/API.IntegrationTests/Tests/AuthorizationTests.cs b/API.IntegrationTests/Tests/AuthorizationTests.cs index 1dfcd161..c3785ba9 100644 --- a/API.IntegrationTests/Tests/AuthorizationTests.cs +++ b/API.IntegrationTests/Tests/AuthorizationTests.cs @@ -25,6 +25,7 @@ public sealed class AuthorizationTests [Arguments("/1/tokens")] [Arguments("/1/sessions/self")] [Arguments("/1/users/self")] + [Arguments("/1/account")] public async Task ProtectedEndpoint_NoAuth_Returns401(string url) { using var client = WebApplicationFactory.CreateClient(); diff --git a/API/Controller/Account/Authenticated/GetSelf.cs b/API/Controller/Account/Authenticated/GetSelf.cs new file mode 100644 index 00000000..960bc8bd --- /dev/null +++ b/API/Controller/Account/Authenticated/GetSelf.cs @@ -0,0 +1,32 @@ +using System.Net.Mime; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using OpenShock.API.Models.Response; +using OpenShock.Common.Authentication; +using OpenShock.Common.Extensions; +using OpenShock.Common.OpenShockDb; + +namespace OpenShock.API.Controller.Account.Authenticated; + +public sealed partial class AuthenticatedAccountController +{ + /// + /// Get the current user's account information. + /// + /// The account information was successfully retrieved. + [HttpGet] + [Authorize(AuthenticationSchemes = OpenShockAuthSchemes.UserSessionApiTokenCombo)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + public UserSelfResponse GetSelf() => CreateSelfResponse(CurrentUser); + + internal static UserSelfResponse CreateSelfResponse(OpenShock.Common.OpenShockDb.User user) => new() + { + Id = user.Id, + Name = user.Name, + Email = user.Email, + Image = user.GetImageUrl(), + Roles = user.Roles, + Rank = user.Roles.Count > 0 ? user.Roles.Max().ToString() : "User", + HasPassword = !string.IsNullOrEmpty(user.PasswordHash) + }; +} diff --git a/API/Controller/Users/GetSelf.cs b/API/Controller/Users/GetSelf.cs index b3a3ced8..39715181 100644 --- a/API/Controller/Users/GetSelf.cs +++ b/API/Controller/Users/GetSelf.cs @@ -1,43 +1,22 @@ using System.Net.Mime; using Microsoft.AspNetCore.Mvc; -using OpenShock.Common.Extensions; +using OpenShock.API.Controller.Account.Authenticated; +using OpenShock.API.Models.Response; using OpenShock.Common.Models; -using OpenShock.Common.OpenShockDb; namespace OpenShock.API.Controller.Users; public sealed partial class UsersController { /// - /// Get the current user's information. + /// Get the current user's information. Deprecated: use GET /1/account instead. /// /// The user's information was successfully retrieved. + [Obsolete("Use GET /1/account instead.")] [HttpGet("self")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public IActionResult GetSelf() { - return LegacyDataOk( - new UserSelfResponse - { - Id = CurrentUser.Id, - Name = CurrentUser.Name, - Email = CurrentUser.Email, - Image = CurrentUser.GetImageUrl(), - Roles = CurrentUser.Roles, - Rank = CurrentUser.Roles.Count > 0 ? CurrentUser.Roles.Max().ToString() : "User", - HasPassword = !string.IsNullOrEmpty(CurrentUser.PasswordHash) - } - ); + return LegacyDataOk(AuthenticatedAccountController.CreateSelfResponse(CurrentUser)); } - - public sealed class UserSelfResponse - { - public required Guid Id { get; init; } - public required string Name { get; init; } - public required string Email { get; init; } - public required Uri Image { get; init; } - public required List Roles { get; init; } - public required string Rank { get; init; } - public required bool HasPassword { get; init; } - } -} \ No newline at end of file +} diff --git a/API/Models/Response/UserSelfResponse.cs b/API/Models/Response/UserSelfResponse.cs new file mode 100644 index 00000000..c3a6091b --- /dev/null +++ b/API/Models/Response/UserSelfResponse.cs @@ -0,0 +1,14 @@ +using OpenShock.Common.OpenShockDb; + +namespace OpenShock.API.Models.Response; + +public sealed class UserSelfResponse +{ + public required Guid Id { get; init; } + public required string Name { get; init; } + public required string Email { get; init; } + public required Uri Image { get; init; } + public required List Roles { get; init; } + public required string Rank { get; init; } + public required bool HasPassword { get; init; } +}