mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-03 12:22:38 +02:00
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:
parent
2133d7226f
commit
b9599f2a19
1 changed files with 45 additions and 22 deletions
|
|
@ -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(
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue