import type { Route } from "./+types/oauth.token"; import { exchangeCodeForTokens, refreshAccessToken, OAuthError, } from "../lib/oauth.server.ts"; /** * POST /oauth/token * * OAuth2 token endpoint. Supports two grant types: * - authorization_code: Exchange code + PKCE verifier for tokens * - refresh_token: Exchange refresh token for new token pair */ export async function action({ request }: Route.ActionArgs) { if (request.method !== "POST") { return new Response(null, { status: 405 }); } const body = await request.formData(); const grantType = body.get("grant_type") as string | null; try { if (grantType === "authorization_code") { const code = body.get("code") as string | null; const clientId = body.get("client_id") as string | null; const redirectUri = body.get("redirect_uri") as string | null; const codeVerifier = body.get("code_verifier") as string | null; if (!code || !clientId || !redirectUri || !codeVerifier) { return oauthErrorResponse("invalid_request", "Missing required parameters"); } const deviceName = body.get("device_name") as string | null; const tokens = await exchangeCodeForTokens({ code, clientId, redirectUri, codeVerifier, deviceName: deviceName ?? undefined, }); return Response.json(tokens); } if (grantType === "refresh_token") { const refreshToken = body.get("refresh_token") as string | null; const clientId = body.get("client_id") as string | null; if (!refreshToken || !clientId) { return oauthErrorResponse("invalid_request", "Missing required parameters"); } const tokens = await refreshAccessToken({ refreshToken, clientId }); return Response.json(tokens); } return oauthErrorResponse("unsupported_grant_type", `Unsupported grant type: ${grantType}`); } catch (err) { if (err instanceof OAuthError) { return oauthErrorResponse(err.code, err.message); } throw err; } } function oauthErrorResponse(error: string, description: string) { return Response.json( { error, error_description: description }, { status: 400 }, ); }