fix(auth): auto-submit OIDC logout form to eliminate xsrf desync
Users clicking sign-out in the wiki were getting 'xsrf token invalid'. The old logoutSource extracted the xsrf from oidc-provider's form into a separate short-lived cookie and bounced through /auth/logout-confirm, but that dance kept desyncing — the xsrf on the eventual submit didn't always match the session state on /oidc/session/end/confirm. Drop the custom confirmation page and auto-submit oidc-provider's own form inline from logoutSource. The xsrf stays inside the original form HTML the provider generated, so the validation is guaranteed to match. Clicking sign-out in the wiki is already confirmation enough. Also clear the Ghost Guild auth-token cookie in postLogoutSuccessSource so signing out of the wiki fully signs the user out rather than leaving a stale ghostguild.org session behind.
This commit is contained in:
parent
3ad22a8b67
commit
39eb9e039a
1 changed files with 31 additions and 22 deletions
|
|
@ -93,34 +93,43 @@ export async function getOidcProvider() {
|
||||||
rpInitiatedLogout: {
|
rpInitiatedLogout: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
logoutSource: async (ctx: any, form: string) => {
|
logoutSource: async (ctx: any, form: string) => {
|
||||||
// oidc-provider's form HTML is a stable format (see node_modules/
|
// Auto-submit oidc-provider's own form so the xsrf value stays
|
||||||
// oidc-provider/lib/actions/end_session.js:90):
|
// inside the same request cycle that generated it. The previous
|
||||||
// <form id="op.logoutForm" method="post" action="..."><input
|
// approach extracted the xsrf into a separate cookie and bounced
|
||||||
// type="hidden" name="xsrf" value="HEX"/></form>
|
// through a Nuxt page for a "are you sure?" confirmation, which
|
||||||
// We extract just the xsrf token and hand off to a Nuxt page at
|
// kept desyncing and producing "xsrf token invalid" errors.
|
||||||
// /auth/logout-confirm that renders a styled form posting back to
|
// Clicking sign-out in the wiki is already confirmation enough.
|
||||||
// /oidc/session/end/confirm with that xsrf value. The token rides
|
|
||||||
// in a short-lived httpOnly cookie so it never hits the URL.
|
|
||||||
const match = form.match(/name="xsrf"\s+value="([^"]+)"/);
|
|
||||||
if (!match) {
|
|
||||||
// Defensive: if oidc-provider ever changes its form format, fall
|
|
||||||
// back to the raw form so logout still works.
|
|
||||||
ctx.type = "html";
|
ctx.type = "html";
|
||||||
ctx.status = 200;
|
ctx.status = 200;
|
||||||
ctx.body = `<!DOCTYPE html><html><body>${form}<script>document.getElementById('op.logoutForm').submit()</script></body></html>`;
|
ctx.body = `<!DOCTYPE html>
|
||||||
return;
|
<html>
|
||||||
}
|
<head>
|
||||||
ctx.cookies.set("oidc_logout_xsrf", match[1], {
|
<title>Signing out — Ghost Guild</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style>
|
||||||
|
html, body { margin: 0; padding: 0; height: 100%; background: #1a1814; color: #e8d9b8; font-family: "Commit Mono", ui-monospace, monospace; }
|
||||||
|
body { display: grid; place-items: center; }
|
||||||
|
p { font-size: 13px; letter-spacing: 0.05em; text-transform: uppercase; color: #b09c76; }
|
||||||
|
form { display: none; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Signing you out…</p>
|
||||||
|
${form}
|
||||||
|
<script>document.getElementById('op.logoutForm').submit()</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
},
|
||||||
|
postLogoutSuccessSource: async (ctx: any) => {
|
||||||
|
// Kill the Ghost Guild session cookie so the user is fully signed
|
||||||
|
// out, not just logged out of Outline.
|
||||||
|
ctx.cookies.set("auth-token", null, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: "lax",
|
sameSite: "lax",
|
||||||
maxAge: 120_000, // 2 minutes
|
|
||||||
path: "/",
|
path: "/",
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
signed: false,
|
signed: false,
|
||||||
});
|
});
|
||||||
ctx.redirect("/auth/logout-confirm");
|
|
||||||
},
|
|
||||||
postLogoutSuccessSource: async (ctx: any) => {
|
|
||||||
ctx.redirect("/auth/logout-success");
|
ctx.redirect("/auth/logout-success");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue