Skip to content

Add vercel_oauth_app and vercel_oauth_app_client_secret resources - #576

Merged
dglsparsons merged 2 commits into
mainfrom
oauth-app-resources
Aug 7, 2026
Merged

Add vercel_oauth_app and vercel_oauth_app_client_secret resources#576
dglsparsons merged 2 commits into
mainfrom
oauth-app-resources

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

What

Adds Terraform support for Sign in with Vercel OAuth applications, which until now could only be created in the dashboard.

vercel_oauth_app

Manages 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 resource id is the OAuth client_id. Importable as [team_id/]client_id.

  • A set validator enforces that explicitly configured scopes include "openid" — the API force-includes it server-side, so omitting it would otherwise produce a perpetual diff (Provider produced inconsistent result).
  • Nullable URL fields are cleared with explicit JSON nulls on update (pointer fields without omitempty), so removing an attribute from config actually clears it.

vercel_oauth_app_client_secret

Generates 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 via last_four_chars — which is also how the delete endpoint addresses secrets. Apps allow at most two secrets, so zero-downtime rotation works with create_before_destroy + -replace. Not importable by nature; every change requires replacement.

API quirks handled in the client

  • GET /v1/oauth-apps/:id wraps its response in { app } (create/update return the bare object)
  • A missing app is reported as HTTP 400 invalid_client rather than 404 — handled by a dedicated client.OAuthAppNotFound helper (verified against production)
  • POST .../secret rejects body-less requests with 415, so an empty JSON object is sent

Testing

Acceptance tests pass against the production API:

--- PASS: TestAcc_OAuthAppResource (3.75s)            # create → import → update (name/desc/URIs/scopes) → destroy
--- PASS: TestAcc_OAuthAppClientSecretResource (2.10s) # create secret → verify metadata via GET → destroy

Note for CI: all mutations require the team Owner role — the token behind VERCEL_API_TOKEN must be an Owner of VERCEL_TERRAFORM_TESTING_TEAM (a Developer-role token gets forbidden).

staticcheck, tfproviderlint -R018=false, gofmt -s, and go vet are 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).

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.
Copilot AI review requested due to automatic review settings August 7, 2026 01:44
@TooTallNate
TooTallNate requested a review from ecklf as a code owner August 7, 2026 01:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_app resource (CRUD + import) with scope validation enforcing openid.
  • Introduces vercel_oauth_app_client_secret resource 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_uris is Optional, so it may be null in the plan when omitted or explicitly set to null. Unconditionally calling ElementsAs will 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

  • scopes can be null if explicitly configured as null (it’s Optional+Computed), but the update path always calls ElementsAs which will fail on a null Set. Decode only when non-null, and let the existing len(scopes)==0 fallback apply for the default openid behavior (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.

Comment thread vercel/resource_oauth_app.go
- 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.
@TooTallNate TooTallNate changed the title Add vercel_oauth_app and vercel_oauth_app_client_secret resources Add vercel_oauth_app and vercel_oauth_app_client_secret resources Aug 7, 2026
@dglsparsons
dglsparsons merged commit c8a6711 into main Aug 7, 2026
15 checks passed
@dglsparsons
dglsparsons deleted the oauth-app-resources branch August 7, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants