diff --git a/apps/studio/data/oauth-custom-providers/custom-provider-admin-request.ts b/apps/studio/data/oauth-custom-providers/custom-provider-admin-request.ts new file mode 100644 index 0000000000000..7c2c583c3071c --- /dev/null +++ b/apps/studio/data/oauth-custom-providers/custom-provider-admin-request.ts @@ -0,0 +1,83 @@ +import { getOrRefreshTemporaryApiKey } from '@/data/api-keys/temp-api-keys-utils' +import { handleError } from '@/data/fetchers' + +/** + * Keeps the structural `custom:` prefix literal while still encoding the + * user-controlled slug, e.g. `custom:naver` -> `custom:naver`, + * `custom:my provider` -> `custom:my%20provider`. + */ +function encodeCustomProviderIdentifier(identifier: string) { + const slug = identifier.replace(/^custom:/i, '') + return `custom:${encodeURIComponent(slug)}` +} + +function extractErrorMessage(payload: unknown, fallback: string) { + if (payload && typeof payload === 'object') { + if ('msg' in payload && typeof payload.msg === 'string') return payload.msg + if ('message' in payload && typeof payload.message === 'string') return payload.message + if ('error_description' in payload && typeof payload.error_description === 'string') { + return payload.error_description + } + } + return fallback +} + +/** + * Performs a raw request against the GoTrue custom-providers admin endpoint. + * + * We bypass the auth-js SDK's `customProviders.updateProvider` / `deleteProvider` + * helpers here because they run the identifier through `encodeURIComponent` + * (supabase-js#2383), which turns the required `custom:` prefix into `custom%3A`. + * The GoTrue endpoint does not decode the path segment, so the encoded colon + * fails its `identifier must start with 'custom:'` validation. + * + * TODO: remove this and return to the SDK helpers once GoTrue decodes the + * custom-provider path segment (supabase-js#2383). + */ +export async function customProviderAdminRequest({ + method, + projectRef, + clientEndpoint, + identifier, + body, +}: { + method: 'PUT' | 'DELETE' + projectRef: string + clientEndpoint: string + identifier: string + body?: unknown +}) { + const { apiKey } = await getOrRefreshTemporaryApiKey(projectRef) + const path = encodeCustomProviderIdentifier(identifier) + + const response = await fetch(`${clientEndpoint}/auth/v1/admin/custom-providers/${path}`, { + method, + headers: { + apikey: apiKey, + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + ...(body !== undefined ? { body: JSON.stringify(body) } : {}), + }) + + if (!response.ok) { + let payload: unknown + try { + payload = await response.json() + } catch { + // response had no JSON body + } + handleError({ + message: extractErrorMessage(payload, `Request failed with status ${response.status}`), + code: response.status, + }) + } + + if (method === 'DELETE') return null + + try { + return await response.json() + } catch { + return null + } +} diff --git a/apps/studio/data/oauth-custom-providers/oauth-custom-provider-delete-mutation.ts b/apps/studio/data/oauth-custom-providers/oauth-custom-provider-delete-mutation.ts index 384e801151aba..b3335b6f0ce7c 100644 --- a/apps/studio/data/oauth-custom-providers/oauth-custom-provider-delete-mutation.ts +++ b/apps/studio/data/oauth-custom-providers/oauth-custom-provider-delete-mutation.ts @@ -1,9 +1,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' +import { customProviderAdminRequest } from './custom-provider-admin-request' import { oAuthCustomProvidersKeys } from './keys' -import { handleError } from '@/data/fetchers' -import { createProjectSupabaseClient } from '@/lib/project-supabase-client' import type { ResponseError, UseCustomMutationOptions } from '@/types' export type OAuthCustomProviderDeleteVariables = { @@ -21,10 +20,12 @@ export async function deleteOAuthCustomProvider({ if (!clientEndpoint) throw new Error('Client endpoint is required') if (!identifier) throw new Error('Provider identifier is required') - const supabaseClient = await createProjectSupabaseClient(projectRef, clientEndpoint) - const { error } = await supabaseClient.auth.admin.customProviders.deleteProvider(identifier) - - if (error) handleError(error) + await customProviderAdminRequest({ + method: 'DELETE', + projectRef, + clientEndpoint, + identifier, + }) return null } diff --git a/apps/studio/data/oauth-custom-providers/oauth-custom-provider-update-mutation.ts b/apps/studio/data/oauth-custom-providers/oauth-custom-provider-update-mutation.ts index 9caf8c212dc24..41b892b1f6e79 100644 --- a/apps/studio/data/oauth-custom-providers/oauth-custom-provider-update-mutation.ts +++ b/apps/studio/data/oauth-custom-providers/oauth-custom-provider-update-mutation.ts @@ -2,9 +2,8 @@ import type { UpdateCustomProviderParams } from '@supabase/auth-js' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' +import { customProviderAdminRequest } from './custom-provider-admin-request' import { oAuthCustomProvidersKeys } from './keys' -import { handleError } from '@/data/fetchers' -import { createProjectSupabaseClient } from '@/lib/project-supabase-client' import type { ResponseError, UseCustomMutationOptions } from '@/types' export type OAuthCustomProviderUpdateVariables = UpdateCustomProviderParams & { @@ -23,14 +22,13 @@ export async function updateOAuthCustomProvider({ if (!clientEndpoint) throw new Error('Client endpoint is required') if (!identifier) throw new Error('Provider identifier is required') - const supabaseClient = await createProjectSupabaseClient(projectRef, clientEndpoint) - const { data, error } = await supabaseClient.auth.admin.customProviders.updateProvider( + return customProviderAdminRequest({ + method: 'PUT', + projectRef, + clientEndpoint, identifier, - params - ) - - if (error) handleError(error) - return data! + body: params, + }) } type OAuthCustomProviderUpdateData = Awaited> diff --git a/apps/www/components/Hero/Hero.tsx b/apps/www/components/Hero/Hero.tsx index 73be8cf56cee2..ca3a1445518cc 100644 --- a/apps/www/components/Hero/Hero.tsx +++ b/apps/www/components/Hero/Hero.tsx @@ -1,6 +1,5 @@ import SectionContainer from '~/components/Layouts/SectionContainer' import { useSendTelemetryEvent } from '~/lib/telemetry' -import AnnouncementBadge from 'components/Announcement/Badge' import Link from 'next/link' import { Button } from 'ui' @@ -14,11 +13,6 @@ const Hero = () => {
- -

Build in a weekend