From 1da9dd845bce9c7395de8bbff55de790e8770abe Mon Sep 17 00:00:00 2001 From: Daniel Woelfel Date: Tue, 11 Aug 2026 12:34:10 -0700 Subject: [PATCH 1/4] update all oauth providers to allow editing the config from the dashboard --- client/www/components/dash/AppAuth.tsx | 16 +- client/www/components/dash/auth/Apple.tsx | 219 +++++++++++++++++-- client/www/components/dash/auth/Clerk.tsx | 147 ++++++++++++- client/www/components/dash/auth/Firebase.tsx | 135 +++++++++++- client/www/components/dash/auth/GitHub.tsx | 36 ++- client/www/components/dash/auth/LinkedIn.tsx | 35 ++- client/www/components/dash/auth/shared.tsx | 124 ++++++++++- 7 files changed, 672 insertions(+), 40 deletions(-) diff --git a/client/www/components/dash/AppAuth.tsx b/client/www/components/dash/AppAuth.tsx index 29c4dbde77..e152636d29 100644 --- a/client/www/components/dash/AppAuth.tsx +++ b/client/www/components/dash/AppAuth.tsx @@ -330,7 +330,13 @@ function ClientItem({ /> ); case 'apple': - return ; + return ( + + ); case 'github': return ( ); case 'firebase': - return ; + return ( + + ); default: return null; } diff --git a/client/www/components/dash/auth/Apple.tsx b/client/www/components/dash/auth/Apple.tsx index ef083be7c0..5e3e7c5f98 100644 --- a/client/www/components/dash/auth/Apple.tsx +++ b/client/www/components/dash/auth/Apple.tsx @@ -1,13 +1,11 @@ import { FormEventHandler, useContext, useState } from 'react'; -import { errorToast } from '@/lib/toast'; +import { errorToast, successToast } from '@/lib/toast'; import { TokenContext } from '@/lib/contexts'; import { Button, Copyable, TextInput, TextArea } from '@/components/ui'; import * as Collapsible from '@radix-ui/react-collapsible'; import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/solid'; -import logo from '../../../public/img/apple_logo_black.svg'; -import Image from 'next/image'; import { messageFromInstantError } from '@/lib/errors'; -import { addProvider, addClient, findName } from './shared'; +import { addProvider, addClient, findName, updateClient } from './shared'; import { InstantApp, InstantIssue, @@ -20,20 +18,213 @@ import { APPLE_TOKEN_ENDPOINT, } from '@instantdb/platform'; -export function AppleClient({ client }: { client: OAuthClient }) { +// Editor for an Apple client's credentials. The Services ID, Team ID, and Key +// ID are readable and prefilled; the Private Key is write-only (never returned +// to the dashboard), so leaving it blank keeps the current key. The private key +// and the Team/Key IDs are combined server-side only at web sign-in time, so +// each can be updated independently. +function AppleCredentialsEditor({ + app, + client, + onUpdateClient, +}: { + app: InstantApp; + client: OAuthClient; + onUpdateClient: (client: OAuthClient) => void; +}) { + const token = useContext(TokenContext); + const [isEditing, setIsEditing] = useState(false); + const [servicesId, setServicesId] = useState(client.client_id || ''); + const [teamId, setTeamId] = useState(client.meta?.teamId || ''); + const [keyId, setKeyId] = useState(client.meta?.keyId || ''); + const [privateKey, setPrivateKey] = useState(''); + const [isSaving, setIsSaving] = useState(false); + + const resetFields = () => { + setServicesId(client.client_id || ''); + setTeamId(client.meta?.teamId || ''); + setKeyId(client.meta?.keyId || ''); + setPrivateKey(''); + }; + + const openEditor = () => { + resetFields(); + setIsEditing(true); + }; + + const cancel = () => { + setIsEditing(false); + resetFields(); + }; + + const validationError = () => { + if (!servicesId) { + return 'Missing Apple Services ID'; + } + // Team ID and Key ID are only meaningful together (web redirect flow). + if ((teamId || keyId) && !(teamId && keyId)) { + return 'Both Team ID and Key ID are required for the Web redirect flow.'; + } + }; + + const handleSave = async () => { + const err = validationError(); + if (err) { + errorToast(err, { autoClose: 5000 }); + return; + } + try { + setIsSaving(true); + const resp = await updateClient({ + token, + appId: app.id, + oauthClientID: client.id, + body: { + client_id: servicesId, + meta: { teamId, keyId }, + ...(privateKey ? { client_secret: privateKey } : {}), + }, + }); + onUpdateClient(resp.client); + cancel(); + successToast('Credentials updated'); + } catch (e) { + console.error(e); + const msg = + messageFromInstantError(e as InstantIssue) || + 'Error updating credentials.'; + errorToast(msg, { autoClose: 5000 }); + } finally { + setIsSaving(false); + } + }; + + if (!isEditing) { + return ( +
+ + + {client.meta?.teamId ? ( + + ) : null} + + {client.meta?.keyId ? ( + + ) : null} + +
+ +
+
+ ); + } + + return ( +
{ + e.preventDefault(); + handleSave(); + }} + autoComplete="off" + data-lpignore="true" + data-1p-ignore="true" + data-bwignore="true" + data-form-type="other" + > + + Services ID from{' '} + + Identifiers + + + } + /> + + Team ID from{' '} + + Membership details + + + } + /> + + Key ID from{' '} + + Keys + + + } + /> +