Add /api/auth/{register,login,logout,me} with first-user-is-admin rule
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace backend.tests;
|
||||
|
||||
public class AuthTests(ApiFactory factory) : IClassFixture<ApiFactory>
|
||||
{
|
||||
private readonly ApiFactory _factory = factory;
|
||||
|
||||
[Fact]
|
||||
public async Task Register_then_login_then_me_returns_user()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var reg = await client.PostAsJsonAsync("/api/auth/register",
|
||||
new { email = "first@example.com", password = "Passw0rd!" });
|
||||
reg.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var login = await client.PostAsJsonAsync("/api/auth/login",
|
||||
new { email = "first@example.com", password = "Passw0rd!" });
|
||||
login.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var me = await client.GetAsync("/api/auth/me");
|
||||
me.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
(await me.Content.ReadAsStringAsync()).Should().Contain("first@example.com");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Me_without_login_returns_401()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
var res = await client.GetAsync("/api/auth/me");
|
||||
res.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task First_user_is_admin()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
await client.PostAsJsonAsync("/api/auth/register",
|
||||
new { email = "first@example.com", password = "Passw0rd!" });
|
||||
await client.PostAsJsonAsync("/api/auth/login",
|
||||
new { email = "first@example.com", password = "Passw0rd!" });
|
||||
|
||||
var me = await client.GetAsync("/api/auth/me");
|
||||
(await me.Content.ReadAsStringAsync()).Should().Contain("\"isAdmin\":true");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace backend.Auth;
|
||||
|
||||
public record RegisterRequest(string Email, string Password);
|
||||
public record LoginRequest(string Email, string Password);
|
||||
|
||||
public static class AuthEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapAuthEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var grp = app.MapGroup("/api/auth");
|
||||
|
||||
grp.MapPost("/register", async (
|
||||
[FromBody] RegisterRequest req,
|
||||
UserManager<ApplicationUser> users,
|
||||
RoleManager<IdentityRole<Guid>> roles) =>
|
||||
{
|
||||
var user = new ApplicationUser { UserName = req.Email, Email = req.Email };
|
||||
var create = await users.CreateAsync(user, req.Password);
|
||||
if (!create.Succeeded)
|
||||
return Results.BadRequest(new { errors = create.Errors.Select(e => e.Description) });
|
||||
|
||||
var anyAdmin = await users.GetUsersInRoleAsync(Roles.Admin);
|
||||
var role = anyAdmin.Count == 0 ? Roles.Admin : Roles.User;
|
||||
await users.AddToRoleAsync(user, role);
|
||||
return Results.Ok(new { id = user.Id, email = user.Email, role });
|
||||
});
|
||||
|
||||
grp.MapPost("/login", async (
|
||||
[FromBody] LoginRequest req,
|
||||
SignInManager<ApplicationUser> signIn) =>
|
||||
{
|
||||
var res = await signIn.PasswordSignInAsync(req.Email, req.Password,
|
||||
isPersistent: true, lockoutOnFailure: false);
|
||||
return res.Succeeded ? Results.Ok() : Results.Unauthorized();
|
||||
});
|
||||
|
||||
grp.MapPost("/logout", async (SignInManager<ApplicationUser> signIn) =>
|
||||
{
|
||||
await signIn.SignOutAsync();
|
||||
return Results.Ok();
|
||||
}).RequireAuthorization();
|
||||
|
||||
grp.MapGet("/me", async (
|
||||
UserManager<ApplicationUser> users,
|
||||
HttpContext http) =>
|
||||
{
|
||||
var user = await users.GetUserAsync(http.User);
|
||||
if (user is null) return Results.Unauthorized();
|
||||
var rolesList = await users.GetRolesAsync(user);
|
||||
return Results.Ok(new
|
||||
{
|
||||
id = user.Id,
|
||||
email = user.Email,
|
||||
isAdmin = rolesList.Contains(Roles.Admin),
|
||||
});
|
||||
}).RequireAuthorization();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ using (var scope = app.Services.CreateScope())
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapAuthEndpoints();
|
||||
|
||||
app.MapGet("/health", () => Results.Ok(new { ok = true }));
|
||||
|
||||
app.Run();
|
||||
|
||||
Reference in New Issue
Block a user