Style wiki auth screens with guild design system

Add guild-styled HTML templates for OIDC logout confirmation, post-logout
success, and error pages. Update wiki login heading to brand convention
(candlelight + warm-text). Restyle magic link email from blue to guild
colour tokens.
This commit is contained in:
Jennie Robinson Faber 2026-03-04 17:26:48 +00:00
parent 79d3ba0f78
commit bf57f4b33d
3 changed files with 175 additions and 18 deletions

View file

@ -11,6 +11,126 @@ import { MongoAdapter } from "./oidc-mongodb-adapter.js";
import Member from "../models/member.js";
import { connectDB } from "./mongoose.js";
/**
* Renders a standalone HTML page in the guild dark style.
* Used for OIDC logout/error screens that are served outside Nuxt.
*/
function guildPageShell(title: string, bodyContent: string, extraStyles = "") {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${title} Ghost Guild</title>
<style>
@font-face {
font-family: 'Quietism';
src: url('/fonts/Quietism-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Quietism';
src: url('/fonts/Quietism-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background-color: #1a1510;
background-image:
radial-gradient(ellipse at 20% 50%, rgba(154, 111, 44, 0.06) 0%, transparent 60%),
radial-gradient(ellipse at 80% 50%, rgba(154, 111, 44, 0.04) 0%, transparent 60%);
color: #bfb3a2;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.card {
background-color: #2a241c;
border: 1px solid rgba(154, 111, 44, 0.15);
border-radius: 12px;
box-shadow: 0 0 30px rgba(208, 158, 78, 0.06);
padding: 2.5rem;
width: 100%;
max-width: 420px;
text-align: center;
}
h1 {
font-family: 'Quietism', Georgia, 'Times New Roman', serif;
font-size: 1.5rem;
font-weight: 700;
color: #d09e4e;
margin-bottom: 0.75rem;
}
p { line-height: 1.6; margin-bottom: 1rem; }
.subtext { font-size: 0.875rem; color: #6b5f4d; }
.btn-primary {
display: inline-block;
background-color: #9a6f2c;
color: #f0ebe4;
padding: 0.625rem 1.5rem;
border-radius: 6px;
border: none;
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
text-decoration: none;
transition: background-color 0.15s;
}
.btn-primary:hover { background-color: #b8862f; }
.btn-secondary {
display: inline-block;
background-color: transparent;
color: #bfb3a2;
padding: 0.625rem 1.5rem;
border-radius: 6px;
border: 1px solid rgba(154, 111, 44, 0.3);
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
text-decoration: none;
transition: border-color 0.15s;
}
.btn-secondary:hover { border-color: rgba(154, 111, 44, 0.5); }
.actions { display: flex; gap: 0.75rem; justify-content: center; margin-top: 1.5rem; }
.brand {
margin-top: 2rem;
font-family: 'Quietism', Georgia, 'Times New Roman', serif;
font-size: 0.75rem;
font-variant: small-caps;
letter-spacing: 0.05em;
color: #6b5f4d;
}
.error-detail {
margin-top: 1rem;
background-color: #1a1510;
border: 1px solid rgba(154, 111, 44, 0.1);
border-radius: 6px;
padding: 1rem;
font-family: 'Ubuntu Mono', 'Courier New', monospace;
font-size: 0.75rem;
color: #6b5f4d;
text-align: left;
word-break: break-word;
}
${extraStyles}
</style>
</head>
<body>
<div class="card">
${bodyContent}
<div class="brand">Ghost Guild</div>
</div>
</body>
</html>`;
}
let _provider: InstanceType<typeof Provider> | null = null;
export async function getOidcProvider() {
@ -90,7 +210,30 @@ export async function getOidcProvider() {
enabled: process.env.NODE_ENV !== "production",
},
revocation: { enabled: true },
rpInitiatedLogout: { enabled: true },
rpInitiatedLogout: {
enabled: true,
logoutSource: async (ctx: any, form: string) => {
ctx.body = guildPageShell("Sign Out", `
<h1>Sign Out</h1>
<p>Do you want to sign out of your Ghost Guild session?</p>
<p class="subtext">This will sign you out of the wiki and any other connected services.</p>
${form}
<div class="actions">
<button class="btn-primary" form="op.logoutForm" type="submit" value="yes" name="logout">Yes, sign me out</button>
<a class="btn-secondary" href="https://wiki.ghostguild.org">Stay signed in</a>
</div>
`, "form#op\\.logoutForm { display: none; }");
},
postLogoutSuccessSource: async (ctx: any) => {
ctx.body = guildPageShell("Signed Out", `
<h1>Signed Out</h1>
<p>You have been successfully signed out.</p>
<div class="actions">
<a class="btn-primary" href="https://wiki.ghostguild.org">Return to Wiki</a>
</div>
`);
},
},
},
// Mount all OIDC endpoints under /oidc prefix
@ -115,6 +258,20 @@ export async function getOidcProvider() {
},
},
renderError: async (ctx: any, out: Record<string, string>, _error: Error) => {
const details = Object.entries(out)
.map(([key, value]) => `<strong>${key}:</strong> ${value}`)
.join("<br>");
ctx.body = guildPageShell("Something Went Wrong", `
<h1>Something Went Wrong</h1>
<p>An error occurred during authentication. Please try again.</p>
<div class="error-detail">${details}</div>
<div class="actions">
<a class="btn-primary" href="https://wiki.ghostguild.org">Return to Wiki</a>
</div>
`);
},
// Allow Outline to use PKCE but don't require it
pkce: {
required: () => false,