2026-04-11 21:36:05 +02:00

106 lines
3.6 KiB
C#

using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using OnlyPrompt.Backend.ApiModels.Auth;
using OnlyPrompt.Backend.Database;
using OnlyPrompt.Backend.Database.Models;
using OnlyPrompt.Backend.Services.Jwt;
using OnlyPrompt.Backend.Utils;
namespace OnlyPrompt.Backend.Controllers
{
[ApiController]
[Route("api/v1/auth")]
public class AuthController : BaseController
{
private static readonly CookieOptions AuthCookieOptions = new CookieOptions { Secure = true, HttpOnly = true, IsEssential = true };
private readonly IPasswordHasher<UserModel> _passwordHasher;
private readonly ITokenService _jwtService;
private readonly ILogger<AuthController> _logger;
private readonly IMapper _mapper;
public AuthController(OnlyPromptContext db, IPasswordHasher<UserModel> passwordHasher, IMapper mapper, ILogger<AuthController> logger, ITokenService jwtService) : base(db)
{
_passwordHasher=passwordHasher;
_mapper=mapper;
_logger=logger;
_jwtService=jwtService;
}
[AllowAnonymous]
[HttpPost("login")]
public async Task<Results<RedirectHttpResult, BadRequest<string>, NotFound<string>>> LoginAsync([FromBody] ApiLoginRequest request)
{
var user = await FindUserAsync(request.UserNameOrEmail);
if (user is null)
return TypedResults.NotFound("User not found");
var verificationResult = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, request.Password);
if (verificationResult == PasswordVerificationResult.Failed)
return TypedResults.NotFound("User not found"); // Don't reveal that the user exists
if (user.IsLockoutEnabled)
return TypedResults.BadRequest("User is locked out"); // Don't reveal that the user exists
var token = _jwtService.BuildToken(user, out var validUntil);
this.Response.Cookies.Append("jwt", token, AuthCookieOptions.Copy(c => c.Expires = validUntil));
return TypedResults.Redirect("feed");
}
[AllowAnonymous]
[HttpPost("register")]
public async Task<Results<RedirectHttpResult, ValidationProblem, Ok<ApiUser>>> RegisterAsync([FromBody] ApiRegisterRequest request)
{
var existingUser = await FindUserAsync(request.UserName, request.Email);
if (existingUser is not null)
{
var errors = new Dictionary<string, string[]>();
if (existingUser.UserName == request.UserName)
errors.Add(nameof(request.UserName), ["Username is already taken"]);
if (existingUser.Email == request.Email)
errors.Add(nameof(request.Email), ["Email is already registered"]);
return TypedResults.ValidationProblem(errors);
}
var id = Guid.NewGuid();
var slug = await SlugHelper.GenerateUniqueSlug(request.UserName, s => _db.UserProfiles.AnyAsync(up => up.Slug == s), 32);
var avatarUrl = $"https://api.dicebear.com/9.x/bottts/svg?seed={id}";
var newUser = new UserModel
{
Id = id,
Profile = new UserProfileModel
{
AvatarUrl = avatarUrl,
DisplayName = request.DisplayName,
Slug = slug,
},
Roles = [ModelConstants.UserRole],
PasswordHash = null,
UserName = request.UserName,
Email = request.Email,
IsLockoutEnabled = false,
};
newUser.PasswordHash = _passwordHasher.HashPassword(newUser, request.Password);
_db.Users.Add(newUser);
await _db.SaveChangesAsync();
return TypedResults.Ok(_mapper.Map<ApiUser>(newUser));
}
[HttpPost("logout")]
public RedirectHttpResult Logout()
{
this.Response.Cookies.Delete("jwt", AuthCookieOptions);
return TypedResults.Redirect("login");
}
}
}