Prevent false OAuth success pages before callback validation completes

The callback server was acknowledging success before the asynchronous validation/token-exchange path had actually finished, which could leave users looking at a success page while the flow failed in an unhandled rejection. The handler now awaits callback completion and returns an error page when callback processing fails.

Constraint: Needs to remain compatible with the existing full-callback-URL OAuth flow added in prior fixes
Rejected: Catch-and-log callback errors without changing the response body | preserves the misleading success UX and hides failure state from users
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the auth callback response coupled to actual callback completion; do not move async failures back onto an unobserved path
Tested: pnpm install; pnpm run deps; apps/main npm run build; source-backed auth callback failure validation JSON
Not-tested: Live provider OAuth round-trip against real credentials
This commit is contained in:
JunghwanNA 2026-04-18 00:44:34 +09:00
parent 2133d7226f
commit b9599f2a19

View file

@ -29,7 +29,7 @@ export function createAuthServer(
onCallback: (callbackUrl: URL) => void | Promise<void> onCallback: (callbackUrl: URL) => void | Promise<void>
): Promise<AuthServerResult> { ): Promise<AuthServerResult> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const server = createServer((req, res) => { const server = createServer(async (req, res) => {
if (!req.url) { if (!req.url) {
res.writeHead(400); res.writeHead(400);
res.end('Bad Request'); res.end('Bad Request');
@ -64,8 +64,10 @@ export function createAuthServer(
return; return;
} }
// Handle callback - pass full URL so params like iss (OpenID Connect) are preserved for token exchange try {
onCallback(url); // Handle callback - pass full URL so params like iss (OpenID Connect)
// are preserved for token exchange.
await onCallback(url);
res.writeHead(200, { 'Content-Type': 'text/html' }); res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(` res.end(`
@ -85,6 +87,28 @@ export function createAuthServer(
</body> </body>
</html> </html>
`); `);
} catch (callbackError) {
const message = callbackError instanceof Error ? callbackError.message : String(callbackError);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<!DOCTYPE html>
<html>
<head>
<title>OAuth Error</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
.error { color: #d32f2f; }
</style>
</head>
<body>
<h1 class="error">Authorization Failed</h1>
<p>Error: ${escapeHtml(message)}</p>
<p>You can close this window.</p>
<script>setTimeout(() => window.close(), 3000);</script>
</body>
</html>
`);
}
} else { } else {
res.writeHead(404); res.writeHead(404);
res.end('Not Found'); res.end('Not Found');
@ -104,4 +128,3 @@ export function createAuthServer(
}); });
}); });
} }