import { exchangeCodeForToken } from '@nylas-labs/cli-kit/v3' import { createFileRoute } from '@tanstack/react-router' import { MAIL_HOME_PATH } from '#app/config/route-paths' import { platform } from '#server/platform' import { addVerifiedSessionAccount, consumeConnectState, getSession } from '#server/session' import { OWNMAIL_USER_AGENT } from '#server/usage-attribution' export const Route = createFileRoute('/auth/callback')({ server: { handlers: { /** Nylas Connect callback: code → grant, then cookie (+KV) session. */ GET: async ({ request }) => { const { env } = await platform() const url = new URL(request.url) const code = url.searchParams.get('code') const state = url.searchParams.get('state') const error = url.searchParams.get('error') if (error) { if (!state) return loginFailedResponse(callbackFailureMessage(error)) const failedAttempt = await consumeConnectState(request, state) return loginFailedResponse(callbackFailureMessage(error), failedAttempt?.clearCookie) } if (!code || !state) return loginFailedResponse(callbackFailureMessage(null)) const connectState = await consumeConnectState(request, state) if (!connectState) { return loginFailedResponse('expired login attempt — please try again') } try { const existingSession = await getSession(request) const token = await exchangeCodeForToken({ region: env.NYLAS_REGION, baseUrl: env.NYLAS_API_BASE_URL, clientId: env.NYLAS_CLIENT_ID, clientSecret: env.NYLAS_API_KEY, redirectUri: `${url.origin}/auth/callback`, code, userAgent: OWNMAIL_USER_AGENT, }) const verifiedEmail = callbackEmail(token.email, env.INBOX_EMAIL, Boolean(existingSession)) if (!verifiedEmail) { return loginFailedResponse( 'We couldn’t verify the email address for that inbox. Please try again.', connectState.clearCookie, ) } const headers = new Headers({ Location: MAIL_HOME_PATH }) headers.append( 'Set-Cookie', await addVerifiedSessionAccount(request, token.grant_id, verifiedEmail), ) headers.append('Set-Cookie', connectState.clearCookie) return new Response(null, { status: 302, headers }) } catch (err) { reportTokenExchangeFailure(err) // Never surface exchange internals to the browser. return loginFailedResponse(tokenExchangeFailureMessage(err), connectState.clearCookie) } }, }, }, }) function callbackEmail( tokenEmail: string | undefined, configuredInboxEmail: string | undefined, hasExistingSession: boolean, ): string | null { const verifiedEmail = tokenEmail?.trim() if (verifiedEmail) return verifiedEmail if (hasExistingSession) return null return configuredInboxEmail?.trim() || null } /** * Records identifiers that let operators find a failed exchange in provider logs * without logging the authorization code, API key, password, or response body. */ function reportTokenExchangeFailure(error: unknown): void { if (!error || typeof error !== 'object' || (error as { name?: unknown }).name !== 'NylasApiError') return const details = error as { status?: unknown; requestId?: unknown; type?: unknown } console.error('OwnMail token exchange failed', { ...(typeof details.status === 'number' ? { status: details.status } : {}), ...(typeof details.requestId === 'string' ? { requestId: details.requestId } : {}), ...(typeof details.type === 'string' ? { type: details.type } : {}), }) } /** * Only use allow-listed provider outcomes in browser copy. Provider-supplied * error strings can contain implementation details or untrusted text. */ function callbackFailureMessage(providerError: string | null): string { if (providerError === 'access_denied') { return 'Sign-in was cancelled or denied. Check your OwnMail inbox email and password, then try again.' } return 'We couldn’t complete your sign-in. Please try again.' } /** Keeps invalid credentials actionable while treating all other exchange failures safely. */ function tokenExchangeFailureMessage(error: unknown): string { const status = error && typeof error === 'object' && typeof (error as { status?: unknown }).status === 'number' ? (error as { status: number }).status : undefined if (status === 400 || status === 401 || status === 403) { return 'That email or password was not accepted. Check your OwnMail inbox credentials and try again.' } if (status === 429) return 'Too many sign-in attempts. Wait a moment, then try again.' return 'We couldn’t complete your sign-in. Please try again.' } function loginFailedResponse(message: string, clearCookie?: string): Response { const html = `Sign-in failed

Couldn’t sign you in

${escapeHtml(message)}

Try again
` const headers = new Headers({ 'Content-Type': 'text/html; charset=utf-8' }) if (clearCookie) headers.set('Set-Cookie', clearCookie) return new Response(html, { status: 401, headers }) } export function escapeHtml(value: string): string { return value.replace( /[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c] as string, ) }