106 lines
3.6 KiB
C#
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");
|
|
}
|
|
}
|
|
}
|