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>
): Promise<AuthServerResult> {
return new Promise((resolve, reject) => {
const server = createServer((req, res) => {
const server = createServer(async (req, res) => {
if (!req.url) {
res.writeHead(400);
res.end('Bad Request');
@ -64,27 +64,51 @@ export function createAuthServer(
return;
}
// Handle callback - pass full URL so params like iss (OpenID Connect) are preserved for token exchange
onCallback(url);
try {
// 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.end(`
<!DOCTYPE html>
<html>
<head>
<title>Authorization Successful</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
.success { color: #2e7d32; }
</style>
</head>
<body>
<h1 class="success">Authorization Successful</h1>
<p>You can close this window.</p>
<script>setTimeout(() => window.close(), 2000);</script>
</body>
</html>
`);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<!DOCTYPE html>
<html>
<head>
<title>Authorization Successful</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
.success { color: #2e7d32; }
</style>
</head>
<body>
<h1 class="success">Authorization Successful</h1>
<p>You can close this window.</p>
<script>setTimeout(() => window.close(), 2000);</script>
</body>
</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 {
res.writeHead(404);
res.end('Not Found');
@ -104,4 +128,3 @@ export function createAuthServer(
});
});
}