Add password hashing, user settings with 2FA, and project image lightbox.

Hardens auth with PBKDF2, lockouts, local QR setup, and safer embeds while moving account security under the username menu.
This commit is contained in:
Atakan Doğan Özban
2026-07-17 18:49:34 +02:00
parent 8ec6e3fd50
commit e97a71e5b2
18 changed files with 1253 additions and 62 deletions
+116
View File
@@ -0,0 +1,116 @@
@{
ViewBag.Title = "User Settings";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
var enabled = ViewBag.TwoFactorEnabled == true;
}
<h2 class="text-white mt-3 mb-3">User Settings</h2>
<p class="text-white-50">Account: <strong class="text-white">@ViewBag.Username</strong></p>
<div class="row">
<div class="col-lg-6 mb-4">
<div class="bg-dark border border-secondary rounded p-4 text-white h-100">
<h4 class="mb-3">Change password</h4>
@if (TempData["pwdOk"] != null)
{
<div class="alert alert-success py-2">@TempData["pwdOk"]</div>
}
@if (TempData["pwdError"] != null)
{
<div class="alert alert-danger py-2">@TempData["pwdError"]</div>
}
@using (Html.BeginForm("ChangePassword", "admin", FormMethod.Post))
{
@Html.AntiForgeryToken()
<div class="mb-3">
<label class="form-label">Current password</label>
<input type="password" name="currentPassword" class="form-control bg-dark text-white" autocomplete="current-password" required />
</div>
<div class="mb-3">
<label class="form-label">New password</label>
<input type="password" name="newPassword" class="form-control bg-dark text-white" autocomplete="new-password" minlength="8" required />
<small class="text-muted">At least 8 characters.</small>
</div>
<div class="mb-3">
<label class="form-label">Confirm new password</label>
<input type="password" name="confirmPassword" class="form-control bg-dark text-white" autocomplete="new-password" minlength="8" required />
</div>
<button type="submit" class="btn btn-primary">Update password</button>
}
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="bg-dark border border-secondary rounded p-4 text-white h-100">
<h4 class="mb-3">
Two-factor authentication
@if (enabled)
{
<span class="badge text-bg-success ms-2">Enabled</span>
}
else
{
<span class="badge text-bg-secondary ms-2">Disabled</span>
}
</h4>
@if (TempData["tfaOk"] != null)
{
<div class="alert alert-success py-2">@TempData["tfaOk"]</div>
}
@if (TempData["tfaError"] != null)
{
<div class="alert alert-danger py-2">@TempData["tfaError"]</div>
}
@if (enabled)
{
<p class="text-white-50">2FA is active. Enter a current authenticator code to disable it.</p>
using (Html.BeginForm("DisableTwoFactor", "admin", FormMethod.Post))
{
@Html.AntiForgeryToken()
<div class="mb-3">
<label class="form-label">Authenticator code</label>
<input type="text" name="code" class="form-control bg-dark text-white" maxlength="6" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" required />
</div>
<button type="submit" class="btn btn-danger" onclick="return confirm('Disable two-factor authentication?');">Disable 2FA</button>
}
}
else
{
<ol class="text-white-50">
<li>Open your authenticator app and scan the QR code below.</li>
<li>Or enter this secret manually: <code class="text-white">@ViewBag.SetupSecret</code></li>
<li>Enter the 6-digit code to confirm and enable 2FA.</li>
</ol>
<div class="text-center my-3">
<div id="totpQr" class="d-inline-block bg-white p-2 rounded"></div>
</div>
using (Html.BeginForm("EnableTwoFactor", "admin", FormMethod.Post))
{
@Html.AntiForgeryToken()
<div class="mb-3">
<label class="form-label">Authenticator code</label>
<input type="text" name="code" class="form-control bg-dark text-white" maxlength="6" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" required />
</div>
<button type="submit" class="btn btn-success">Enable 2FA</button>
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script>
(function () {
var uri = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject((string)ViewBag.OtpAuthUri));
var el = document.getElementById('totpQr');
if (el && uri && typeof QRCode !== 'undefined') {
new QRCode(el, { text: uri, width: 200, height: 200 });
}
})();
</script>
}
</div>
</div>
</div>