Add vercel_oauth_app and vercel_oauth_app_client_secret resources - #576
Merged
Conversation
Adds Terraform support for Sign in with Vercel OAuth applications (https://vercel.com/docs/sign-in-with-vercel), which until now could only be created in the dashboard. vercel_oauth_app manages the application itself: name, slug, description, home page URI, redirect (callback) URIs, scopes (openid/email/profile/ offline_access), and the consent-page policy URLs. The resource id is the OAuth client_id. Supports import as [team_id/]client_id. A validator enforces that an explicitly configured scopes set includes "openid", since the API force-includes it server-side (omitting it would cause a perpetual diff); nullable URL fields are cleared with explicit JSON nulls on update. vercel_oauth_app_client_secret generates a client secret. The API returns the plaintext exactly once, so it is captured at create time (sensitive) and subsequently tracked via the secret's last four characters — which is also how the delete endpoint addresses secrets. Apps allow at most two secrets, enabling zero-downtime rotation with create_before_destroy. Not importable by nature. API notes baked into the client: the get endpoint wraps its response in { app }, reports missing apps as HTTP 400 code "invalid_client" rather than 404 (handled by a dedicated OAuthAppNotFound helper), and the secret endpoint rejects body-less POSTs with 415, so an empty JSON object is sent. Both acceptance tests pass against the production API (create, import, update, secret generation/verification, destroy). Note the API requires the team Owner role for all mutations, including in acceptance test runs.
TooTallNate
requested review from
craigandrews,
dglsparsons,
jarneson and
kitfoster
as code owners
August 7, 2026 01:44
There was a problem hiding this comment.
Pull request overview
Adds Terraform support for managing Vercel “Sign in with Vercel” OAuth apps and rotating their client secrets, including provider resources, API client support, generated docs, examples, and acceptance tests.
Changes:
- Introduces
vercel_oauth_appresource (CRUD + import) with scope validation enforcingopenid. - Introduces
vercel_oauth_app_client_secretresource to create/delete secrets while persisting the one-time plaintext secret in state. - Adds corresponding Vercel API client methods, acceptance tests, examples, and generated docs.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
vercel/validator_oauth_app_scopes.go |
Adds set validator enforcing openid in explicitly configured scopes. |
vercel/resource_oauth_app.go |
Implements vercel_oauth_app resource schema, CRUD, and import mapping. |
vercel/resource_oauth_app_test.go |
Adds acceptance tests for OAuth app and client secret resources. |
vercel/resource_oauth_app_client_secret.go |
Implements vercel_oauth_app_client_secret resource (create/read/delete). |
vercel/provider.go |
Registers the new resources with the provider. |
examples/resources/vercel_oauth_app/resource.tf |
Adds example configuration for vercel_oauth_app. |
examples/resources/vercel_oauth_app/import.sh |
Adds import usage examples for vercel_oauth_app. |
examples/resources/vercel_oauth_app_client_secret/resource.tf |
Adds example configuration for vercel_oauth_app_client_secret and rotation workflow. |
docs/resources/oauth_app.md |
Generated docs for vercel_oauth_app. |
docs/resources/oauth_app_client_secret.md |
Generated docs for vercel_oauth_app_client_secret. |
client/oauth_app.go |
Adds OAuth app + secret API helpers and “not found” handling. |
Suppressed comments (2)
vercel/resource_oauth_app.go:329
redirect_urisis Optional, so it may be null in the plan when omitted or explicitly set to null. Unconditionally callingElementsAswill add diagnostics and block update; treat null as an empty slice (clear) and only decode when non-null (and preserve state if unknown).
redirectURIs := []string{}
diags = plan.RedirectURIs.ElementsAs(ctx, &redirectURIs, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
vercel/resource_oauth_app.go:336
scopescan be null if explicitly configured asnull(it’s Optional+Computed), but the update path always callsElementsAswhich will fail on a null Set. Decode only when non-null, and let the existinglen(scopes)==0fallback apply for the defaultopenidbehavior (preserving state if the value is unknown).
var scopes []string
diags = plan.Scopes.ElementsAs(ctx, &scopes, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Guard all ElementsAs conversions on redirect_uris/scopes against null AND unknown values. Verified empirically that ElementsAs handles a null Set fine (yields an empty slice, no diagnostics) — the real hazard is UNKNOWN values, which scopes (Optional+Computed) can genuinely be at create time. The guards are now uniform across Create and Update, and the acceptance test's first step is a minimal config (no redirect_uris, no scopes) so the flagged path is exercised for real. - Add the computed id attribute to vercel_oauth_app_client_secret (Pulumi bridge compatibility; fixes TestAllResourcesHaveIDAttribute). The id is the API's secret metadata id, resolved from the app's secret list at create time, with a synthetic <client_id>/<last_four> fallback should the metadata omit it. Unit tests, staticcheck, tfproviderlint, gofmt -s, and go vet are clean; both acceptance tests re-verified against the production API.
vercel_oauth_app and vercel_oauth_app_client_secret resources
dglsparsons
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds Terraform support for Sign in with Vercel OAuth applications, which until now could only be created in the dashboard.
vercel_oauth_appManages the application:
name,slug,description,home_page_uri,redirect_uris,scopes(openid/email/profile/offline_access), and the consent-page policy URLs (privacy_policy_url,terms_of_service_url,code_of_conduct_url). The resourceidis the OAuthclient_id. Importable as[team_id/]client_id.scopesinclude"openid"— the API force-includes it server-side, so omitting it would otherwise produce a perpetual diff (Provider produced inconsistent result).nulls on update (pointer fields withoutomitempty), so removing an attribute from config actually clears it.vercel_oauth_app_client_secretGenerates a client secret for an app. The API returns the plaintext exactly once, so it's captured at create time (
client_secret, sensitive) and tracked afterwards vialast_four_chars— which is also how the delete endpoint addresses secrets. Apps allow at most two secrets, so zero-downtime rotation works withcreate_before_destroy+-replace. Not importable by nature; every change requires replacement.API quirks handled in the client
GET /v1/oauth-apps/:idwraps its response in{ app }(create/update return the bare object)invalid_clientrather than 404 — handled by a dedicatedclient.OAuthAppNotFoundhelper (verified against production)POST .../secretrejects body-less requests with 415, so an empty JSON object is sentTesting
Acceptance tests pass against the production API:
Note for CI: all mutations require the team Owner role — the token behind
VERCEL_API_TOKENmust be an Owner ofVERCEL_TERRAFORM_TESTING_TEAM(a Developer-role token getsforbidden).staticcheck,tfproviderlint -R018=false,gofmt -s, andgo vetare all clean; docs generated with tfplugindocs v0.25.0.Motivation
vercel-labs/durabench is moving its operator auth to Sign in with Vercel and wants the whole app provisioned via Terraform (vercel/infra#32272 currently has to treat the app as a dashboard-created input — with this it can own the app end to end).