server/app.py: - okta_login(): validates and stashes ?next= (same-site path only) in the OAuth-state session before redirecting to Okta, so a deep link an assignment email carried (X1/CR-011/CR-014) survives the round trip instead of always landing on /index.html. - okta_callback(): reads that stashed next= back (re-validated on the way out too - belt and suspenders against a crafted value) and redirects there on success. The two failure paths that used to raise a raw HTTPException - OAuthError (sign-in cancelled/failed) and a locally-disabled account - now redirect to /login.html?error=... instead: this route is reached by a full page browser navigation from Okta, not a fetch() call, so a JSON error body just looks like a broken page to whoever is signing in. html/login.html + html/login.js: rebuilt as a single "Sign in with Okta" link, replacing the username/password form and the forgot/reset-password views (gone entirely - no local password exists to reset, per D15/D16/T10.4). Kept the accessible error/ok banner pattern (role=alert / role=status) byte-for- byte, since CLAUDE.md names this file as the reference other pages copy for that pattern. login.js reads ?next= off its own URL (auth-guard.js's goToLogin() already builds this, unchanged) and forwards it to /api/auth/okta/login, and shows a plain-language message for ?error=disabled / ?error=cancelled, clearing the code from the address bar once shown. Sign- out (auth-guard.js's wpLogout()) already redirected to login.html - untouched, already satisfied "lands back on the app's own login page." Uses a real <a href> rather than a JS-driven navigation, so it's a working link even before login.js runs, and needs no keyboard/touch handling beyond what a link gets for free (C1 accessibility). Also fixed in passing (not a separate commit - this is what exposed it): _safe_next_path() on the server and safeNext() in login.js enforce the exact same rule (same-site path only, reject '//' and scheme URLs) so a crafted ?next= can't become an open redirect through a real Okta sign-in. Verified: a fake-Okta-client round trip against the real app (SessionMiddleware fix from the prior commit) confirms next= is honored end to end, a malicious next= is rejected and falls back to /index.html, OAuthError redirects to ?error=cancelled, and a disabled account redirects to ?error=disabled. The JS-side safeNext() was checked against the same cases directly in Node and matches the server's validation exactly. login.js passes `node --check`; login.html parses cleanly. Live 390px/1440px screenshots were NOT captured this session - the environment's browser pane isn't signed in to view a published preview of it, so that check needs to happen when this branch is actually run and opened by a signed-in browser; the layout risk is low since .card/.brand/.error/.ok/.foot are unchanged from the already-shipped file and the only new CSS is one simple full-width block link. wave-10.md T10.5 / D15 / D16
93 lines
2.8 KiB
HTML
93 lines
2.8 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Sign in — Work Package Suite</title>
|
|
<link rel="icon" href="favicon.ico" sizes="any">
|
|
<link rel="stylesheet" href="theme-light.css">
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--cds-background);
|
|
padding: 1.5rem;
|
|
}
|
|
.card {
|
|
width: 100%;
|
|
max-width: 400px;
|
|
background: var(--cds-layer);
|
|
border: 1px solid var(--cds-border-subtle);
|
|
border-top: 3px solid var(--cds-interactive-01);
|
|
padding: 2.5rem 2rem;
|
|
}
|
|
.brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.brand img { height: 36px; width: auto; }
|
|
h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
|
|
.sub { color: var(--cds-text-secondary); font-size: 0.875rem; margin-bottom: 1.75rem; }
|
|
.btn {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 0.875rem 1rem;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
text-decoration: none;
|
|
color: var(--cds-text-on-color);
|
|
background: var(--cds-button-primary);
|
|
border: none;
|
|
transition: background 0.15s;
|
|
}
|
|
.btn:hover { background: var(--cds-hover-primary); }
|
|
.btn:focus-visible { outline: 2px solid var(--cds-focus); outline-offset: 2px; }
|
|
.error {
|
|
display: none;
|
|
background: var(--wp-status-error-bg);
|
|
border-left: 3px solid var(--cds-support-error);
|
|
color: var(--cds-text-error);
|
|
padding: 0.75rem;
|
|
font-size: 0.8125rem;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.error.show { display: block; }
|
|
.ok {
|
|
display: none;
|
|
background: var(--wp-status-success-bg);
|
|
border-left: 3px solid var(--cds-support-success);
|
|
color: var(--wp-hover-success);
|
|
padding: 0.75rem;
|
|
font-size: 0.8125rem;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.ok.show { display: block; }
|
|
.foot { margin-top: 1.5rem; font-size: 0.75rem; color: var(--cds-text-helper); text-align: center; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main class="card">
|
|
<div class="brand">
|
|
<img src="prime-controls-logo.jpg" alt="Prime Controls" onerror="this.style.display='none'">
|
|
</div>
|
|
<div id="error" class="error" role="alert"></div>
|
|
<div id="ok" class="ok" role="status"></div>
|
|
|
|
<h1>Sign in</h1>
|
|
<p class="sub">Work Package Suite uses your organization's Okta sign-in. Select the
|
|
button below and follow the prompts there.</p>
|
|
<a id="okta-signin" class="btn" href="/api/auth/okta/login" autofocus>Sign in with Okta</a>
|
|
|
|
<p class="foot">Authorized use only · BTG / Pilot</p>
|
|
</main>
|
|
|
|
<script src="login.js"></script>
|
|
</body>
|
|
</html>
|