/** * OIDC Initiate Handler * * Initiates the OIDC authorization code flow by: * 1. Validating the provider is supported and configured * 2. Generating cryptographically secure state, code_verifier, and nonce * 3. Creating an OIDC session to track the flow * 4. Building the provider's authorization URL with PKCE * 5. Redirecting the user to the provider for authorization * * Route: GET /oidc/:provider/initiate?redirectUri={optional_redirect_url} */ import { GetHandler, HttpMethod, RouteProps, badRequest, internalServerError } from "@flink-app/flink"; import InitiateRequest from "../schemas/InitiateRequest"; import { generateState, generateSessionId, generateNonce } from "../utils/state-utils"; import { validateProvider, createOidcError, OidcErrorCodes } from "../utils/error-utils"; import { generators } from "openid-client"; /** * Path parameters for the handler */ interface PathParams { provider: string; [key: string]: string; } /** * Route configuration * This handler is registered programmatically by the plugin */ export const Route: RouteProps = { path: "/oidc/:provider/initiate", method: HttpMethod.get, }; /** * OIDC Initiate Handler * * Starts the OIDC flow by generating security parameters, creating a session, * and redirecting to the OIDC provider's authorization URL. */ const InitiateOidc: GetHandler = async ({ ctx, req }) => { const { provider } = req.params; const { redirectUri } = req.query; try { // Validate provider name format validateProvider(provider); // Get provider registry from context const providerRegistry = (ctx as any).oidcProviderRegistry; if (!providerRegistry) { throw createOidcError(OidcErrorCodes.PROVIDER_NOT_CONFIGURED, "OIDC plugin not properly initialized"); } // Get OIDC provider instance (loads dynamically if needed) const oidcProvider = await providerRegistry.getProvider(provider); // Get plugin options const { options } = ctx.plugins.oidc; // Determine redirect URI (use provided or default to callback URL) const staticProviderConfig = options.providers[provider]; const finalRedirectUri = redirectUri || staticProviderConfig?.callbackUrl || `${req.protocol}://${req.get("host")}/oidc/${provider}/callback`; // Generate cryptographically secure parameters const state = generateState(); const sessionId = generateSessionId(); const nonce = generateNonce(); const codeVerifier = generators.codeVerifier(); // Store session for state validation in callback await ctx.repos.oidcSessionRepo.create({ sessionId, state, codeVerifier, nonce, provider, redirectUri: finalRedirectUri, createdAt: new Date(), }); // Build authorization URL with PKCE and nonce const authorizationUrl = await oidcProvider.getAuthorizationUrl({ state, codeVerifier, nonce, }); // Redirect user to provider's authorization page return { status: 302, headers: { Location: authorizationUrl, }, data: {}, }; } catch (error: any) { // Handle validation errors if (error.code && Object.values(OidcErrorCodes).includes(error.code)) { return badRequest(error.message); } // Handle unexpected errors return internalServerError(error.message || "Failed to initiate OIDC flow"); } }; export default InitiateOidc;