[cli] auth client update#2624
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an interactive "auth client update" CLI command, extracts shared CLI prompt/validation helpers, refactors add/delete to use them, introduces Clerk publishable-key decoding, extends OAuth client schemas and update APIs to handle shared/dev credentials and discovery endpoints, and updates server routes/models and tests accordingly. ChangesAuth client update + shared CLI helpers
Sequence DiagramsequenceDiagram
actor User
participant CLI as instant-cli (command)
participant Shared as shared.ts (prompts & helpers)
participant OAuthLib as CLI OAuth lib
participant Server as Server (routes/model)
participant DB as Database
User->>CLI: run "auth client update" (flags / interactive)
CLI->>OAuthLib: findClientByIdOrName(id/name) or getAppsAuth()
OAuthLib-->>CLI: { auth, client }
CLI->>Shared: request prompts / resolve mode
Shared->>User: show prompts
User-->>Shared: answers
Shared-->>CLI: validated inputs
CLI->>OAuthLib: updateOAuthClient({clientId, clientSecret, discoveryEndpoint, redirectTo, meta, useSharedCredentials})
OAuthLib->>Server: POST /dash/apps/{app}/oauth_clients/{id}
Server->>Server: validate-discovery-endpoint! (if present)
Server->>DB: persist client changes
DB-->>Server: saved row
Server-->>OAuthLib: updated client payload
OAuthLib-->>CLI: updated client
CLI-->>User: confirmation + redirect setup messages
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
|
View Vercel preview at instant-www-js-cli-codex-update-jsv.vercel.app. |
8796533 to
7eadc72
Compare
7eadc72 to
c77d4a0
Compare
c77d4a0 to
e5e8bfe
Compare
e5e8bfe to
77bd498
Compare
77bd498 to
3b34ff8
Compare
3b34ff8 to
890d2ef
Compare
890d2ef to
223b4b4
Compare
223b4b4 to
2b1cc68
Compare
7033cbc to
01783f4
Compare
01783f4 to
ac019dd
Compare
| ) { | ||
| const { yes } = yield* GlobalOpts; | ||
| const publishableKey = yield* optOrPromptIf(opts, 'publishable-key', { | ||
| promptIf: !yes && !hasFlag(opts, 'publishable-key'), |
There was a problem hiding this comment.
What's the reasoning for the optOrPromptIf, is it just an inverted case of optOrPrompt's skipIf?
There was a problem hiding this comment.
Not quite. The reason is that update has a third case that skipIf doesn't model.
optOrPrompt handles:
- Parse the flag if it is present
- If the flag is missing, prompt for it
skipIf means the option is not allowed at all. If skipIf is true, we should reject the flag and also avoid prompting.
For update, we need a different behavior:
- Parse the flag if it is present
- If the flag is missing, prompt only when this update path needs the value
- If the flag is missing and this update path does not need the value, leave it unset
For example:
instant-cli auth client update --name github-prod --custom-redirect-uri https://example.com/callback
This should update only the redirect URI. We still want to allow --client-id and --client-secret if the user provides them, but we don't want to prompt for them when they're omitted.
I do wonder if we need to re-think the abstraction though. It's a bit hard to reason about. I changed the name to optOrPromptWhenNeeded, but perhaps I can think of something a bit different. Let me mull on this
There was a problem hiding this comment.
Update: I will keep optOrPromptWhenNeeded in this PR, but propose a refactor in a separate PR.
My thinking is that we can 'decompose' optOrPromptIf, into a series of pipe operators:
const name = yield* parseStringFlag(opts.name, '--name').pipe(
orAsk(clientNamePrompt),
orFail('--name'),
)
This could let us be more explicit.
I don't want to make the change here though, as it would be quite involved.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
client/packages/cli/__tests__/authClientUpdate.test.ts (1)
265-278: ⚡ Quick winInteractive Google-web → dev-credentials: missing
clientId: null/clientSecret: nullassertionsThe
toMatchObjectat line 273 doesn't assertclientId: nullandclientSecret: null, even though the realupdateGoogleToDevCredentialsexplicitly passes those asnull(Context snippet 2). If a regression causes the implementation to omit those fields, this test won't catch it.🛡️ Tighter assertions
expect(updatedClients[0]).toMatchObject({ oauthClientId: 'google-web', + clientId: null, + clientSecret: null, useSharedCredentials: true, redirectTo: null, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@client/packages/cli/__tests__/authClientUpdate.test.ts` around lines 265 - 278, The test "interactive Google web update can select dev credentials" currently asserts properties on updatedClients[0] but omits clientId and clientSecret; update the assertion in that test to also require clientId: null and clientSecret: null so the test will fail if updateGoogleToDevCredentials stops passing those fields — modify the expect(updatedClients[0]).toMatchObject call in the test to include clientId: null and clientSecret: null.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@client/packages/cli/src/commands/auth/client/shared.ts`:
- Around line 205-210: The function domainFromClerkKey currently splits the key
on '_' and takes the last chunk, which breaks when the base64-encoded domain
contains underscores; change it to take the entire suffix after the first '_'
(e.g., use key.indexOf('_') and substring/slice from that position) so the full
base64 payload is decoded, then base64Decode and strip the leading '$' as
before; update domainFromClerkKey to handle missing '_' or decode errors
gracefully (return null) and reference domainFromClerkKey and base64Decode when
making the change.
In `@client/packages/cli/src/commands/auth/client/update.ts`:
- Around line 308-327: The code still triggers interactive prompts when the CLI
is run non-interactively with --yes; update the prompt logic so prompts are
suppressed when the non-interactive flag is present and instead surface an error
if required credentials are missing. Specifically, change the promptIf
expressions used in optOrPromptIf calls (for client-id, client-secret, redirect
URI) to also check that hasFlag(opts, 'yes') is false (e.g., promptIf:
shouldPromptClientId && !hasFlag(opts, 'yes')), and add an explicit pre-check
before calling optOrPromptIf (or set required accordingly) that throws or
returns a clear error when mustCollectCredentials is true but hasFlag(opts,
'yes') is true and the corresponding opts flag (client-id/client-secret) is not
provided; touch the variables shouldPromptClientId, shouldPromptClientSecret,
shouldPromptRedirectUri and the optOrPromptIf invocations to implement this
behavior.
---
Nitpick comments:
In `@client/packages/cli/__tests__/authClientUpdate.test.ts`:
- Around line 265-278: The test "interactive Google web update can select dev
credentials" currently asserts properties on updatedClients[0] but omits
clientId and clientSecret; update the assertion in that test to also require
clientId: null and clientSecret: null so the test will fail if
updateGoogleToDevCredentials stops passing those fields — modify the
expect(updatedClients[0]).toMatchObject call in the test to include clientId:
null and clientSecret: null.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: df11328a-e64e-4d86-9fa9-b15daa4dd853
📒 Files selected for processing (14)
client/packages/cli/__tests__/authClientAddGoogle.test.tsclient/packages/cli/__tests__/authClientList.test.tsclient/packages/cli/__tests__/authClientUpdate.test.tsclient/packages/cli/__tests__/oauthMock.tsclient/packages/cli/src/commands/auth/client/add.tsclient/packages/cli/src/commands/auth/client/delete.tsclient/packages/cli/src/commands/auth/client/list.tsclient/packages/cli/src/commands/auth/client/shared.tsclient/packages/cli/src/commands/auth/client/update.tsclient/packages/cli/src/index.tsclient/packages/cli/src/lib/oauth.tsserver/src/instant/dash/routes.cljserver/src/instant/model/app_oauth_client.cljserver/test/instant/dash/routes_test.clj
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
client/www/components/dash/auth/Clerk.tsx (1)
457-472:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAbort submit when Clerk domain parsing fails.
After the toast on Line 459, this handler still falls through to
addClientand buildshttps://${domain}/.well-known/openid-configurationwithdomain === null. That turns a client-side validation failure into a broken server request. Return immediately once parsing fails.💡 Suggested fix
const domain = clerkDomainFromPublishableKey(publishableKey); if (!domain) { errorToast( 'Could not determine Clerk domain from key. Ping us in Discord for help.', { autoClose: 5000 }, ); + return; } try { setIsLoading(true); const resp = await addClient({🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@client/www/components/dash/auth/Clerk.tsx` around lines 457 - 472, When clerkDomainFromPublishableKey(publishableKey) returns falsy, abort the submit flow immediately instead of continuing to build the discoveryEndpoint and calling addClient; after calling errorToast(...) return early from the handler so domain is never used (this prevents building `https://${domain}/.well-known/openid-configuration` with a null/undefined domain). Ensure the early return occurs before setIsLoading(true) and addClient(...) so no server request is attempted when domain parsing fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@client/packages/platform/src/clerk.ts`:
- Around line 17-23: The decoded domain from clerkDomainFromPublishableKey must
be validated as a bare host/domain before returning; after calling
base64DecodeUtf8 and trimming the trailing '$', verify the value matches a
hostname regex (e.g. one or more dot-separated DNS labels: labels start/end with
alphanumeric, may contain hyphens, no
scheme/paths/userinfo/ports/colons/slashes) and return null if it does not; keep
the existing try/catch around base64DecodeUtf8 and only return the decoded value
when it passes this hostname validation, otherwise return null.
---
Outside diff comments:
In `@client/www/components/dash/auth/Clerk.tsx`:
- Around line 457-472: When clerkDomainFromPublishableKey(publishableKey)
returns falsy, abort the submit flow immediately instead of continuing to build
the discoveryEndpoint and calling addClient; after calling errorToast(...)
return early from the handler so domain is never used (this prevents building
`https://${domain}/.well-known/openid-configuration` with a null/undefined
domain). Ensure the early return occurs before setIsLoading(true) and
addClient(...) so no server request is attempted when domain parsing fails.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d4ed5683-3f45-458f-acdf-dfb9e4940f9b
📒 Files selected for processing (7)
client/packages/cli/__tests__/authClientUpdate.test.tsclient/packages/cli/src/commands/auth/client/shared.tsclient/packages/cli/src/commands/auth/client/update.tsclient/packages/platform/__tests__/src/clerk.test.tsclient/packages/platform/src/clerk.tsclient/packages/platform/src/index.tsclient/www/components/dash/auth/Clerk.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- client/packages/cli/src/commands/auth/client/shared.ts
- client/packages/cli/tests/authClientUpdate.test.ts
- client/packages/cli/src/commands/auth/client/update.ts
| export function clerkDomainFromPublishableKey(key: string): string | null { | ||
| try { | ||
| const match = key.match(/^pk_[^_]+_(.+)$/); | ||
| const domainPartB64 = match?.[1]; | ||
| if (!domainPartB64) return null; | ||
| const domainPart = base64DecodeUtf8(domainPartB64); | ||
| return domainPart.replace(/\$$/, ''); |
There was a problem hiding this comment.
Only return a validated host from the decoded Clerk key.
Lines 19-23 currently accept any base64url payload after the pk_*_ prefix and return it unchanged apart from trimming the trailing $. The new callers treat that value as a host when they build the discovery endpoint, so a crafted key can resolve to a malformed target or an arbitrary internal hostname. Please reject anything that is not a bare Clerk host/domain and return null instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/packages/platform/src/clerk.ts` around lines 17 - 23, The decoded
domain from clerkDomainFromPublishableKey must be validated as a bare
host/domain before returning; after calling base64DecodeUtf8 and trimming the
trailing '$', verify the value matches a hostname regex (e.g. one or more
dot-separated DNS labels: labels start/end with alphanumeric, may contain
hyphens, no scheme/paths/userinfo/ports/colons/slashes) and return null if it
does not; keep the existing try/catch around base64DecodeUtf8 and only return
the decoded value when it passes this hostname validation, otherwise return
null.
There was a problem hiding this comment.
I think this is fine.
There was a problem hiding this comment.
@stopachka, understood — thanks for the clarification! I'll drop this suggestion.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
client/packages/platform/src/clerk.ts (1)
14-23:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHostname validation is still missing — decoded value is returned unsanitised.
The past review comment flagging this SSRF risk remains unresolved. The value returned by
base64Decodeis accepted verbatim (after stripping$) and used by callers to build discovery endpoint URLs, so a crafted publishable key can direct requests to arbitrary internal hosts.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@client/packages/platform/src/clerk.ts` around lines 14 - 23, In clerkDomainFromPublishableKey, stop returning the raw base64-decoded value: after decoding and stripping '$', validate and sanitize the hostname — trim it, reject any string containing ':' or '/' or other URI characters, ensure it matches a strict hostname regex (e.g. /^[A-Za-z0-9.-]+$/), reject "localhost" and any IP literal (use net.isIP or equivalent) and optionally block private/reserved IP ranges; if validation fails, log and return null so callers cannot build URLs to arbitrary/internal hosts.
🧹 Nitpick comments (1)
client/packages/platform/src/clerk.ts (1)
6-12: ⚡ Quick winThe
catchbranch ofbase64Decodeis dead code —Buffer.from(s, 'base64')never throws.
Buffer.fromwith'base64'silently ignores decoding errors rather than throwing, so thecatchblock on lines 9–11 is never reached. The stated rationale — falling back to url-safe decoding on error — cannot work viatry/catchhere.Additionally, Node.js's
'base64'encoding already correctly accepts the "URL and Filename Safe Alphabet" as specified in RFC 4648, Section 5, making the fallback doubly unnecessary on modern Node.js. The entire helper can be simplified to a single expression:♻️ Proposed simplification
-// Base64 decode, switching to url-safe decode if we hit an error -// Can't be sure which method Clerk uses because you can't generate -// `+` or `/` with characters that go in a normal host. Urls with -// chinese characters exist, they might encode to `+` or `/`, and -// Clerk might support them, so we'll be safe and do both. -function base64Decode(s: string) { - try { - return Buffer.from(s, 'base64').toString('utf-8'); - } catch (e) { - return Buffer.from(s, 'base64url').toString('utf-8'); - } -}Then inline
Buffer.from(domainPartB64, 'base64').toString('utf-8')directly inclerkDomainFromPublishableKey.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@client/packages/platform/src/clerk.ts` around lines 6 - 12, The base64Decode function contains dead code because Buffer.from(s, 'base64') does not throw and already accepts URL-safe base64; remove the try/catch and simplify by replacing base64Decode(s) with a single expression Buffer.from(s, 'base64').toString('utf-8') (or collapse the helper entirely) and inline that expression where used (e.g., in clerkDomainFromPublishableKey) so the fallback branch is eliminated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@client/packages/platform/src/clerk.ts`:
- Around line 14-23: In clerkDomainFromPublishableKey, stop returning the raw
base64-decoded value: after decoding and stripping '$', validate and sanitize
the hostname — trim it, reject any string containing ':' or '/' or other URI
characters, ensure it matches a strict hostname regex (e.g. /^[A-Za-z0-9.-]+$/),
reject "localhost" and any IP literal (use net.isIP or equivalent) and
optionally block private/reserved IP ranges; if validation fails, log and return
null so callers cannot build URLs to arbitrary/internal hosts.
---
Nitpick comments:
In `@client/packages/platform/src/clerk.ts`:
- Around line 6-12: The base64Decode function contains dead code because
Buffer.from(s, 'base64') does not throw and already accepts URL-safe base64;
remove the try/catch and simplify by replacing base64Decode(s) with a single
expression Buffer.from(s, 'base64').toString('utf-8') (or collapse the helper
entirely) and inline that expression where used (e.g., in
clerkDomainFromPublishableKey) so the fallback branch is eliminated.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d6342db2-1db6-48a2-adeb-0ebbe36af905
📒 Files selected for processing (2)
client/packages/platform/__tests__/src/clerk.test.tsclient/packages/platform/src/clerk.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- client/packages/platform/tests/src/clerk.test.ts
| const redirectPrompt = redirectUriPrompt({ | ||
| heading: 'Custom redirect URI (optional):', | ||
| }); | ||
| const newRedirectPrompt = redirectUriPrompt({ heading: 'New redirect URI:' }); |
There was a problem hiding this comment.
Checking for an Instant SDK...
Found @instantdb/core in your package.json.
Select a client to update:
● jlaksdfj (4eee42ad-9f77-4149-bc7e-0a972fa972e8)
Current mode: custom credentials
What do you want to update?
● Update redirect URI
New redirect URI:
[error] Missing required value for --custom-redirect-uri
hint: run `instant-cli auth client update --help` for available arguments
Press Enter to close
Pressing "update redirect uri" and then hitting enter without typing it, gives an error that looks like a flag is missing.
There was a problem hiding this comment.
I think it's ~somewhat reasonable to expect an error like a flag is missing in this case. Here we are explicitly going through "Update redirect URI", but then it is not provided.
It does expose a problem: what if someone wants to remove the redirect uri?
Right now we don't allow this.
Perhaps we should, but I worry it could lead to weird edge cases (like accidentally removing a redirect uri, when the user did not intend it)
| provider_name: string; | ||
| }; | ||
|
|
||
| const redirectPrompt = redirectUriPrompt({ |



This PR adds
npx instant-cli auth client update, so users can change OAuth client credentials after a client has already been created.The main use case is Google shared dev credentials: start fast with Instant dev credentials, then switch to your own Google OAuth credentials when you are ready. It also supports rotating credentials and updating provider-specific settings for the rest of our OAuth client types.
shared helpers
This also extracts shared OAuth client helpers that are now used by
add,update,list, anddelete:optOrPromptWhenNeeded, which lets update flows only prompt for the fields selected by flags or by the interactive action.This keeps
addandupdateusing the same prompt copy and validation instead of duplicating provider-specific details across commands.update
Select a client by name or ID
You can update a client by
--nameor--id:If you do not pass
--nameor--idin interactive mode, the CLI asks which client to update. Dev-credential clients are labeled in the picker.Google dev credentials can move to custom credentials
A Google web client that was created with Instant dev credentials can be updated to use your own Google OAuth credentials:
When the client moves to custom credentials, the CLI prints the Google Console redirect URI again:
Google custom credentials can move back to dev credentials
You can also switch a Google web client back to Instant dev credentials:
The update clears the redirect URI and marks the client as using shared credentials again:
Custom credential providers can rotate values
GitHub, LinkedIn, and Google custom clients can rotate
--client-id,--client-secret, and--custom-redirect-uriwithout recreating the client.Apple can update
--services-id, and the web redirect settings:--team-id,--key-id,--private-key-file, and--custom-redirect-uri.Clerk and Firebase can update their source settings:
For Clerk and Firebase, the CLI also refreshes the stored discovery endpoint.
Interactive mode asks what you want to update
For custom credential clients, interactive mode asks whether to rotate credentials or update the redirect URI. For Google web clients, it can also switch back to Instant dev credentials. When a redirect URI changes, the success output mirrors the provider-specific setup guidance from
auth client add.add
The Google dev-credentials success box now points users at the new update command:
Google OAuth client created: google-dev App type: web Credentials: Instant dev credentials ID: ae5ac75d-6840-42c9-a8d5-2881f630300e No Google Console setup required. Works on localhost and Expo during development. Ready for production? Run: instant-cli auth client update --name google-dev \ --client-id <id> \ --client-secret <secret>The rest of the add flow should behave the same as before, but now uses the shared helpers described above for provider prompts and validation.
Testing
Update flow:
Add and shared-helper regressions:
@drew-harris @dwwoelfel @nezaj