From 8723aae3f670f3d76bc5e7fea725b88d2e204314 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 9 Mar 2026 17:31:32 -0700 Subject: [PATCH] fix(encryptid): show success page instead of auto-OIDC redirect The auto-redirect through OIDC authorize fails because the client app (Postiz) didn't initiate the OAuth flow and has no matching state. Instead, show a branded success page with a link to the app. The user signs in with their passkey when they visit the app normally. Co-Authored-By: Claude Opus 4.6 --- src/encryptid/server.ts | 79 ++++++++++++----------------------------- 1 file changed, 23 insertions(+), 56 deletions(-) diff --git a/src/encryptid/server.ts b/src/encryptid/server.ts index 386f4de..e220439 100644 --- a/src/encryptid/server.ts +++ b/src/encryptid/server.ts @@ -4165,11 +4165,16 @@ app.get('/api/invites/identity/:token/info', async (c) => { if (Date.now() > invite.expiresAt) { return c.json({ error: 'Invite expired' }, 410); } - // Look up OIDC client name if this is a client invite + // Look up OIDC client info if this is a client invite let clientName: string | null = null; + let clientAppUrl: string | null = null; if (invite.clientId) { const client = await getOidcClient(invite.clientId); clientName = client?.name || null; + // Derive app URL from first redirect URI (strip the callback path) + if (client?.redirectUris?.[0]) { + try { clientAppUrl = new URL(client.redirectUris[0]).origin; } catch {} + } } return c.json({ invitedBy: invite.invitedByUsername, @@ -4178,6 +4183,7 @@ app.get('/api/invites/identity/:token/info', async (c) => { spaceSlug: invite.spaceSlug, clientId: invite.clientId, clientName, + clientAppUrl, }); }); @@ -4774,24 +4780,23 @@ function oidcAcceptPage(token: string): string { return; } - // If this is an OIDC client invite, redirect through the OIDC authorize flow - if (inviteData.clientId) { - showStatus('Redirecting to ' + (inviteData.clientName || 'the app') + '...'); - // Store the session token so the OIDC authorize page can use it - localStorage.setItem('eid_token', sessionToken); - // Start the OIDC authorize flow — the authorize page will auto-login - window.location.href = '/oidc/authorize?client_id=' + encodeURIComponent(inviteData.clientId) + - '&response_type=code&scope=openid+profile+email&redirect_uri=' + encodeURIComponent('auto') + - '&state=invite_accept'; - return; - } - - // Non-OIDC invite — show success + // Show success with link to the app document.getElementById('authSection').style.display = 'none'; statusEl.style.display = 'none'; - successEl.innerHTML = 'Welcome!
Your invitation has been accepted.' + - (claimData.spaceSlug ? '
You\\'ve been added to ' + esc(claimData.spaceSlug) + '.' : '') + - '

Go to rSpace \\u2192'; + + const appName = inviteData.clientName || 'rSpace'; + const appUrl = inviteData.clientAppUrl || 'https://rspace.online'; + + if (inviteData.clientId) { + successEl.innerHTML = 'You\\u2019re in!
' + + 'Your account is set up and you\\u2019ve been granted access to ' + esc(appName) + '.' + + '

Go to ' + esc(appName) + ' \\u2192' + + '

You\\u2019ll sign in with your passkey when you get there.

'; + } else { + successEl.innerHTML = 'Welcome!
Your invitation has been accepted.' + + (claimData.spaceSlug ? '
You\\u2019ve been added to ' + esc(claimData.spaceSlug) + '.' : '') + + '

Go to rSpace \\u2192'; + } successEl.style.display = 'block'; } @@ -4946,7 +4951,7 @@ app.get('/.well-known/openid-configuration', (c) => { // Authorization endpoint app.get('/oidc/authorize', async (c) => { const clientId = c.req.query('client_id'); - let redirectUri = c.req.query('redirect_uri'); + const redirectUri = c.req.query('redirect_uri'); const responseType = c.req.query('response_type'); const scope = c.req.query('scope') || 'openid profile email'; const state = c.req.query('state') || ''; @@ -4963,11 +4968,6 @@ app.get('/oidc/authorize', async (c) => { return c.text('Unknown client_id', 400); } - // "auto" redirect_uri: use the client's first registered redirect URI (from invite accept flow) - if (redirectUri === 'auto') { - redirectUri = client.redirectUris[0]; - } - if (!client.redirectUris.includes(redirectUri)) { return c.text('Invalid redirect_uri', 400); } @@ -5353,39 +5353,6 @@ function oidcAuthorizePage(appName: string, clientId: string, redirectUri: strin } } - // Auto-authorize if coming from invite accept flow with a stored session token - (async () => { - if (STATE !== 'invite_accept') return; - const storedToken = localStorage.getItem('eid_token'); - if (!storedToken) return; - localStorage.removeItem('eid_token'); - loginBtn.disabled = true; - showStatus('Authorizing...'); - try { - const authorizeRes = await fetch('/oidc/authorize', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - clientId: CLIENT_ID, - redirectUri: REDIRECT_URI, - scope: SCOPE, - state: STATE, - token: storedToken, - }), - }); - const authorizeResult = await authorizeRes.json(); - if (authorizeResult.error) { - showError(authorizeResult.message || authorizeResult.error); - loginBtn.disabled = false; - return; - } - showStatus('Redirecting...'); - window.location.href = authorizeResult.redirectUrl; - } catch (err) { - showError('Auto-login failed. Please sign in manually.'); - loginBtn.disabled = false; - } - })(); `;