[in-progress] shared dev credentials for Google#2553
[in-progress] shared dev credentials for Google#2553
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 optional shared OAuth credentials: UI toggle and upgrade path for Google web clients; server schema and flags ingestion for shared-oauth-clients; encryption helpers and server-side checks to substitute or validate credentials and relax redirect-origin rules when shared credentials are used. Changes
Sequence DiagramssequenceDiagram
participant User
participant ClientUI as Client UI (Google.tsx)
participant API as Dash API (routes)
participant Flags as Flags Store (shared-oauth-clients)
participant DB as Database
User->>ClientUI: Select credential mode (shared or custom) & submit
alt Shared selected
ClientUI->>API: POST create/update client (meta.useSharedCredentials = true)
API->>Flags: Read shared-oauth-clients for provider
Flags-->>API: Return provider credential (encrypted)
API->>API: Decrypt secret, substitute client_id/secret
API->>DB: Persist OAuth client record (meta.useSharedCredentials = true)
API-->>ClientUI: Return OAuthClient (credentials substituted, not exposed)
else Custom selected
ClientUI->>API: POST create/update client (client_id, client_secret)
API->>API: Encrypt client_secret
API->>DB: Persist encrypted secret and client_id
API-->>ClientUI: Return OAuthClient (client_id visible)
end
ClientUI-->>User: Render client details / setup steps (conditional)
sequenceDiagram
participant BrowserApp as OAuth App (redirect_uri)
participant AuthServer as Authorization Routes
participant Matcher as Redirect Validator
BrowserApp->>AuthServer: Authorization request / token callback (Origin/redirect_uri)
AuthServer->>AuthServer: Load client metadata
alt client.meta.useSharedCredentials == true
AuthServer->>Matcher: shared-credentials-default-match(redirect_uri)
Matcher-->>AuthServer: match result (localhost/exp allowed)
else
AuthServer->>Matcher: find-match(redirect_uri, authorized-origins)
Matcher-->>AuthServer: match result
end
alt matched
AuthServer-->>BrowserApp: Proceed (allow redirect / token exchange)
else
AuthServer-->>BrowserApp: Error (unauthorized origin)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
server/src/instant/runtime/routes.clj (1)
210-216: Minor:get-all-for-appcalled unconditionally but unused whenuse-shared-credentials?is true.The
authorized-originsbinding is fetched even whenuse-shared-credentials?is true, but it's only used in the else branch. Consider moving the fetch inside the conditional to avoid the DB call.♻️ Proposed refactor
use-shared-credentials? (get (:meta client) "useSharedCredentials") - authorized-origins (app-authorized-redirect-origin-model/get-all-for-app - {:app-id (:app_id client)}) matched-origin (if use-shared-credentials? (app-authorized-redirect-origin-model/localhost-match redirect-uri) - (app-authorized-redirect-origin-model/find-match - authorized-origins - redirect-uri)) + (let [authorized-origins (app-authorized-redirect-origin-model/get-all-for-app + {:app-id (:app_id client)})] + (app-authorized-redirect-origin-model/find-match + authorized-origins + redirect-uri)))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/src/instant/runtime/routes.clj` around lines 210 - 216, The code currently calls app-authorized-redirect-origin-model/get-all-for-app and binds authorized-origins unconditionally even when use-shared-credentials? true; to avoid the unnecessary DB call, move the get-all-for-app invocation so it only runs in the branch where use-shared-credentials? is false (i.e., call app-authorized-redirect-origin-model/localhost-match when use-shared-credentials? is true, and otherwise fetch authorized-origins with app-authorized-redirect-origin-model/get-all-for-app and pass it into app-authorized-redirect-origin-model/find-match along with redirect-uri and client).server/src/instant/model/app_authorized_redirect_origin.clj (1)
71-75:localhost-matchignores port, butfind-matchconsiders it — potential inconsistency.
find-match(lines 79-83) appends the port to the host when present (e.g.,localhost:3000), butlocalhost-matchonly checks:host. A redirect URI likehttp://localhost:3000/callbackwould match vialocalhost-match(host ="localhost"), which is likely the desired behavior for dev credentials. However, this diverges from howfind-matchconstructs the host string.If the intent is that any localhost port should work for shared credentials, the current implementation is correct. If consistency is needed, consider documenting this difference or aligning the approaches.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/src/instant/model/app_authorized_redirect_origin.clj` around lines 71 - 75, localhost-match currently only checks :host while find-match builds a host+port string; to make behavior consistent, update localhost-match to include the port when present (e.g., produce {:service (str "localhost:" port)}) by reading :port from parsed-url, or alternatively change find-match to normalize localhost to just "localhost"—choose one and apply consistently across localhost-match and find-match so both use the same host-with-port format; locate and modify the localhost-match function and ensure any callers that expect {:service "localhost"} are updated accordingly.server/src/instant/model/app_oauth_client.clj (1)
167-178: Consider clarifying the UUID source in the comment.The comment instructs to "Copy the id from the modal" but it might be clearer to specify this is the
idof the newly createdshared-oauth-clientsentity in the instant-config app, not a randomly generated UUID.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/src/instant/model/app_oauth_client.clj` around lines 167 - 178, Clarify that the UUID referenced in the example is the id of the newly created shared-oauth-clients entity in the instant-config app (not a random UUID); update the comment around encrypt-client-secret and encryptedClientSecret to explicitly say "use the id of the shared-oauth-clients entry from the instant-config modal" and optionally note where to find that id in the UI so readers know exactly which value to copy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@client/www/components/dash/auth/Google.tsx`:
- Around line 433-476: handleUpgradeCredentials currently deletes the existing
OAuth client with deleteClient before calling addClient, which can leave the
user without any client if addClient fails; change the flow in
handleUpgradeCredentials to create the new client first (call addClient with the
provided upgradeClientId/upgradeClientSecret and a temporary clientName if
needed), on successful add call onAddClient(resp.client) and only then call
deleteClient for the old client and onDeleteClient(client); also handle the case
where deleteClient fails after a successful add by surfacing an error and
optionally cleaning up the newly-created client, and ensure isLoading/errorToast
handling remains correct.
---
Nitpick comments:
In `@server/src/instant/model/app_authorized_redirect_origin.clj`:
- Around line 71-75: localhost-match currently only checks :host while
find-match builds a host+port string; to make behavior consistent, update
localhost-match to include the port when present (e.g., produce {:service (str
"localhost:" port)}) by reading :port from parsed-url, or alternatively change
find-match to normalize localhost to just "localhost"—choose one and apply
consistently across localhost-match and find-match so both use the same
host-with-port format; locate and modify the localhost-match function and ensure
any callers that expect {:service "localhost"} are updated accordingly.
In `@server/src/instant/model/app_oauth_client.clj`:
- Around line 167-178: Clarify that the UUID referenced in the example is the id
of the newly created shared-oauth-clients entity in the instant-config app (not
a random UUID); update the comment around encrypt-client-secret and
encryptedClientSecret to explicitly say "use the id of the shared-oauth-clients
entry from the instant-config modal" and optionally note where to find that id
in the UI so readers know exactly which value to copy.
In `@server/src/instant/runtime/routes.clj`:
- Around line 210-216: The code currently calls
app-authorized-redirect-origin-model/get-all-for-app and binds
authorized-origins unconditionally even when use-shared-credentials? true; to
avoid the unnecessary DB call, move the get-all-for-app invocation so it only
runs in the branch where use-shared-credentials? is false (i.e., call
app-authorized-redirect-origin-model/localhost-match when
use-shared-credentials? is true, and otherwise fetch authorized-origins with
app-authorized-redirect-origin-model/get-all-for-app and pass it into
app-authorized-redirect-origin-model/find-match along with redirect-uri and
client).
🪄 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: 57ea0fb5-ff09-4311-839e-4c3e70999abd
📒 Files selected for processing (6)
client/www/components/dash/auth/Google.tsxserver/flags-config/instant.schema.tsserver/src/instant/flags.cljserver/src/instant/model/app_authorized_redirect_origin.cljserver/src/instant/model/app_oauth_client.cljserver/src/instant/runtime/routes.clj
…tion Default localhost and exp:// for shared credentials, while still checking user-configured authorized redirect origins first.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@server/src/instant/model/app_authorized_redirect_origin.clj`:
- Around line 71-80: The function shared-credential-match currently allows any
URI with host "localhost" regardless of scheme; restrict this by requiring the
scheme to be a web scheme (e.g. "http" or "https") before returning {:service
"localhost"} and keep the existing special-case for (= scheme "exp") returning
{:service "custom-scheme"}; update the conditional in shared-credential-match
(which binds parsed-url, host and scheme) to require both (= host "localhost")
and that scheme is one of the allowed web schemes.
In `@server/src/instant/runtime/routes.clj`:
- Around line 602-611: The oauth-id-token-callback path currently only validates
origin when (:client_secret client) is present and calls find-match, which skips
the shared-credential logic and can incorrectly accept/reject origins; change
its origin-check to mirror oauth-token-callback by using use-shared-credentials?
(from (get (:meta client) "useSharedCredentials")) and computing matched as (or
(app-authorized-redirect-origin-model/find-match authorized-origins origin)
(when use-shared-credentials?
(app-authorized-redirect-origin-model/shared-credential-match origin))),
removing the client_secret gate so the shared credential rules apply correctly
to the effective client (->OAuthClient).
🪄 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: dced14b0-425f-47a8-a61e-7cda56401947
📒 Files selected for processing (2)
server/src/instant/model/app_authorized_redirect_origin.cljserver/src/instant/runtime/routes.clj
| (and (not (contains? reserved-uri-schemes uri-scheme)) | ||
| (= scheme uri-scheme))) | ||
|
|
||
| (defn shared-credential-match [url] |
There was a problem hiding this comment.
Maybe this should be something like shared-credentials-default-match.
We should add a comment block in here, saying why:
When users choose shared credentials, we automatically allow localhost and expo dev, without them having to set those explicitly in their allowed origins.
For more custom urls, they can always set them in allowed origins
| (throw (ex-info "Unsupported OAuth client" {:oauth-client oauth-client})))) | ||
| (let [provider-name (get (:meta oauth-client) "providerName") | ||
| use-shared-credential? (get (:meta oauth-client) "useSharedCredentials") | ||
| oauth-client (if use-shared-credential? |
There was a problem hiding this comment.
maybe we do if-not here?
| :let [value (get-in req [:params param])] | ||
| :when value] | ||
| [param value])) | ||
| use-shared-credentials? (get (:meta client) "useSharedCredentials") |
There was a problem hiding this comment.
small nit, but imo we should have a use-shared-credentials? function in app-oauth-client, feels brittle to be querying meta like this in multiple places
| :when value] | ||
| [param value])) | ||
| use-shared-credentials? (get (:meta client) "useSharedCredentials") | ||
| authorized-origins (app-authorized-redirect-origin-model/get-all-for-app |
There was a problem hiding this comment.
I do wonder if we should have a single function like assert-authorized origin, which we can re-use here and in our check below.
| } | ||
| try { | ||
| setIsLoading(true); | ||
| await deleteClient({ |
There was a problem hiding this comment.
This is a bad idea. it can lead to a temporary break in service. imo we should add an update-client endpoint, which can take a client name, and update only the clientId and clientSecret.
- Consolidate origin validation into assert-authorized-redirect-uri! / assert-authorized-request-origin! helpers (oauth-start, oauth-token-callback, oauth-id-token-callback). Drop the stale :client_secret gate so shared clients still validate. - Enforce a 100-user cap for shared credentials at oauth-start and on client create/update via assert-shared-credentials-allowed! + users-at-least? (bounded LIMIT query). - Add use-shared-credentials? predicate and switch all call sites. - Rename shared-credential-match -> shared-credentials-default-match and restrict the localhost shortcut to http/https. - Extend update-oauth-client to accept client_id / client_secret so the upgrade-to-custom-credentials flow can be a single atomic update instead of delete + recreate.
| discovery-endpoint (when-not (= "github" provider-name) | ||
| (ex/get-param! req [:body :discovery_endpoint] string-util/coerce-non-blank-str)) | ||
|
|
||
| _ (when (get meta "useSharedCredentials") |
There was a problem hiding this comment.
we should use a lil helper like (uses-shared-credentials? client) that checks get meta
| (ex/get-param! req [:body :discovery_endpoint] string-util/coerce-non-blank-str)) | ||
|
|
||
| _ (when (get meta "useSharedCredentials") | ||
| (app-oauth-client-model/assert-shared-credentials-allowed! {:app-id app-id})) |
There was a problem hiding this comment.
would just pass in app-id as positional arg
| redirect-to | ||
| (url-util/redirect-url-validation-errors | ||
| redirect-to :allow-localhost? true))) | ||
| _ (when (get meta "useSharedCredentials") |
| ;; localhost (http/https) and expo dev (exp://) as redirect targets, | ||
| ;; without them having to set those explicitly in their allowed | ||
| ;; origins. For custom domains or schemes, they can still add them to | ||
| ;; allowed origins. |
There was a problem hiding this comment.
this should be in a "" block for the function def
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@server/src/instant/dash/routes.clj`:
- Around line 631-632: When meta has "useSharedCredentials" true you must also
validate that meta contains a "providerName" and that the provider is supported
before saving; update the branch that currently calls
app-oauth-client-model/assert-shared-credentials-allowed! to first check (get
meta "providerName") and call (or add and call) an app-oauth-client-model
function like assert-shared-credential-provider-supported! (passing {:app-id
app-id :providerName providerName}) so the provider is validated early (do the
same change in the other branch referenced around lines 658-659) to avoid later
failure in app-oauth-client-model/->OAuthClient.
In `@server/src/instant/model/app_user.clj`:
- Around line 119-128: The users-at-least? check currently requests LIMIT (inc
n) and then tests (> (count rows) n), which makes the predicate true only when
there are more than n rows; update the SQL call in users-at-least? (look for
attr-id, rows and the sql/select) to pass n as the LIMIT (not (inc n)) and
change the boolean test to (>= (count rows) n) so the function returns true when
there are at least n users.
In `@server/src/instant/runtime/routes.clj`:
- Around line 233-234: The shared-credentials cap check is currently enforced
too early in the OAuth start flow; remove the call to
app-oauth-client-model/assert-shared-credentials-allowed! from the
authorization-start path (the when using
app-oauth-client-model/use-shared-credentials? block) and instead invoke that
check only on the new-user creation path: after you verify the OAuth identity
does NOT already map to an existing account and immediately before calling
app-user-model/create!; keep the guard that the client uses shared credentials
by calling app-oauth-client-model/use-shared-credentials? and then call
app-oauth-client-model/assert-shared-credentials-allowed! only in that new-user
branch.
🪄 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: 2264ffdf-5f1e-4301-bfb3-38227a5338aa
📒 Files selected for processing (7)
client/www/components/dash/auth/Google.tsxclient/www/components/dash/auth/shared.tsxserver/src/instant/dash/routes.cljserver/src/instant/model/app_authorized_redirect_origin.cljserver/src/instant/model/app_oauth_client.cljserver/src/instant/model/app_user.cljserver/src/instant/runtime/routes.clj
|
View Vercel preview at instant-www-js-sharedcred-jsv.vercel.app. |
| (fn [{:keys [get-entity]}] | ||
| (get-entity id))))) | ||
|
|
||
| ;; Bounded existence check: returns true when the app has at least |
There was a problem hiding this comment.
move this into "" block for fn
| ([params] (users-at-least? (aurora/conn-pool :read) params)) | ||
| ([conn {:keys [app-id n]}] | ||
| (let [attr-id (system-catalog/get-attr-id "$users" "id") | ||
| rows (sql/select conn |
There was a problem hiding this comment.
I wonder if we can use our hsql preformat -- imo it's cooler
- Move the 100-user cap from oauth-start to the new-user branch of upsert-oauth-link! so returning users aren't blocked once the app hits the limit. - Derive providerName from provider_id server-side in ->OAuthClient; drop the redundant providerName field from the client-side shared- credentials meta payload. - Validate that a shared credential is configured for the provider on client create/update, so saves don't silently defer failure to runtime. - Rewrite users-at-least? with uhsql/preformat + formatp and fix the off-by-one so the predicate trips at n, not n+1. - use-shared-credentials? now takes a meta map directly; assert- shared-credentials-allowed! takes app-id positionally; docstrings replace comment blocks on shared-credentials-default-match and users-at-least?. - Drop the showUpgrade toggle in the Google Client panel; always show the upgrade form inline when meta.useSharedCredentials is true. - Copy update: "localhost" -> "development (localhost and Expo)" in the two shared-credential notices.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@client/www/components/dash/auth/Google.tsx`:
- Around line 221-225: Fix the typo in the rel attribute for the external anchor
elements in the Google.tsx component: replace the incorrect "noreferer" with the
correct "noreferrer" so the security attribute works as intended; update each
JSX <a> instance that currently uses rel="noopener noreferer" (the external
links in this component — look for anchor tags with className="underline" and
other external hrefs inside the Google component) to use rel="noopener
noreferrer".
🪄 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: 76c483e3-55fb-44ae-986d-981aa5c8d5fd
📒 Files selected for processing (6)
client/www/components/dash/auth/Google.tsxserver/src/instant/dash/routes.cljserver/src/instant/model/app_authorized_redirect_origin.cljserver/src/instant/model/app_oauth_client.cljserver/src/instant/model/app_user.cljserver/src/instant/runtime/routes.clj
🚧 Files skipped from review as they are similar to previous changes (3)
- server/src/instant/dash/routes.clj
- server/src/instant/model/app_authorized_redirect_origin.clj
- server/src/instant/model/app_user.clj
The dashboard has bounced between h-full, min-h-full, and h-[100dvh] on the outer MainDashLayout div three times: - pre #1983: h-full -> Auth tab scrolled at the wrong level - #1983 (c71f9f5): h-full -> min-h-full -> Dashboard overflow in other tabs - #2252 (a6ff11a): min-h-full -> h-[100dvh] -> Current bug: body paints through below the app (visible html #FBF9F6 beige band under both the sidebar and the content pane, with the fixed Intercom avatar sitting in that band - proof the visible viewport is bigger than what 100dvh resolves to) Each round flipped a single Tailwind height class and traded one tab's symptom for another's. Root cause nobody addressed: the dashboard's sizing depends on a cascade of html/body/#__next height rules, and viewport units (100dvh in particular) don't always match the actual visible viewport (browser chrome transitions, DevTools docking, zoom, and iframe embedding can all make dvh resolve smaller). The layout is trying to satisfy three simultaneous needs: 1. Sidebar: exactly viewport-tall, never scrolls, never shrinks. 2. Explorer tab: exactly viewport-tall with independent internal scroll panes. 3. Auth tab: taller than viewport; needs internal scrolling in the main pane. A single h-* class on a flex container can't solve all three because the height propagation depends on parents whose rules aren't fully under the dashboard's control. Fix: replace 'flex h-[100dvh] w-full flex-col' with 'fixed inset-0 flex flex-col'. fixed inset-0 makes the wrapper exactly the visual viewport regardless of html/body/#__next sizing, address-bar state, viewport-unit quirks, or page embedding. The sidebar's h-full now resolves against a container that is actually viewport-tall, so it can never shrink. The internal overflow-y-auto scroll container only triggers when tab content genuinely exceeds the viewport. This breaks the coupling that's been generating the whack-a-mole cycle, so future layout refactors shouldn't re-expose the seam.
The old helper took camelCase TS args (clientId, clientSecret) and
translated them to snake_case JSON (client_id, client_secret) plus a
hardcoded meta: { useSharedCredentials: false } — two layers of
indirection for a function that's really just "POST this body to the
client update endpoint."
Rewrite as updateClient that takes the body fields directly (snake_case,
matching the wire format) via a rest spread. Callers now spell the
fields exactly as they appear in the request, and the
useSharedCredentials: false flag moves to the one caller that actually
needs it (the upgrade-to-custom-credentials flow in Google.tsx) instead
of being baked into the helper for every future caller.
The sensitive-type comment previously led with the mechanism ("use
type=text + CSS masking") which made it hard to tell why we weren't
just using type=password. Rewrite to lead with the problem: LastPass
attaches its UI to <input type=password> even when data-lpignore is
set, so when we need it to leave the field alone we render as text
and mask the value with -webkit-text-security.
rel="noopener noreferer" was spelled with a single r in 9 places across the OAuth provider tabs and the NavTabBar. Browsers ignore unknown rel tokens silently, so the typo meant we were not actually requesting the no-referrer behaviour we intended for those external links. Correcting to the standard rel="noopener noreferrer".
Two logical concerns were tangled into one comment block: 1. The general "try to prevent password managers from attaching" intent, which belongs with the data-*ignore / autoComplete cluster. 2. The specific reason we render sensitive fields as type="text" rather than type="password" — LastPass ignores data-lpignore on password inputs, so we opt out by never being a password input in the first place and mask the value visually via -webkit-text-security. Move `type` to sit next to `style` (where the masking lives) and put the LastPass-specific comment on that line. Put the general "password managers, please ignore" comment on the data-*ignore block above it.
The shared/non-shared credentials section in Client was an inline IIFE (~90 lines) that defined cancelUpgrade, googleConsoleLink, editForm, and two conditional return branches. The surrounding Client component also held all the upgrade-related state (showUpgrade, upgradeClientId, upgradeClientSecret) and the handleUpgradeCredentials handler, even though none of it is used elsewhere in Client. Pull it out into a CredentialsEditor component that owns: - its own showUpgrade / upgradeClientId / upgradeClientSecret / isLoading state - cancelUpgrade + handleUpgradeCredentials - the shared-credentials notice + "Set custom credentials" button - the non-shared Copyable + "Update credentials" button - the shared edit form used by both branches Client drops the now-unused state and handler; its JSX shrinks to a single <CredentialsEditor ... /> use next to the Client name Copyable. Also fixes the noreferer -> noreferrer typo that was present on googleConsoleLink and on several other external links in Google.tsx.
Problem
Say you want to add Google Oauth.
Even to test in development, you have to go through Google Cloud Console and create an Oauth client. Google Cloud Console is quite annoying to navigate, which means poor devex for us.
Solution
We will provide default oauth credentials for development!
Users don't have to go through Google Cloud Console. They can Oauth Clients without providing
client-idandclient-secretvalues. In development, Instant will handle those values for them.This PR adds support for Google, but we will add support for Apple, LinkedIn, GitHub in short order
User Experience
Now the user can select "Use shared credentials".
This lets them create a google client without a
client-idorclient-secret:During runtime, we can fill those values in for them.
Once they are ready to go to production, they can insert their product
client-idandclient-secret:Accepted Origins
By default clients with dev credentials automatically accept
localhostandexp://as allowed origins. The user can also expand this by adding custom origins.Limits
This is strictly meant for development. To make sure, clients with dev credentials will start to throw after 100 sign-ups.
It's also good note that when the user signs in, the consent screen says "Instant Shared Dev Credentials"
Native Flows are not supported
Right now this works for web flows, but not Native IOS, or Google Button. Those experiences require the user to know a
client-id.We chose not to share a
client-idwith the user, in case we end up needing to change the oauth client.Since this is dev only though, perhaps it's not too big of a deal. We may change our decision about this later.
Implementation
1. We added
shared-oauth-clientsin instant-configI've added a shared-oauth-clients table in instant-config. It has:
Note: we encrypt client secrets just like we do with normal oauth clients. The encrypted result is a byte array though. To store this in Instant, I encode the bytes as a hex string.
Note: providerName is currently unique. In the future we may want to have multiple clients for one provider. This will be useful to share load. At that point we can remove the unique constraint though.
shared-oauth-clientsgive us instant-wideclient-idandclient-secrets.2. app-oauth-clients now store
useSharedCredentialsuseSharedCredentialsin an app-oauth-client'smetafielduseSharedCredentialsis enabled:flags/shared-oauth-clients, and use them5. Users can optionally add their own allowed redirect uris -- i.e their domain, or app scheme
Todos