import { QODER_CONFIG } from "../constants/oauth"; export const qoder = { config: QODER_CONFIG, flowType: "authorization_code", buildAuthUrl: (config, redirectUri, state) => { if (!config?.enabled || !config?.authorizeUrl) { return null; } const params = new URLSearchParams({ loginMethod: config.extraParams.loginMethod, type: config.extraParams.type, redirect: redirectUri, state: state, client_id: config.clientId, }); return `${config.authorizeUrl}?${params.toString()}`; }, exchangeToken: async (config, code, redirectUri) => { if (!config?.enabled || !config?.tokenUrl) { throw new Error( "Qoder browser OAuth is experimental and disabled by default. Configure QODER_OAUTH_* environment variables or use a Personal Access Token." ); } const headers: Record = { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", }; if (config.clientSecret) { const basicAuth = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64"); headers.Authorization = `Basic ${basicAuth}`; } const bodyParams: Record = { grant_type: "authorization_code", code: code, redirect_uri: redirectUri, client_id: config.clientId, }; if (config.clientSecret) { bodyParams.client_secret = config.clientSecret; } const response = await fetch(config.tokenUrl, { method: "POST", headers: headers, body: new URLSearchParams(bodyParams), }); if (!response.ok) { const error = await response.text(); throw new Error(`Token exchange failed: ${error}`); } return await response.json(); }, postExchange: async (tokens) => { if (!QODER_CONFIG.enabled || !QODER_CONFIG.userInfoUrl) { throw new Error( "Qoder browser OAuth is experimental and disabled by default. Configure QODER_OAUTH_* environment variables or use a Personal Access Token." ); } const userInfoRes = await fetch( `${QODER_CONFIG.userInfoUrl}?accessToken=${encodeURIComponent(tokens.access_token)}`, { headers: { Accept: "application/json" } } ); const result = userInfoRes.ok ? await userInfoRes.json() : {}; const userInfo = result.success ? result.data : {}; return { userInfo }; }, mapTokens: (tokens, extra) => ({ accessToken: tokens.access_token, refreshToken: tokens.refresh_token, expiresIn: tokens.expires_in, apiKey: extra?.userInfo?.apiKey, email: extra?.userInfo?.email || extra?.userInfo?.phone, displayName: extra?.userInfo?.nickname || extra?.userInfo?.name, }), };