From 5e95a4756d790a9b61bb946fc398eb471c18c6d8 Mon Sep 17 00:00:00 2001 From: Bryan Brooks Date: Thu, 19 Feb 2026 12:56:51 -0600 Subject: [PATCH] fix: use native form POST for OIDC login (fixes iOS Safari) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iOS Safari blocks window.location.href navigations after async fetch() calls because the user gesture chain is broken. Changed the ATAuth login page from fetch-based JavaScript to native HTML form submission — the server already handles non-JSON POSTs with 302 redirects. On error, re-renders the login page with the error message instead of returning plain text. Extracted login page HTML into reusable renderLoginPage() function. Co-Authored-By: Claude Opus 4.6 --- gateway/src/routes/oidc/authorize.ts | 305 ++++++++++----------------- 1 file changed, 116 insertions(+), 189 deletions(-) diff --git a/gateway/src/routes/oidc/authorize.ts b/gateway/src/routes/oidc/authorize.ts index 891a01e..75d81ba 100644 --- a/gateway/src/routes/oidc/authorize.ts +++ b/gateway/src/routes/oidc/authorize.ts @@ -19,6 +19,115 @@ export function createAuthorizeRouter( ): Router { const router = Router(); + function renderLoginPage(clientName: string, authCode: string, state: string, errorMessage?: string): string { + const errorHtml = errorMessage + ? `
${errorMessage}
` + : '
'; + return ` + + + + + + Sign in - ATAuth + + + +
+ +

Sign in with ATAuth

+

Use your Bluesky or AT Protocol identity

+
Signing in to ${clientName}
+ ${errorHtml} +
+ + + + +

Enter your Bluesky handle or custom domain

+ +
+
+ + +`; + } + /** * GET/POST /oauth/authorize * Start the authorization flow @@ -152,194 +261,7 @@ export function createAuthorizeRouter( }); // Show login page asking for handle - const loginPageHtml = ` - - - - - - Sign in - ATAuth - - - -
- -

Sign in with ATAuth

-

Use your Bluesky or AT Protocol identity

- -
- Signing in to ${client.name || client_id} -
- -
- -
- - - - - -

Enter your Bluesky handle or custom domain

- - -
-
- - - -`; - - res.type('html').send(loginPageHtml); + res.type('html').send(renderLoginPage(client.name || client_id, authCode, state || '')); } catch (error) { console.error('[OIDC Authorize] Error:', error); res.status(500).json({ @@ -419,7 +341,12 @@ export function createAuthorizeRouter( if (isJsonRequest) { res.status(400).json({ error: userMessage }); } else { - res.status(400).send(userMessage); + // Re-render login page with error for native form submissions + const { auth_code: errAuthCode } = req.body; + const errAuthData = errAuthCode ? db.getAuthorizationCode(errAuthCode) : null; + const errClient = errAuthData ? db.getApp(errAuthData.client_id) : null; + const errClientName = errClient?.name || errAuthData?.client_id || 'Unknown'; + res.status(400).type('html').send(renderLoginPage(errClientName, errAuthCode || '', errAuthData?.state || '', userMessage)); } } }); -- knotmirror 0.0.1