-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMfaController.cs
More file actions
94 lines (78 loc) · 3.34 KB
/
MfaController.cs
File metadata and controls
94 lines (78 loc) · 3.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Backend.Data;
namespace Backend.Controllers;
[ApiController]
[Route("api/mfa")]
[Authorize]
public class MfaController(UserManager<ApplicationUser> userManager) : ControllerBase
{
/// <summary>
/// Returns whether MFA is currently enabled for the authenticated user.
/// </summary>
[HttpGet("status")]
public async Task<IActionResult> GetStatus()
{
var user = await userManager.GetUserAsync(User);
if (user is null) return Unauthorized();
return Ok(new { isMfaEnabled = await userManager.GetTwoFactorEnabledAsync(user) });
}
/// <summary>
/// Returns the TOTP shared key and a QR code URI for authenticator app setup.
/// The QR code URI follows the otpauth:// format compatible with Google Authenticator,
/// Authy, and other TOTP apps.
/// </summary>
[HttpGet("setup")]
public async Task<IActionResult> GetSetup()
{
var user = await userManager.GetUserAsync(User);
if (user is null) return Unauthorized();
// Only generate a new key if one doesn't already exist.
// Resetting on every request would invalidate an existing authenticator app.
var key = await userManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrWhiteSpace(key))
{
await userManager.ResetAuthenticatorKeyAsync(user);
key = await userManager.GetAuthenticatorKeyAsync(user);
}
if (string.IsNullOrWhiteSpace(key))
return Problem("Unable to generate authenticator key.");
var email = user.Email ?? user.UserName ?? "user";
const string issuer = "LunasProject";
// Format: otpauth://totp/{issuer}:{account}?secret={key}&issuer={issuer}&digits=6
var qrCodeUri = $"otpauth://totp/{Uri.EscapeDataString(issuer)}:{Uri.EscapeDataString(email)}" +
$"?secret={key}&issuer={Uri.EscapeDataString(issuer)}&digits=6";
return Ok(new { sharedKey = key, qrCodeUri });
}
/// <summary>
/// Verifies a TOTP code and enables MFA for the authenticated user.
/// </summary>
[HttpPost("enable")]
public async Task<IActionResult> Enable([FromBody] MfaCodeRequest request)
{
var user = await userManager.GetUserAsync(User);
if (user is null) return Unauthorized();
var isValid = await userManager.VerifyTwoFactorTokenAsync(
user,
userManager.Options.Tokens.AuthenticatorTokenProvider,
request.Code.Replace(" ", "").Replace("-", ""));
if (!isValid)
return BadRequest(new { message = "Invalid verification code. Please try again." });
await userManager.SetTwoFactorEnabledAsync(user, true);
return Ok(new { message = "MFA has been enabled." });
}
/// <summary>
/// Disables MFA for the authenticated user.
/// </summary>
[HttpPost("disable")]
public async Task<IActionResult> Disable()
{
var user = await userManager.GetUserAsync(User);
if (user is null) return Unauthorized();
await userManager.SetTwoFactorEnabledAsync(user, false);
await userManager.ResetAuthenticatorKeyAsync(user);
return Ok(new { message = "MFA has been disabled." });
}
}
public record MfaCodeRequest(string Code);