chahinebrini 7fb76465f0 fix(mail): explicit imports for MS-OAuth handlers (createOauthPendingState et al.)
Runtime-Error im /init und /callback weil Nitro auto-import nur server/utils/
scannt, nicht server/db/. Die Helper-Funktionen (createOauthPendingState,
consumeOauthPendingState, upsertOauthMicrosoftConnection, countMailConnections)
sowie ms-oauth-utility-Functions (exchangeCodeForTokens, extractEmailFromId
Token, generate*-Helpers, MS_*-Konstanten) wurden im Code implizit referenziert
aber nicht explizit importiert.

pnpm build:backend hat das nicht gefangen (Nitro Bundle-Step ist toleranter als
strict tsc). Erster Symptom auf staging:

  ReferenceError: createOauthPendingState is not defined
    at .../routes/api/mail/oauth/microsoft/init.post.mjs:36:3

Fix: explizite import-Statements in beiden Endpoints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 21:25:43 +02:00

82 lines
2.9 KiB
TypeScript

import { createOauthPendingState } from "../../../../db/mail";
import {
MS_AUTH_BASE,
MS_REDIRECT_URI,
MS_OAUTH_SCOPES,
generateCodeVerifier,
computeCodeChallenge,
generateStateId,
} from "../../../../utils/ms-oauth";
/**
* POST /api/mail/oauth/microsoft/init
*
* Step 1 of the Microsoft OAuth PKCE flow.
* Generates a PKCE code_verifier + code_challenge, persists the state in
* oauth_pending_states (TTL 10 min), and returns the authorization URL.
*
* The native app opens this URL via expo-web-browser (WebBrowser.openAuthSessionAsync).
* After login + consent, Microsoft redirects to rebreak://auth/mail-oauth-callback?code=…&state=…
* The app deep-link handler calls POST /api/mail/oauth/microsoft/callback with { code, state }.
*
* Body (optional):
* email?: string — pre-fills login_hint + prompt logic
*
* Response:
* 200: { authorizationUrl: string }
* 401: not authenticated
*/
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
const config = useRuntimeConfig(event);
const clientId = config.msOauthClientId as string;
if (!clientId) {
throw createError({
statusCode: 500,
data: { error: "MS_OAUTH_CLIENT_ID not configured" },
});
}
const body = await readBody(event).catch(() => ({})) as { email?: string };
const hintEmail = body?.email?.trim() || null;
// ── PKCE ─────────────────────────────────────────────────────────────────
const codeVerifier = generateCodeVerifier();
const codeChallenge = computeCodeChallenge(codeVerifier);
const stateId = generateStateId();
// ── Persist state (TTL enforced at read time in callback) ─────────────────
// Clean up stale entries for this user (older than 10 min) before inserting.
await createOauthPendingState({
stateId,
userId: user.id,
codeVerifier,
email: hintEmail,
});
// ── Build authorization URL ───────────────────────────────────────────────
const params = new URLSearchParams({
client_id: clientId,
response_type: "code",
redirect_uri: MS_REDIRECT_URI,
response_mode: "query",
scope: MS_OAUTH_SCOPES,
state: stateId,
code_challenge: codeChallenge,
code_challenge_method: "S256",
// prompt=consent: always show consent screen so user sees what scopes are requested.
// Alternative: 'select_account' for re-connect flows (less friction but skips
// scope confirmation). Using 'consent' is the safer DSGVO-aligned default.
prompt: "consent",
});
if (hintEmail) {
params.set("login_hint", hintEmail);
}
const authorizationUrl = `${MS_AUTH_BASE}?${params.toString()}`;
return { authorizationUrl };
});