[backend/razor] Add TOTP 2FA support to OAuth page
This commit is contained in:
parent
ec6e334266
commit
5cf951e908
3 changed files with 155 additions and 35 deletions
|
@ -17,6 +17,63 @@
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@if (Model.TwoFactorFormData is { } data)
|
||||||
|
{
|
||||||
|
<form method="post">
|
||||||
|
<div class="margin-bottom-5px">
|
||||||
|
Feature flags:
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (data.SupportsHtmlFormatting)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="supportsHtmlFormatting" checked disabled/>
|
||||||
|
<input type="hidden" name="supportsHtmlFormatting" value="1"/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="supportsHtmlFormatting" disabled/>
|
||||||
|
}
|
||||||
|
<label for="supportsHtmlFormatting">This app supports HTML formatting</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (data.AutoDetectQuotes)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="autoDetectQuotes" checked disabled/>
|
||||||
|
<input type="hidden" name="autoDetectQuotes" value="1"/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="autoDetectQuotes" disabled/>
|
||||||
|
}
|
||||||
|
<label for="autoDetectQuotes">Automatically detect quotes</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (data.IsPleroma)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="isPleroma" checked disabled/>
|
||||||
|
<input type="hidden" name="isPleroma" value="1"/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="isPleroma" disabled/>
|
||||||
|
}
|
||||||
|
<label for="isPleroma">This app is intended for Pleroma or Akkoma</label>
|
||||||
|
</div>
|
||||||
|
<div class="margin-bottom-5px margin-top-10px">
|
||||||
|
Log in below to confirm this:
|
||||||
|
</div>
|
||||||
|
<input type="text" disabled value="@data.Username"
|
||||||
|
autocomplete="username"/>
|
||||||
|
<input type="hidden" name="username" value="@data.Username"/>
|
||||||
|
<input type="password" disabled value="@data.Password"/>
|
||||||
|
<input type="hidden" name="password" value="@data.Password"/>
|
||||||
|
<input type="text" inputmode="numeric" pattern="[0-9]{6}" autocomplete="one-time-code"
|
||||||
|
placeholder="TOTP" name="totp" required/>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="margin-bottom-5px">
|
<div class="margin-bottom-5px">
|
||||||
Feature flags:
|
Feature flags:
|
||||||
|
@ -44,25 +101,30 @@
|
||||||
<div class="margin-bottom-5px margin-top-5px">
|
<div class="margin-bottom-5px margin-top-5px">
|
||||||
Alternatively, sign in with to a different account below:
|
Alternatively, sign in with to a different account below:
|
||||||
</div>
|
</div>
|
||||||
<input type="text" placeholder="Username" name="username"/>
|
<input type="text" placeholder="Username" name="username" autocomplete="username"/>
|
||||||
<input type="password" placeholder="Password" name="password"/>
|
<input type="password" placeholder="Password" name="password" autocomplete="current-password"/>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="margin-bottom-5px margin-top-10px">
|
<div class="margin-bottom-5px margin-top-10px">
|
||||||
Log in below to confirm this:
|
Log in below to confirm this:
|
||||||
</div>
|
</div>
|
||||||
<input type="text" placeholder="Username" name="username" required/>
|
<input type="text" placeholder="Username" name="username" autocomplete="username" required/>
|
||||||
<input type="password" placeholder="Password" name="password" required/>
|
<input type="password" placeholder="Password" name="password" autocomplete="current-password"
|
||||||
|
required/>
|
||||||
}
|
}
|
||||||
|
<input type="text" autocomplete="one-time-code" placeholder="TOTP" name="totp" class="hidden-input"
|
||||||
|
tabindex="-1" aria-hidden="true"/>
|
||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else if (Model.Token.RedirectUri == "urn:ietf:wg:oauth:2.0:oob")
|
else if (Model.Token.RedirectUri == "urn:ietf:wg:oauth:2.0:oob")
|
||||||
{
|
{
|
||||||
<div>
|
<div>
|
||||||
Your code is: <pre>@Model.Token.Code</pre>
|
Your code is:
|
||||||
|
<pre>@Model.Token.Code</pre>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -3,6 +3,7 @@ using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
using Iceshrimp.Backend.Core.Helpers;
|
using Iceshrimp.Backend.Core.Helpers;
|
||||||
using Iceshrimp.Backend.Core.Middleware;
|
using Iceshrimp.Backend.Core.Middleware;
|
||||||
|
using Iceshrimp.Shared.Schemas.Web;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
@ -30,6 +31,8 @@ public class AuthorizeModel(DatabaseContext db) : PageModel
|
||||||
[MemberNotNull(nameof(Scopes))] set => Scopes = value.Split(' ').ToList();
|
[MemberNotNull(nameof(Scopes))] set => Scopes = value.Split(' ').ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LoginData? TwoFactorFormData = null;
|
||||||
|
|
||||||
public async Task OnGet()
|
public async Task OnGet()
|
||||||
{
|
{
|
||||||
if (ResponseType == null || ClientId == null || RedirectUri == null)
|
if (ResponseType == null || ClientId == null || RedirectUri == null)
|
||||||
|
@ -53,7 +56,7 @@ public class AuthorizeModel(DatabaseContext db) : PageModel
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnPost(
|
public async Task OnPost(
|
||||||
[FromForm] string? username, [FromForm] string? password, [FromForm] string? userId,
|
[FromForm] string? username, [FromForm] string? password, [FromForm] string? totp, [FromForm] string? userId,
|
||||||
[FromForm] bool supportsHtmlFormatting, [FromForm] bool autoDetectQuotes, [FromForm] bool isPleroma
|
[FromForm] bool supportsHtmlFormatting, [FromForm] bool autoDetectQuotes, [FromForm] bool isPleroma
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
@ -77,6 +80,25 @@ public class AuthorizeModel(DatabaseContext db) : PageModel
|
||||||
throw Forbidden();
|
throw Forbidden();
|
||||||
if (AuthHelpers.ComparePassword(password, userSettings.Password) == false)
|
if (AuthHelpers.ComparePassword(password, userSettings.Password) == false)
|
||||||
throw Forbidden();
|
throw Forbidden();
|
||||||
|
|
||||||
|
if (userSettings.TwoFactorEnabled)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(totp))
|
||||||
|
{
|
||||||
|
if (userSettings.TwoFactorSecret == null)
|
||||||
|
throw new Exception("2FA is enabled but secret is null");
|
||||||
|
if (!TotpHelper.Validate(userSettings.TwoFactorSecret, totp))
|
||||||
|
{
|
||||||
|
SetTwoFactorFormData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetTwoFactorFormData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = new OauthToken
|
var token = new OauthToken
|
||||||
|
@ -99,5 +121,30 @@ public class AuthorizeModel(DatabaseContext db) : PageModel
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
Token = token;
|
Token = token;
|
||||||
|
return;
|
||||||
|
|
||||||
|
void SetTwoFactorFormData()
|
||||||
|
{
|
||||||
|
TwoFactorFormData = new LoginData
|
||||||
|
{
|
||||||
|
Username = username,
|
||||||
|
Password = password,
|
||||||
|
AutoDetectQuotes = autoDetectQuotes,
|
||||||
|
SupportsHtmlFormatting = supportsHtmlFormatting,
|
||||||
|
IsPleroma = isPleroma
|
||||||
|
};
|
||||||
|
|
||||||
|
Response.Headers.CacheControl = "private, no-store, no-cache";
|
||||||
|
Response.Headers.Pragma = "no-cache";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoginData
|
||||||
|
{
|
||||||
|
public required string Username;
|
||||||
|
public required string Password;
|
||||||
|
public required bool SupportsHtmlFormatting;
|
||||||
|
public required bool AutoDetectQuotes;
|
||||||
|
public required bool IsPleroma;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,3 +13,14 @@
|
||||||
.margin-top-10px {
|
.margin-top-10px {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden-input {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
left: -10px;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue