import { CALLBACK_PATH, CALLBACK_TIMEOUT_MS } from './config.ts';
const SUCCESS_HTML = `
Logged in
You're logged in to Oriyn.
You can close this tab and return to your terminal.
`;
const errorHtml = (message: string): string => `
Login failed
Login failed
${message.replace(/[<>&]/g, '')}
`;
export interface CallbackResult {
code: string;
state: string;
}
export interface CallbackHandle {
port: number;
redirectUri: string;
result: Promise;
close: () => void;
}
export const startCallbackServer = (input: { expectedState: string }): CallbackHandle => {
let resolve!: (value: CallbackResult) => void;
let reject!: (err: Error) => void;
const result = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
const server = Bun.serve({
hostname: '127.0.0.1',
port: 0,
fetch(req) {
const url = new URL(req.url);
if (url.pathname !== CALLBACK_PATH) {
return new Response('Not found', { status: 404 });
}
const error = url.searchParams.get('error');
if (error) {
const description = url.searchParams.get('error_description') ?? error;
reject(new Error(`OAuth provider returned error: ${description}`));
return new Response(errorHtml(description), {
status: 400,
headers: { 'content-type': 'text/html; charset=utf-8' },
});
}
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
if (!code || !state) {
reject(new Error('OAuth callback missing code or state'));
return new Response(errorHtml('Missing code or state.'), {
status: 400,
headers: { 'content-type': 'text/html; charset=utf-8' },
});
}
if (state !== input.expectedState) {
reject(new Error('OAuth state mismatch (possible CSRF attempt)'));
return new Response(errorHtml('State mismatch.'), {
status: 400,
headers: { 'content-type': 'text/html; charset=utf-8' },
});
}
resolve({ code, state });
return new Response(SUCCESS_HTML, {
status: 200,
headers: { 'content-type': 'text/html; charset=utf-8' },
});
},
});
const timeout = setTimeout(() => {
reject(new Error('Login timed out waiting for browser callback'));
}, CALLBACK_TIMEOUT_MS);
const close = () => {
clearTimeout(timeout);
server.stop();
};
// Auto-close after either resolution or rejection.
result.finally(close).catch(() => {});
const port = server.port;
if (port == null) {
close();
throw new Error('Failed to bind callback server');
}
return {
port,
redirectUri: `http://127.0.0.1:${port}${CALLBACK_PATH}`,
result,
close,
};
};