import * as wmill from "jsr:@windmill/windmill"; type ClickupCredentials = { client_id: string; expires_at: number; access_token: string; client_secret: string; }; // Function to initiate OAuth flow and get initial tokens export async function get_initial_token( client_id: string, client_secret: string, redirect_uri: string, ) { const state = crypto.randomUUID(); const authUrl = `https://app.clickup.com/api?client_id=${encodeURIComponent(client_id) }&redirect_uri=${encodeURIComponent(redirect_uri)}&state=${encodeURIComponent(state) }&response_type=code&scope=*`; console.log("Please visit this URL to authorize the application:", authUrl); return { auth_url: authUrl, state: state, next_step: "After authorization, you will receive a code in the redirect URI. Use this code with exchange_code_for_token function and verify the state parameter matches.", }; } // Function to exchange authorization code for tokens export async function exchange_code_for_token( code: string, client_id: string, client_secret: string, expected_state?: string, received_state?: string, ) { if (expected_state && received_state) { if (expected_state !== received_state) { throw new Error("State parameter mismatch - possible CSRF attack"); } } const formData = new URLSearchParams({ client_id: client_id, client_secret: client_secret, code: code, grant_type: "authorization_code" }); try { const response = await fetch("https://api.clickup.com/api/v2/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: formData.toString(), }); if (!response.ok) { const errorText = await response.text(); console.error("Token exchange error details:", { status: response.status, statusText: response.statusText, error: errorText, requestBody: formData.toString(), }); throw new Error( `Token exchange failed: ${response.status} ${errorText}`, ); } const tokens = await response.json(); const credentials: ClickupCredentials = { client_id, client_secret, access_token: tokens.access_token, expires_at: Date.now() + (tokens.expires_in * 1000), }; // Using setResource with string path instead of object await wmill.setResource("f/clickup/clickup_prod_creds", JSON.stringify(credentials)); return { status: "initialized", expires_at: new Date(credentials.expires_at).toISOString(), }; } catch (error) { console.error("Detailed error:", error); throw error; } } export async function main( clickupResource: ClickupCredentials, mode: "check" | "refresh" | "initialize" = "check", code?: string, redirect_uri?: string, state?: string, expected_state?: string, ) { if (mode === "initialize") { if (!clickupResource.client_id || !clickupResource.client_secret) { throw new Error( "Client ID and Client Secret are required for initialization", ); } if (!redirect_uri) { throw new Error("Redirect URI is required for initialization"); } return await get_initial_token( clickupResource.client_id, clickupResource.client_secret, redirect_uri, ); } if (!code) { throw new Error("Clickup oAuth CODE required to get token. Use Initialize step first."); } return await exchange_code_for_token( code, clickupResource.client_id, clickupResource.client_secret, expected_state, state, ); }