import { createClient } from '@/lib/supabase/server' import { NextResponse } from 'next/server' import { createAuthCode } from '@/lib/auth/oauth-codes' /** * OAuth 2.0 Authorization Endpoint. * * GET → show consent page (or redirect to login) * POST → process consent, create auth code, redirect to callback * * The API key is NOT created here — it's created in the token endpoint * after PKCE verification, preventing orphaned keys on abandoned flows. */ // Allowed redirect URI patterns — prevent open redirect attacks const ALLOWED_REDIRECT_PATTERNS = [ /^https:\/\/claude\.ai\/api\//, // Claude.ai API callbacks (connector IDs vary in path) /^https:\/\/claude\.com\/api\//, // Claude.com API callbacks /^http:\/\/localhost(:\d+)?\//, // Local development /^http:\/\/127\.0\.0\.1(:\d+)?\//, // Local development ] function isAllowedRedirectUri(uri: string): boolean { return ALLOWED_REDIRECT_PATTERNS.some((pattern) => pattern.test(uri)) } function buildLoginRedirect(request: Request): Response { const url = new URL(request.url) const next = `${url.pathname}${url.search}` return NextResponse.redirect( new URL(`/login?next=${encodeURIComponent(next)}`, url.origin) ) } function errorRedirect(redirectUri: string, state: string | null, error: string, desc: string): Response { const url = new URL(redirectUri) url.searchParams.set('error', error) url.searchParams.set('error_description', desc) if (state) url.searchParams.set('state', state) return NextResponse.redirect(url.toString()) } /** * GET /api/mcp-oauth/authorize — show consent page */ export async function GET(request: Request) { const url = new URL(request.url) const redirectUri = url.searchParams.get('redirect_uri') const state = url.searchParams.get('state') const codeChallenge = url.searchParams.get('code_challenge') const codeChallengeMethod = url.searchParams.get('code_challenge_method') || 'S256' const responseType = url.searchParams.get('response_type') if (responseType !== 'code') { return NextResponse.json( { error: 'unsupported_response_type' }, { status: 400 } ) } if (!redirectUri) { return NextResponse.json( { error: 'invalid_request', error_description: 'redirect_uri is required' }, { status: 400 } ) } // Validate redirect_uri against allowlist (prevents open redirect) if (!isAllowedRedirectUri(redirectUri)) { return NextResponse.json( { error: 'invalid_request', error_description: 'redirect_uri is not allowed' }, { status: 400 } ) } if (codeChallengeMethod !== 'S256') { return NextResponse.json( { error: 'invalid_request', error_description: 'Only S256 code_challenge_method is supported' }, { status: 400 } ) } // Check if user is logged in const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() if (!user) { return buildLoginRedirect(request) } // Get company name for the consent page const { data: settings } = await supabase .from('company_settings') .select('company_name') .eq('user_id', user.id) .single() const companyName = settings?.company_name || user.email // Render consent page const html = `
En extern applikation vill ansluta till ditt gnubok-konto.