export function normalizeBaseUrl(url: string) { return url.trim().replace(/\/+$/, ''); } export function resolveOAuthApiBase(settings: Record, fallback = 'http://localhost:3000') { const publicApi = settings.PUBLIC_API_URL?.trim(); if (publicApi) { return normalizeBaseUrl(publicApi); } const domain = settings.PROJECT_DOMAIN?.trim(); if (domain) { if (domain.startsWith('http://') || domain.startsWith('https://')) { return normalizeBaseUrl(domain); } return `https://${domain.replace(/^\/+/, '')}`; } return normalizeBaseUrl(fallback); } export interface OAuthEndpoints { issuer: string; authorizationEndpoint: string; tokenEndpoint: string; userInfoEndpoint: string; openIdConfigurationUrl: string; jwksUrl: string; } export function buildOAuthEndpoints(apiBase: string): OAuthEndpoints { const base = normalizeBaseUrl(apiBase); return { issuer: base, authorizationEndpoint: `${base}/oauth/authorize`, tokenEndpoint: `${base}/oauth/token`, userInfoEndpoint: `${base}/oauth/userinfo`, openIdConfigurationUrl: `${base}/.well-known/openid-configuration`, jwksUrl: `${base}/.well-known/jwks.json` }; } export function buildAuthorizeUrl( apiBase: string, params: { clientId: string; redirectUri: string; scope: string; userId?: string; state?: string; useStandardParams?: boolean } ) { const url = new URL(`${normalizeBaseUrl(apiBase)}/oauth/authorize`); if (params.useStandardParams ?? true) { url.searchParams.set('client_id', params.clientId); url.searchParams.set('redirect_uri', params.redirectUri); url.searchParams.set('response_type', 'code'); url.searchParams.set('scope', params.scope); if (params.state) url.searchParams.set('state', params.state); } else { url.searchParams.set('clientId', params.clientId); url.searchParams.set('redirectUri', params.redirectUri); url.searchParams.set('scope', params.scope); if (params.userId) url.searchParams.set('userId', params.userId); if (params.state) url.searchParams.set('state', params.state); } return url.toString(); }