Skip to main content
Glama

mcp-google-sheets

oauth2-utils.ts3.97 kB
import { nanoid } from 'nanoid'; import { useSearchParams } from 'react-router-dom'; import { isNil, ThirdPartyAuthnProviderEnum } from '@activepieces/shared'; import { FROM_QUERY_PARAM, LOGIN_QUERY_PARAM, PROVIDER_NAME_QUERY_PARAM, STATE_QUERY_PARAM, } from './navigation-utils'; let currentPopup: Window | null = null; export const oauth2Utils = { openOAuth2Popup, useThirdPartyLogin, }; function useThirdPartyLogin() { const [searchParams] = useSearchParams(); return (loginUrl: string, providerName: ThirdPartyAuthnProviderEnum) => { const from = searchParams.get(FROM_QUERY_PARAM) || '/flows'; const state = { [PROVIDER_NAME_QUERY_PARAM]: providerName, [FROM_QUERY_PARAM]: from, [LOGIN_QUERY_PARAM]: 'true', }; const loginUrlWithState = new URL(loginUrl); loginUrlWithState.searchParams.set( STATE_QUERY_PARAM, JSON.stringify(state), ); window.location.href = loginUrlWithState.toString(); }; } async function openOAuth2Popup( params: OAuth2PopupParams, ): Promise<OAuth2PopupResponse> { closeOAuth2Popup(); const pckeChallenge = nanoid(43); const url = await constructUrl(params, pckeChallenge); currentPopup = openWindow(url); return { code: await getCode(params.redirectUrl), codeChallenge: params.pkce ? pckeChallenge : undefined, }; } function openWindow(url: string): Window | null { const winFeatures = [ 'resizable=no', 'toolbar=no', 'left=100', 'top=100', 'scrollbars=no', 'menubar=no', 'status=no', 'directories=no', 'location=no', 'width=600', 'height=800', ].join(', '); return window.open(url, '_blank', winFeatures); } function closeOAuth2Popup() { currentPopup?.close(); } async function generateCodeChallenge(codeVerifier: string): Promise<string> { const encoder = new TextEncoder(); const data = encoder.encode(codeVerifier); const digest = await window.crypto.subtle.digest('SHA-256', data); const base64String = btoa(String.fromCharCode(...new Uint8Array(digest))); return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); } async function constructUrl(params: OAuth2PopupParams, pckeChallenge: string) { const queryParams: Record<string, string> = { response_type: 'code', client_id: params.clientId, redirect_uri: params.redirectUrl, access_type: 'offline', state: nanoid(), prompt: 'consent', scope: params.scope, ...(params.extraParams || {}), }; if (params.prompt === 'omit') { delete queryParams['prompt']; } else if (!isNil(params.prompt)) { queryParams['prompt'] = params.prompt; } if (params.pkce) { const method = params.pkceMethod || 'plain'; queryParams['code_challenge_method'] = method; if (method === 'S256') { queryParams['code_challenge'] = await generateCodeChallenge( pckeChallenge, ); } else { queryParams['code_challenge'] = pckeChallenge; } } const url = new URL(params.authUrl); Object.entries(queryParams).forEach(([key, value]) => { if (value !== '') { url.searchParams.append(key, value); } }); return url.toString(); } function getCode(redirectUrl: string): Promise<string> { return new Promise<string>((resolve) => { window.addEventListener('message', function handler(event) { if ( redirectUrl && redirectUrl.startsWith(event.origin) && event.data['code'] ) { resolve(decodeURIComponent(event.data.code)); closeOAuth2Popup(); window.removeEventListener('message', handler); } }); }); } type OAuth2PopupParams = { authUrl: string; clientId: string; redirectUrl: string; scope: string; prompt?: 'none' | 'consent' | 'login' | 'omit'; pkce: boolean; pkceMethod?: 'plain' | 'S256'; extraParams?: Record<string, string>; }; type OAuth2PopupResponse = { code: string; codeChallenge: string | undefined; };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/activepieces/activepieces'

If you have feedback or need assistance with the MCP directory API, please join our Discord server