Added re-authentication modal

This commit is contained in:
2025-11-28 22:27:06 +01:00
parent 464e5e8603
commit bc3e678e25
8 changed files with 189 additions and 45 deletions

View File

@@ -166,6 +166,7 @@ public class HomeController : Controller
return RedirectToAction("Index", "Home"); return RedirectToAction("Index", "Home");
} }
Response.StatusCode = 500;
switch (authenticationResult.AuthenticationState) switch (authenticationResult.AuthenticationState)
{ {
case UserNotAuthenticatedReason.InvalidCredentials: case UserNotAuthenticatedReason.InvalidCredentials:
@@ -198,4 +199,18 @@ public class HomeController : Controller
{ {
return View(); return View();
} }
[Authorize]
[HttpGet("RemainingTime")]
public async Task<IActionResult> GetRemainingSessionTime()
{
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (!result.Succeeded || result.Properties?.ExpiresUtc == null)
return Json(new { remainingMinutes = 0 });
var remaining = result.Properties.ExpiresUtc.Value - DateTimeOffset.UtcNow;
return Json(new { remainingMinutes = (int) Math.Ceiling(remaining.TotalMinutes) });
}
} }

View File

@@ -19,12 +19,6 @@
<data name="Login" xml:space="preserve"> <data name="Login" xml:space="preserve">
<value>Anmelden</value> <value>Anmelden</value>
</data> </data>
<data name="Username" xml:space="preserve">
<value>Benutzername</value>
</data>
<data name="Password" xml:space="preserve">
<value>Passwort</value>
</data>
<data name="Invalid login credentials" xml:space="preserve"> <data name="Invalid login credentials" xml:space="preserve">
<value>Ungültige Anmeldedaten</value> <value>Ungültige Anmeldedaten</value>
</data> </data>

View File

@@ -61,4 +61,28 @@
<data name="Admin settings" xml:space="preserve"> <data name="Admin settings" xml:space="preserve">
<value>Administration</value> <value>Administration</value>
</data> </data>
<data name="Extend session duration" xml:space="preserve">
<value>Sitzung verlängern</value>
</data>
<data name="The session expires soon." xml:space="preserve">
<value>Die Sitzung läuft bald ab.</value>
</data>
<data name="Please authenticate to continue without losing data." xml:space="preserve">
<value>Bitte authentifizieren Sie sich, um fortzufahren, ohne Daten zu verlieren.</value>
</data>
<data name="Re-authenticate" xml:space="preserve">
<value>Authentifizieren</value>
</data>
<data name="Invalid login credentials" xml:space="preserve">
<value>Ungültige Anmeldedaten</value>
</data>
<data name="Your account has been locked. Wait a few minutes or ask an administrator to unlock you" xml:space="preserve">
<value>Ihr Konto wurde gesperrt. Warten Sie einige Minuten oder bitten Sie einen Administrator, die Sperre aufzuheben.</value>
</data>
<data name="You are not authorized for login. Ask an administrator to authorize you." xml:space="preserve">
<value>Sie sind nicht zur Anmeldung berechtigt. Bitten Sie einen Administrator, Ihnen die Berechtigung zu erteilen.</value>
</data>
<data name="Hell froze over. Make a screenshot and send it to an administrator." xml:space="preserve">
<value>Die Hölle ist zugefroren. Machen Sie einen Screenshot und senden Sie ihn an einen Administrator.</value>
</data>
</root> </root>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, ...</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, ...</value>
</resheader>
<data name="Login" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="Username" xml:space="preserve">
<value>Benutzername</value>
</data>
<data name="Password" xml:space="preserve">
<value>Passwort</value>
</data>
</root>

View File

@@ -15,17 +15,5 @@
</div> </div>
} }
} }
<form method="post" action="/Home/Login" class="mt-4" style="max-width: 400px; margin: auto;"> <partial name="_Login" />
<div class="form-group mb-3">
<label for="username" class="form-label">@T["Username"]</label>
<input autofocus type="text" class="form-control" id="username" name="username" autocomplete="username" required>
</div>
<div class="form-group mb-3">
<label for="password" class="form-label">@T["Password"]</label>
<input type="password" class="form-control" id="password" name="password" autocomplete="current-password" required>
</div>
<button type="submit" class="btn btn-primary w-100">@T["Login"]</button>
</form>
</div> </div>

View File

@@ -139,6 +139,88 @@
&copy; 2025 - Berufsschule_HAM &copy; 2025 - Berufsschule_HAM
</div> </div>
</footer> </footer>
<!-- Re-authentication Modal -->
<div class="modal fade" id="reAuthModal" tabindex="-1" aria-labelledby="reAuthModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-info text-dark">
<h3 class="modal-title" id="detailModalLabel">@T["Extend session duration"]</h3>
<button type="button" class="btn-close btn-close-white" style="filter: invert(0);" aria-label="Close" data-bs-dismiss="modal"></button>
</div>
<form id="reAuthForm">
<div class="modal-body">
<p id="reAuthModalLabel" class="text-center">@T["The session expires soon."]<br/>@T["Please authenticate to continue without losing data."]</p>
<div class="mb-3">
<label for="reAuthUsername" class="form-label">@T["Username"]</label>
<input type="text" class="form-control" id="reAuthUsername" name="Username"
value="@User.Identity?.Name" readonly />
</div>
<div class="mb-3">
<label for="reAuthPassword" class="form-label">@T["Password"]</label>
<input type="password" class="form-control" id="reAuthPassword" name="Password" required />
</div>
<div class="alert alert-danger d-none" id="reAuthError"></div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">@T["Re-authenticate"]</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Flag so modal only opens once
window.reAuthModalOpen = false;
function openReAuthModal() {
window.reAuthModalOpen = true;
const reAuthModal = new bootstrap.Modal(document.getElementById('reAuthModal'));
reAuthModal.show();
}
function scheduleReAuthModal() {
fetch('/Home/RemainingTime')
.then(res => res.json())
.then(data => {
let remainingMinutesThreshold = 20;
let remainingMinutes = data.remainingMinutes;
let triggerMinutes = Math.max(0, remainingMinutes - remainingMinutesThreshold);
let triggerMs = triggerMinutes * 60 * 1000;
setTimeout(() => {
if (!window.reAuthModalOpen) {
openReAuthModal();
}
}, triggerMs);
})
.catch(console.error);
}
scheduleReAuthModal();
$('#reAuthForm').on('submit', function (e) {
e.preventDefault();
const formData = $(this).serialize();
$.post('/Home/Login', formData)
.done(function () {
window.reAuthModalOpen = false;
$('#reAuthModal').modal('hide');
})
.fail(function (data) {
console.log(data);
const parser = new DOMParser();
const doc = parser.parseFromString(data.responseText, 'text/html');
const errorHeading = doc.querySelector("main div div h2");
let responseText = data.responseText;
document.getElementById("reAuthError").textContent = errorHeading.textContent;
$('#reAuthError').removeClass('d-none');
});
});
});
</script>
@* <script src="~/lib/jquery/dist/jquery.min.js" defer></script> *@ @* <script src="~/lib/jquery/dist/jquery.min.js" defer></script> *@
<script src="https://code.jquery.com/jquery-3.7.1.min.js" <script src="https://code.jquery.com/jquery-3.7.1.min.js"
crossorigin="anonymous" defer></script> crossorigin="anonymous" defer></script>

View File

@@ -0,0 +1,16 @@
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer T
<form method="post" action="/Home/Login" class="mt-4" style="max-width: 400px; margin: auto;">
<div class="form-group mb-3">
<label for="username" class="form-label">@T["Username"]</label>
<input autofocus type="text" class="form-control" id="username" name="username" autocomplete="username" required>
</div>
<div class="form-group mb-3">
<label for="password" class="form-label">@T["Password"]</label>
<input type="password" class="form-control" id="password" name="password" autocomplete="current-password" required>
</div>
<button type="submit" class="btn btn-primary w-100">@T["Login"]</button>
</form>