[in-progress] [cli] dev credentials for google#2591
Conversation
Backend foundation for shared OAuth credentials. Splits the server- only changes out of the full sharedcred PR so it can be reviewed on its own; the dashboard UI follows in a separate PR rebased on top. ## What this enables Apps can opt an OAuth client into Instant-provided dev credentials instead of supplying their own client_id / client_secret. Primary use case: a developer can test Google sign-in on localhost / exp:// without ever touching Google Cloud Console. ## Config - New flag `shared-oauth-clients` in instant-config (keyed by provider name, contains clientId + encryptedClientSecretHexString). See `server/flags-config/instant.schema.ts` + `server/src/instant/ flags.clj`. ## Model changes server/src/instant/model/app_oauth_client.clj - `use-shared-credentials?` predicate on a client's meta. - `get-shared-credential!` (throws when absent) and `get-provider-by-id!` (canonical provider-row lookup; returns the full record so callers destructure). - `->OAuthClient` now derives provider-name from provider_id and, on a shared-credential client, substitutes the app's row with the shared cred values at request time. No more meta.providerName. - `assert-shared-credentials-allowed!` enforces the 100-user cap (development-only guardrail). Callers: oauth-clients-post, update-oauth-client, and the new-user branch of upsert-oauth-link!. - `update!` extended to accept :client-id and :encrypted-client- secret so the upgrade-from-shared flow is an in-place update instead of delete + recreate. server/src/instant/model/app_authorized_redirect_origin.clj - `shared-credentials-default-match` auto-allows localhost (http/https) and exp:// for shared-credential clients, without them having to register those origins manually. Custom domains still go through the existing authorized-origins list. server/src/instant/model/app_user.clj - `users-at-least?` bounded existence check: SELECT COUNT(*) over a LIMIT-n subquery of the $users.id attr. Used by the cap above. ## Route changes server/src/instant/runtime/routes.clj - Origin/redirect-uri validation consolidated behind `assert-authorized-redirect-uri!` / `assert-authorized-request-origin!`. Both branch on use-shared-credentials? to fall back to the default-match. - `oauth-id-token-callback` now validates origins too (previously gated on client_secret, which shared-cred rows don't have on the raw record). - 100-user cap checked inside upsert-oauth-link!'s new-user branch so returning users are never blocked. server/src/instant/dash/routes.clj - oauth-clients-post / update-oauth-client: when meta.useSharedCredentials is true, require that a shared cred exists for the provider (via get-shared-credential!) and enforce the user cap before saving. - update-oauth-client now accepts client_id / client_secret so the dashboard's in-place "update credentials" action can swap creds without destroying and recreating the client.
Dashboard UI on top of the server-side foundation in
sharedcred-backend. Users can now pick "Use dev credentials" when
adding a Google OAuth client and sign in on localhost / Expo with
no Google Cloud Console setup. When they're ready for production,
they can swap in their own client_id / client_secret in place.
## AddClientForm (Google)
- Toggle between "Use dev credentials" and "Use my own" (web clients
only; native clients still require a real client_id).
- Shared-creds mode omits the client_id / client_secret inputs and
shows a brief note explaining what's happening.
- GitHub: drop `meta: { providerName: 'github' }` from the create
call (server derives provider-name from provider_id now).
## GoogleClient (per-client panel)
- New `CredentialsEditor` subcomponent that owns the credentials
section and encapsulates the upgrade / rotate flow:
- Shared-credentials clients see the "Using Instant's dev
credentials" notice with "Set custom credentials" to opt out.
- Custom-credentials clients see the existing client_id Copyable
with an "Update credentials" button for rotating in place.
- Both share the same edit form (inputs + Save / Cancel) wrapped
in a `<form>` with Enter-to-submit + password-manager opt-outs.
- Update uses the new in-place `updateClient` endpoint added in the
backend PR, so credential rotation no longer destroys and
recreates the client.
- `GoogleClient` renamed from `Client` to match the other provider
panels' naming.
## shared.tsx
- Generic `updateClient` helper: `{ token, appId, oauthClientID,
body }`. Body passes through unchanged to `POST
/dash/apps/:app_id/oauth_clients/:id`.
## MainDashLayout
- Pin the dashboard wrapper with `fixed inset-0` on the outer
column. Decouples the dashboard from html/body height cascade so
the "body shows through below the app" seam stops recurring (the
same line had been bounced between h-full / min-h-full /
h-[100dvh] three times; each trade-off was a symptom of the same
underlying coupling).
## Copytext
- Lighter background in light mode so the inline URL badges don't
read as dark pills on the grey dashboard panel.
Mirrors the dashboard's "Use dev credentials" toggle for `instant-cli auth client add`. With `--dev-credentials` set (or picked in the interactive credential-mode selector) on --type google --app-type web, the CLI creates an OAuth client with meta.useSharedCredentials: true and omits client_id / client_secret / redirect_to. The server substitutes Instant-provided credentials at request time, so the client works on localhost / Expo with no Google Cloud Console setup. Interactive flow for Google web adds a new step between app type and name: a two-option selector (Use dev credentials / Use my own) defaulted to dev — same default the dashboard uses. Skipped when --client-id or --dev-credentials is already supplied. Validation: - --dev-credentials cannot be combined with --client-id, --client- secret, or --custom-redirect-uri. - --dev-credentials is only supported for --app-type web. - --dev-credentials is only supported for --type google at the moment. Success summary for the shared-creds path replaces the "Add this redirect URI in Google Console" block with a "No setup required" note and a hint pointing at `auth client update` for the production upgrade path.
New `instant-cli auth client update` subcommand. Wraps the server's
in-place update route at POST /dash/apps/:app_id/oauth_clients/:id, so
users can rotate a client_id / client_secret pair without destroying
and recreating the client (which churned the database id and broke
in-flight sessions).
Covers two flows with one command, mirroring the dashboard's unified
CredentialsEditor:
- Rotate: an existing custom-cred client gets new credentials.
- Upgrade: a shared-cred client (created with --dev-credentials) gets
its own client_id / client_secret, transitioning meta.
useSharedCredentials from true to false.
Flags:
- --id <id> identify by database id (exclusive with --name)
- --name <name> identify by client name
- --client-id <val> new client_id
- --client-secret <val> new client_secret (prompt masked)
- -a --app <app-id> app; same default as the other subcommands
Interactive behaviour:
- No --id / --name → UI.Select picker listing clients. Shared-cred
clients show a "(dev credentials)" suffix so the user can tell them
apart.
- No field flags → prompt for client_id and (for web clients)
client_secret. Native Google clients (meta.appType === 'ios' /
'android' / 'button-for-web') skip the secret prompt since they
don't have one.
- Supplying one field explicitly skips the other prompt — partial
updates are fine.
--yes mode:
- Requires --id or --name (error matches `delete`).
- Requires at least one of --client-id / --client-secret.
The update body always sends meta: { useSharedCredentials: false }.
Idempotent on already-custom clients; flips shared-cred clients over.
This matches the dashboard's "Update credentials" / "Set custom
credentials" behaviour — the operation is "I'm providing my own
credentials now."
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
auth client add + new auth client update|
View Vercel preview at instant-www-js-sharedcred-cli-jsv.vercel.app. |
ea6cd20 to
6ca4780
Compare
CLI counterpart to the dashboard changes in #2553. Stacked on top of
sharedcredso the diff here is only the CLI.What's new at a glance
add --type googlealways requires a realclient_id(+ secret for web).add --type google --app-type web --dev-credentialscreates a working client with zero setup.delete+ re-add, which churns the client's db id and breaks any in-flight refresh tokens.updatesubcommand swaps credentials in place.addfor Google + web dumps the user straight into "paste yourclient_id".Why now
The backend PR (#2588) and the dashboard PR (#2553) already landed these capabilities. Without the CLI, a developer who bootstraps their app with
instant-cli initstill has to open the dashboard just to create an OAuth client — defeating the "no setup" value prop of shared dev credentials. This PR closes that loop.1.
auth client add --dev-credentialsInteractive (non-
--yes) flow — what the user seesThe credential-mode selector is deliberately not a Yes/No. Mirroring the dashboard's two-option toggle (
Use dev credentials/Use my own) makes the alternative discoverable — users who don't know shared creds exist still learn about them when they runaddfor the first time.When the selector is skipped
The selector exists to resolve ambiguity. When the user's flags already commit to a mode, we skip it:
--dev-credentials--client-id <x>(or--client-secret)--yes--app-type≠webValidation
Three new error paths, each with a specific message:
Request payload (shared-creds path vs existing web path)
POST /dash/apps/:app_id/oauth_clients { provider_id: "prov-...", client_name: "google-web", - client_id: "123-abc.apps.googleusercontent.com", - client_secret: "GOCSPX-...", authorization_endpoint: "https://accounts.google.com/o/oauth2/v2/auth", token_endpoint: "https://oauth2.googleapis.com/token", discovery_endpoint: "https://accounts.google.com/.well-known/openid-configuration", - redirect_to: "https://api.instantdb.com/runtime/oauth/callback", meta: { appType: "web", skipNonceChecks: true, + useSharedCredentials: true, } }Endpoints stay the same on purpose — the record shape should be uniform whether or not it uses shared creds. The server substitutes
client_id/client_secretat request time, triggered bymeta.useSharedCredentials.2.
auth client update(new subcommand)What it does
One subcommand covers two flows, mirroring the dashboard's unified
CredentialsEditor:--dev-credentials) → its own credentials;meta.useSharedCredentialsflips fromtruetofalse.The implementation is just one POST —
update-oauth-clienton the server (shipped in #2588) accepts either or both ofclient_id/client_secretalong with ametamerge. The CLI always sendsmeta: { useSharedCredentials: false }, which is idempotent on already-custom clients and performs the upgrade on shared-cred ones.Interactive flow
The
(dev credentials)suffix in the picker comes frommeta.useSharedCredentialson each client — critical signal for telling which clients are currently on shared creds.Prompt decision tree
The partial-update shape ("supplying only one field is fine") matches how the API works — both fields are optional on the server — and matches what users actually want when rotating just the secret.
--yesmodeTwo errors guard
--yes:Changes at file level
client/packages/cli/src/commands/auth/client/add.tsselectCredentialModeeffect — UI.Select that resolves to'dev' | 'custom'.handleGoogleClientbranches on shared-creds mode: skips id/secret/redirect prompts, sends the shared-creds payload, prints the new success summary.authClientAddCmdbefore dispatch.client/packages/cli/src/commands/auth/client/update.ts(new)authClientUpdateCmdeffect.resolveClienthelper that handles--id/--name/ picker + the(dev credentials)suffix rendering.promptClientId/promptClientSecreteffects (had to inline rather than useoptOrPrompt— itsskipIfhelper errors when a flag value is present, which is wrong for partial updates where supplying a flag should mean "don't prompt for this one but do still accept it").client/packages/cli/src/lib/oauth.tsupdateOAuthClient({ appId?, oauthClientId, body })— thin POST wrapper.bodyis pass-through so the helper doesn't get opinionated about the update shape.client/packages/cli/src/index.ts--dev-credentialsflag + help-text entry onauthClientAddDef.authClientUpdateDefcommand def mirroringauthClientDeleteDeffor identification + adding credential flags.client/packages/cli/__tests__/authClientAddGoogle.test.ts--client-id→ prompts for id" test: the new credential-mode selector lands before the id prompt, so the test now asserts on prompts[0] (selector) and prompts[1] (id) explicitly.--yesshared-creds success,--dev-credentials + --client-iderror,--dev-credentials + --app-type ioserror, interactive "pick dev creds" success.client/packages/cli/__tests__/authClientUpdate.test.ts(new)--yeshappy/error paths, interactive picker, partial updates, and native-client secret-prompt skipping.Design decisions
--dev-credentialsrather than--shared-credentials. Matches the dashboard copy ("Use dev credentials") which itself was chosen because "dev" is immediately meaningful to the user — they know what they're opting into.dev, notcustom. Mirrors the dashboard. The zero-setup path is the path most users probably want on first contact; anyone who knows they want their own creds will skip past the selector anyway (either via--client-idon the command line or by picking option 2).updatealways setsmeta.useSharedCredentials: false, regardless of the client's current state. The operation is semantically "I'm providing my own credentials now." If we ever need the inverse (go back to dev creds after setting custom ones), that would be an explicit separate flag.updatev1 scope is just--client-id/--client-secret, not--custom-redirect-uri/--name/ other meta fields. Keeps the diff small and matches what the dashboard's "Update credentials" button does today. Can extend later without API changes.--client-id, we don't nag for--client-secret— they told us what they wanted.Pre-existing bug worth a follow-up (not fixed here)
The CLI hardcodes
skipNonceChecks: truefor all Google clients it creates (add.ts). The dashboard usesisNative(appType)—trueonly for iOS/Android,falsefor Web / Button-for-Web. Web clients shouldn't be opting out of nonce checks. Worth a one-line fix in a follow-up.Test plan
pnpm --filter instant-cli exec vitest run— 102 unit tests pass across 8 files. 30 e2e failures are unrelated (same failure set onmain; they require infrastructure not available locally).pnpm exec tsc -p packages/cli/tsconfig.build.json --noEmit— clean (ignoring the pre-existingminimisttype error on main).instant-cli auth client add --type google --app-type web --dev-credentials --name g-dev→ shared-cred client created; sign in onhttp://localhost:3000succeeds.instant-cli auth client add --type google --app-type web→ interactive selector appears, dev is default.instant-cli auth client update --name g-dev --client-id X --client-secret Y→ credentials rotated, same db id,meta.useSharedCredentialsclears tofalse.instant-cli auth client update→ picker lists clients;g-devshows(dev credentials)suffix.updateon the now-custom client → rotates, no meta change (idempotent).