Improve Gateway authentication and static HTTP fidelity

This commit is contained in:
StasonJatham committed 2026-10-07 07:32:34 +02:00
1 parent 42561bfa34
commit 4ab9411aca
8 files changed
+167 -58

No files matched your search

+2 -33
View File
@@ -7,42 +7,10 @@
<title class="_ctxstxt_NetscalerGateway">Citrix Gateway</title>
<link rel="icon" href="/logon/LogonPoint/receiver/images/common/icon_vpn.ico" sizes="16x16 32x32 48x48 64x64" type="image/vnd.microsoft.icon">
<link rel="shortcut icon" href="/logon/LogonPoint/receiver/images/common/icon_vpn.ico" type="image/vnd.microsoft.icon">
<style>
* { box-sizing: border-box; }
html, body { min-height: 100%; margin: 0; }
body { background: #302c41 url('/logon/LogonPoint/receiver/images/common/ReceiverFullScreenBackground.jpg') center / cover fixed;
font-family: "Helvetica Neue", "Segoe UI", Helvetica, Arial, sans-serif; color: #fff; }
main { min-height: 100vh; display: flex; align-items: center; }
.band { width: 100%; min-height: 350px; background: rgba(58, 54, 65, .86); display: flex; align-items: center; }
.layout { width: min(1086px, calc(100% - 64px)); margin: auto; display: grid; grid-template-columns: 258px 570px; align-items: center; }
.logo { width: 160px; height: auto; }
h1 { font-size: 18px; font-weight: 400; margin: 0 0 18px; }
.row { display: grid; grid-template-columns: 185px 1fr; align-items: center; margin-bottom: 6px; }
label { color: #bdbbc2; font-size: 16px; }
input { width: 100%; height: 44px; border: 1px solid #ddd; background: white; color: #111; border-radius: 0; padding: 8px 15px; font: inherit; }
input:focus-visible { outline: 2px solid #79cce2; outline-offset: 2px; }
button { display: block; width: calc(100% - 185px); margin: 26px 0 0 185px; height: 45px; border: 0; border-radius: 0;
background: #49a0bb; color: white; font: 600 16px "Helvetica Neue", "Segoe UI", Helvetica, Arial, sans-serif; cursor: pointer; }
button:hover { background: #398aa4; }
button:focus-visible { outline: 2px solid white; outline-offset: 3px; }
button:disabled { cursor: wait; opacity: .75; }
#message { margin: 12px 0 0 185px; font-size: 14px; color: #fff; }
#message:empty { display: none; }
@media (max-width: 950px) {
.layout { width: min(570px, calc(100% - 48px)); display: block; padding: 40px 0; }
.logo { margin-bottom: 28px; }
}
@media (max-width: 540px) {
.row { display: block; margin-bottom: 16px; }
label { display: block; margin-bottom: 7px; }
button { width: 100%; margin-left: 0; }
#message { margin-left: 0; }
}
</style>
<link rel="stylesheet" href="/vpn/js/rdx/core/css/rdx.css">
<script src="/vpn/js/rdx.js"></script>
<script src="/vpn/js/gateway_login_view.js"></script>
<script src="/logon/LogonPoint/login.js" defer></script>
<script>var _ctxstxt_CitrixCopyright = 'Copyright (c) Citrix Systems, Inc.';</script>
</head>
<body>
<main>
@@ -53,6 +21,7 @@
<h1>Melden Sie sich an</h1>
<div class="row"><label for="login">Benutzername:</label><input id="login" name="login" required autofocus autocomplete="off" spellcheck="false"></div>
<div class="row"><label for="passwd">Kennwort:</label><input id="passwd" name="passwd" type="password" required autocomplete="off"></div>
<input type="hidden" name="StateContext" value="">
<input type="hidden" name="savecredentials" value="false">
<button id="nsg-x1-logon-button" name="nsg-x1-logon-button" value="Log On" type="submit">Anmelden</button>
<p id="message" role="status" aria-live="polite"></p>
+18 -6
View File
@@ -2,19 +2,31 @@
const form = document.getElementById('login-form');
const button = document.getElementById('nsg-x1-logon-button');
const message = document.getElementById('message');
async function authenticate(path, body) {
const response = await fetch(path, {
method: 'POST', body, credentials: 'same-origin', redirect: 'error',
headers: { Accept: 'application/vnd.citrix.authenticateresponse-1+xml' },
signal: AbortSignal.timeout(10000)
});
if (!response.ok) throw new Error('Request failed');
const result = rdx.utils.authentication(await response.text());
form.elements.StateContext.value = result.state;
// Only the local authentication endpoint can receive form credentials.
if (result.postback === '/nf/auth/doAuthentication.do') form.action = result.postback;
return result;
}
const ready = authenticate('/nf/auth/getAuthenticationRequirements.do', new URLSearchParams())
.catch(() => null);
form.addEventListener('submit', async event => {
event.preventDefault();
button.disabled = true;
message.textContent = '';
try {
await ready;
const body = new URLSearchParams(new FormData(form));
body.set('nsg-x1-logon-button', 'Log On');
const response = await fetch('/nf/auth/doAuthentication.do', {
method: 'POST', body, credentials: 'same-origin', redirect: 'error',
signal: AbortSignal.timeout(10000)
});
if (!response.ok) throw new Error('Request failed');
message.textContent = 'Anmeldung fehlgeschlagen. Überprüfen Sie Ihre Zugangsdaten.';
const result = await authenticate('/nf/auth/doAuthentication.do', body);
message.textContent = result.errors.join(' ') || 'Anmeldung fehlgeschlagen. Überprüfen Sie Ihre Zugangsdaten.';
} catch {
message.textContent = 'Die Anmeldung ist derzeit nicht möglich. Versuchen Sie es erneut.';
} finally {
+30
View File
@@ -0,0 +1,30 @@
* { box-sizing: border-box; }
html, body { min-height: 100%; margin: 0; }
body { background: #302c41 url('/logon/LogonPoint/receiver/images/common/ReceiverFullScreenBackground.jpg') center / cover fixed;
font-family: "Helvetica Neue", "Segoe UI", Helvetica, Arial, sans-serif; color: #fff; }
main { min-height: 100vh; display: flex; align-items: center; }
.band { width: 100%; min-height: 350px; background: rgba(58, 54, 65, .86); display: flex; align-items: center; }
.layout { width: min(1086px, calc(100% - 64px)); margin: auto; display: grid; grid-template-columns: 258px 570px; align-items: center; }
.logo { width: 160px; height: auto; }
h1 { font-size: 18px; font-weight: 400; margin: 0 0 18px; }
.row { display: grid; grid-template-columns: 185px 1fr; align-items: center; margin-bottom: 6px; }
label { color: #bdbbc2; font-size: 16px; }
input { width: 100%; height: 44px; border: 1px solid #ddd; background: white; color: #111; border-radius: 0; padding: 8px 15px; font: inherit; }
input:focus-visible { outline: 2px solid #79cce2; outline-offset: 2px; }
button { display: block; width: calc(100% - 185px); margin: 26px 0 0 185px; height: 45px; border: 0; border-radius: 0;
background: #49a0bb; color: white; font: 600 16px "Helvetica Neue", "Segoe UI", Helvetica, Arial, sans-serif; cursor: pointer; }
button:hover { background: #398aa4; }
button:focus-visible { outline: 2px solid white; outline-offset: 3px; }
button:disabled { cursor: wait; opacity: .75; }
#message { margin: 12px 0 0 185px; font-size: 14px; color: #fff; }
#message:empty { display: none; }
@media (max-width: 950px) {
.layout { width: min(570px, calc(100% - 48px)); display: block; padding: 40px 0; }
.logo { margin-bottom: 28px; }
}
@media (max-width: 540px) {
.row { display: block; margin-bottom: 16px; }
label { display: block; margin-bottom: 7px; }
button { width: 100%; margin-left: 0; }
#message { margin-left: 0; }
}
+23
View File
@@ -0,0 +1,23 @@
'use strict';
var _ctxstxt_CitrixCopyright = 'Copyright (c) Citrix Systems, Inc.';
window.CTXS = window.CTXS || {};
window.rdx = window.rdx || {};
rdx.base_view = function (element) { this.element = element; };
rdx.base_view.prototype.show = function () { if (this.element) this.element.hidden = false; };
rdx.base_view.prototype.hide = function () { if (this.element) this.element.hidden = true; };
rdx.utils = {
get_view: function (id) { return new rdx.base_view(document.getElementById(id || 'login-form')); },
authentication: function (xml) {
const document = new DOMParser().parseFromString(xml, 'application/xml');
const ns = 'http://citrix.com/authentication/response/1';
if (document.getElementsByTagName('parsererror').length ||
document.documentElement.localName !== 'AuthenticateResponse' ||
document.documentElement.namespaceURI !== ns) throw new Error('Invalid authentication response');
const text = name => document.getElementsByTagNameNS(ns, name)[0]?.textContent || '';
const errors = Array.from(document.getElementsByTagNameNS(ns, 'Label'))
.filter(label => label.getElementsByTagNameNS(ns, 'Type')[0]?.textContent === 'error')
.map(label => label.getElementsByTagNameNS(ns, 'Text')[0]?.textContent || '');
return { state: text('StateContext'), postback: text('PostBack'), errors };
}
};
CTXS.core = { getView: rdx.utils.get_view };