219 lines
7.7 KiB
C#
219 lines
7.7 KiB
C#
using System.Security.Claims;
|
|
using AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.AddEmailIdentity;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.ChangePassword;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.DisableMfa;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.EnableTotp;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.IssueTokenPair;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Login;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Logout;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.LogoutAll;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Refresh;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Register;
|
|
using AMREZ.EOP.Contracts.DTOs.Authentications.VerifyEmail;
|
|
using AMREZ.EOP.Domain.Shared.Contracts;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace AMREZ.EOP.API.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
public class AuthenticationController : ControllerBase
|
|
{
|
|
private readonly ILoginUseCase _login;
|
|
private readonly IRegisterUseCase _register;
|
|
private readonly IChangePasswordUseCase _changePassword;
|
|
|
|
private readonly IAddEmailIdentityUseCase _addEmail;
|
|
private readonly IVerifyEmailUseCase _verifyEmail;
|
|
|
|
private readonly IEnableTotpUseCase _enableTotp;
|
|
private readonly IDisableMfaUseCase _disableMfa;
|
|
|
|
private readonly ILogoutUseCase _logout;
|
|
private readonly ILogoutAllUseCase _logoutAll;
|
|
|
|
private readonly IIssueTokenPairUseCase _issueTokens;
|
|
private readonly IRefreshUseCase _refresh;
|
|
|
|
public AuthenticationController(
|
|
ILoginUseCase login,
|
|
IRegisterUseCase register,
|
|
IChangePasswordUseCase changePassword,
|
|
IAddEmailIdentityUseCase addEmail,
|
|
IVerifyEmailUseCase verifyEmail,
|
|
IEnableTotpUseCase enableTotp,
|
|
IDisableMfaUseCase disableMfa,
|
|
ILogoutUseCase logout,
|
|
ILogoutAllUseCase logoutAll,
|
|
IIssueTokenPairUseCase issueTokens,
|
|
IRefreshUseCase refresh)
|
|
{
|
|
_login = login;
|
|
_register = register;
|
|
_changePassword = changePassword;
|
|
_addEmail = addEmail;
|
|
_verifyEmail = verifyEmail;
|
|
_enableTotp = enableTotp;
|
|
_disableMfa = disableMfa;
|
|
_logout = logout;
|
|
_logoutAll = logoutAll;
|
|
_issueTokens = issueTokens;
|
|
_refresh = refresh;
|
|
}
|
|
|
|
[HttpPost("login")]
|
|
public async Task<IActionResult> PostLogin([FromBody] LoginRequest body, CancellationToken ct)
|
|
{
|
|
var res = await _login.ExecuteAsync(body, ct);
|
|
if (res is null) return Unauthorized(new { message = "Invalid credentials" });
|
|
|
|
var claims = new List<Claim>
|
|
{
|
|
new(ClaimTypes.NameIdentifier, res.UserId.ToString()),
|
|
new(ClaimTypes.Name, res.Email),
|
|
new(ClaimTypes.Email, res.Email),
|
|
new("tenant", res.TenantId)
|
|
};
|
|
|
|
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, AuthPolicies.Scheme));
|
|
await HttpContext.SignInAsync(AuthPolicies.Scheme, principal);
|
|
|
|
var tokenPair = await _issueTokens.ExecuteAsync(new IssueTokenPairRequest()
|
|
{
|
|
UserId = res.UserId,
|
|
Tenant = res.TenantId,
|
|
Email = res.Email
|
|
}, ct);
|
|
|
|
if (!string.IsNullOrWhiteSpace(tokenPair.RefreshToken))
|
|
{
|
|
Response.Cookies.Append(
|
|
"refresh_token",
|
|
tokenPair.RefreshToken!,
|
|
new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
Secure = true,
|
|
SameSite = SameSiteMode.Strict,
|
|
Expires = tokenPair.RefreshExpiresAt?.UtcDateTime
|
|
});
|
|
}
|
|
|
|
return Ok(new
|
|
{
|
|
user = res,
|
|
access_token = tokenPair.AccessToken,
|
|
token_type = "Bearer",
|
|
expires_at = tokenPair.AccessExpiresAt
|
|
});
|
|
}
|
|
|
|
[HttpPost("refresh")]
|
|
public async Task<IActionResult> Refresh([FromBody] RefreshRequest body, CancellationToken ct)
|
|
{
|
|
var raw = string.IsNullOrWhiteSpace(body.RefreshToken)
|
|
? Request.Cookies["refresh_token"]
|
|
: body.RefreshToken;
|
|
|
|
if (string.IsNullOrWhiteSpace(raw))
|
|
return Unauthorized(new { message = "Missing refresh token" });
|
|
|
|
var res = await _refresh.ExecuteAsync(body, ct);
|
|
if (res is null) return Unauthorized(new { message = "Invalid/expired refresh token" });
|
|
|
|
if (!string.IsNullOrWhiteSpace(res.RefreshToken))
|
|
{
|
|
Response.Cookies.Append(
|
|
"refresh_token",
|
|
res.RefreshToken!,
|
|
new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
Secure = true,
|
|
SameSite = SameSiteMode.Strict,
|
|
Expires = res.RefreshExpiresAt?.UtcDateTime
|
|
});
|
|
}
|
|
|
|
return Ok(new
|
|
{
|
|
access_token = res.AccessToken,
|
|
token_type = "Bearer",
|
|
expires_at = res.AccessExpiresAt
|
|
});
|
|
}
|
|
|
|
[HttpPost("register")]
|
|
public async Task<IActionResult> Register([FromBody] RegisterRequest body, CancellationToken ct)
|
|
{
|
|
var res = await _register.ExecuteAsync(body, ct);
|
|
if (res is null)
|
|
return Conflict(new { message = "Email already exists or tenant not found" });
|
|
|
|
return Created($"/api/authentication/users/{res.UserId}", res);
|
|
}
|
|
|
|
[HttpPost("change-password")]
|
|
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest body, CancellationToken ct)
|
|
{
|
|
var ok = await _changePassword.ExecuteAsync(body, ct);
|
|
if (!ok) return BadRequest(new { message = "Change password failed" });
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpPost("logout")]
|
|
public async Task<IActionResult> Logout()
|
|
{
|
|
await HttpContext.SignOutAsync(AuthPolicies.Scheme);
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpPost("email")]
|
|
public async Task<IActionResult> AddEmail([FromBody] AddEmailIdentityRequest body, CancellationToken ct)
|
|
{
|
|
var ok = await _addEmail.ExecuteAsync(body, ct);
|
|
if (!ok) return BadRequest(new { message = "Cannot add email identity" });
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpPost("email/verify")]
|
|
public async Task<IActionResult> VerifyEmail([FromBody] VerifyEmailRequest body, CancellationToken ct)
|
|
{
|
|
var ok = await _verifyEmail.ExecuteAsync(body, ct);
|
|
if (!ok) return BadRequest(new { message = "Verify email failed" });
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpPost("totp/enable")]
|
|
public async Task<IActionResult> EnableTotp([FromBody] EnableTotpRequest body, CancellationToken ct)
|
|
{
|
|
var res = await _enableTotp.ExecuteAsync(body, ct);
|
|
if (res is null) return BadRequest(new { message = "Enable TOTP failed" });
|
|
return Ok(res);
|
|
}
|
|
|
|
[HttpPost("disable")]
|
|
public async Task<IActionResult> DisableMfa([FromBody] DisableMfaRequest body, CancellationToken ct)
|
|
{
|
|
var ok = await _disableMfa.ExecuteAsync(body, ct);
|
|
if (!ok) return BadRequest(new { message = "Disable MFA failed" });
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpPost("revoke")]
|
|
public async Task<IActionResult> Revoke([FromBody] LogoutRequest body, CancellationToken ct)
|
|
{
|
|
var ok = await _logout.ExecuteAsync(body, ct);
|
|
if (!ok) return NotFound(new { message = "Session not found" });
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpPost("revoke-all")]
|
|
public async Task<IActionResult> RevokeAll([FromBody] LogoutAllRequest body, CancellationToken ct)
|
|
{
|
|
var n = await _logoutAll.ExecuteAsync(body, ct);
|
|
return Ok(new { revoked = n });
|
|
}
|
|
} |