Skip to content

refactor(dashboard-api): extract internal/provisioning package#3189

Draft
ben-fornefeld wants to merge 5 commits into
auth/sso-initialfrom
refactor/dashboard-api-provisioning
Draft

refactor(dashboard-api): extract internal/provisioning package#3189
ben-fornefeld wants to merge 5 commits into
auth/sso-initialfrom
refactor/dashboard-api-provisioning

Conversation

@ben-fornefeld

@ben-fornefeld ben-fornefeld commented Jul 2, 2026

Copy link
Copy Markdown
Member

What

Follow-up to #3094 (based on auth/sso-initial): extracts the team-provisioning/SSO domain out of internal/handlers into a new internal/provisioning package, following the extraction pattern this service already uses (teamprovision, identity) and the shape packages/api uses for its domain modules (service struct + explicit-args constructor, e.g. template-manager).

Move-only: no behavior change. Every moved function is statement-for-statement identical after the declared renames — verified by an adversarial per-function body diff against the base commit (zero drift findings). Review with git diff --color-moved=dimmed-zebra.

Why

utils_team_provisioning.go had grown to 658 lines (~19% of all non-test handler code) — a full domain layer (bootstrap saga, team creation, SSO enrollment/guards) living as APIStore methods, distinguishable from HTTP handlers only by naming. With RBAC / Workspaces / DirectorySync ahead, this domain keeps growing; extracting it now gives that code a home before it lands on the pile.

Structure

internal/provisioning
├── service.go          Service{authDB, idp, billing, issuerURL}, New, types
├── bootstrap.go        BootstrapOIDCUser + the bootstrapUserWithIdentity saga
├── team.go             CreateTeam, BootstrapTeam, team-limit policy
├── sso.go              enrollSSOMember, ensureNotSSOManaged, ValidateInviteeOrganization
├── creator_context.go  billing creator-context resolution
└── names.go            default-team naming
  • handlers keeps: all HTTP endpoints (now uniformly thin), handleProvisioningError (error→HTTP mapping), authz guards, pagination helpers.
  • APIStore drops teamProvisionSink; NewAPIStore's signature is unchanged (it builds the Service internally), so main.go is untouched.
  • Dependency direction: handlers → provisioning → {identity, teamprovision, authdb}. No module imports gin or handlers.

Deliberate deltas (the only non-move changes)

  1. Invite guard de-ginned: rejectInviteOutsideSSOOrg (wrote HTTP responses itself) became ValidateInviteeOrganization returning an error the handler maps via handleProvisioningError. HTTP status codes and body text are identical on all paths; the lookup-error path now reports through telemetry instead of a raw log line.
  2. Dead code deleted instead of moved: bootstrapUser (wrapper, test-only caller) and defaultTeamNameFromProfile/emailPrefix (no production callers).
  3. Test consolidation: TestBootstrapOIDCUser_OryModeRejectsNonOryJWTIssuer collapsed into UnknownIssuerReturnsBadRequest — its AuthProvider.JWT config never reached the tested code (the issuer check reads only OryIssuerURL).

Tests

Direct saga/SSO tests moved to internal/provisioning with unchanged assertions (constructed via provisioning.New + the same fakes); HTTP-level tests stay in handlers. Full DB-backed suites pass for both packages; go build/vet/gofmt clean.

Not in scope

Extracting team_members.go add/remove invariants (internal/membership) and the admin_users_delete.go saga — deferred until DirectorySync/SCIM introduces a second caller.

🤖 Generated with Claude Code

Also in this PR

internal/userprofile is renamed to internal/identity — the package is the identity-provider boundary, not just profile lookup, and call sites now read identity.Provider. Fields follow (userProfiles/profilesidp). The generated /admin/user-profiles API surface is untouched. Rename-only commit (5186c99).

@cla-bot cla-bot Bot added the cla-signed label Jul 2, 2026
@cursor

cursor Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large structural change on signup, team creation, and SSO invite paths; behavior is intended to be unchanged but any missed wiring would affect auth and provisioning.

Overview
This refactor pulls OIDC user bootstrap, team creation, admin team bootstrap, and SSO enrollment/guards out of APIStore into a dedicated provisioning service so HTTP handlers mostly validate input and map errors. The former userprofile package is renamed to identity; admin profile and delete flows use idp instead of userProfiles. SSO team invites no longer reject out-of-org users inside a gin-specific helper—they call ValidateInviteeOrganization and share handleProvisioningError with other provisioning endpoints. Saga-level tests live under provisioning; handler tests wire provisioning.New directly. Unused bootstrap helpers and a redundant issuer JWT test were dropped.

Reviewed by Cursor Bugbot for commit 8c349d7. Bugbot is set up for automated code reviews on this repo. Configure here.

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
3175 2 3173 6
View the top 3 failed test(s) by shortest run time
github.com/e2b-dev/infra/tests/integration/internal/tests/api/templates::TestTemplateBuildRUN
Stack Traces | 0s run time
=== RUN   TestTemplateBuildRUN
=== PAUSE TestTemplateBuildRUN
=== CONT  TestTemplateBuildRUN
--- FAIL: TestTemplateBuildRUN (0.00s)
github.com/e2b-dev/infra/tests/integration/internal/tests/proxies::TestMaskRequestHostAPIParameter
Stack Traces | 4.77s run time
=== RUN   TestMaskRequestHostAPIParameter
=== PAUSE TestMaskRequestHostAPIParameter
=== CONT  TestMaskRequestHostAPIParameter
    mask_request_host_test.go:44: Command [python3] output: event:{start:{pid:1114}}
    mask_request_host_test.go:68: Command [cat] output: event:{start:{pid:1115}}
    mask_request_host_test.go:68: Command [cat] output: event:{data:{stderr:"cat: /tmp/nc_output.txt: No such file or directory\n"}}
    mask_request_host_test.go:68: Command [cat] output: event:{end:{exit_code:1 exited:true status:"exit status 1" error:"exit status 1"}}
    mask_request_host_test.go:69: 
        	Error Trace:	.../tests/proxies/mask_request_host_test.go:69
        	Error:      	Received unexpected error:
        	            	command cat in sandbox i08aj49kclhrrhelqbl4s failed with exit code 1
        	Test:       	TestMaskRequestHostAPIParameter
--- FAIL: TestMaskRequestHostAPIParameter (4.77s)
github.com/e2b-dev/infra/tests/integration/internal/tests/api/templates::TestTemplateBuildRUN/Single_RUN_command
Stack Traces | 164s run time
=== RUN   TestTemplateBuildRUN/Single_RUN_command
=== PAUSE TestTemplateBuildRUN/Single_RUN_command
=== CONT  TestTemplateBuildRUN/Single_RUN_command
    build_template_test.go:133: test-ubuntu-run: [info] Building template 48per9zhg5nmry9mwsj2/46a35a55-dde6-4ba6-bd68-a10829e28339
    build_template_test.go:133: test-ubuntu-run: [info] [base] FROM ubuntu:22.04 [f9f564014e009a9561a82bf8c84f9314242971e833fb019936654ecba452f184]
    build_template_test.go:133: test-ubuntu-run: [info] Base Docker image size: 30 MB
    build_template_test.go:133: test-ubuntu-run: [info] Creating file system and pulling Docker image
    build_template_test.go:133: test-ubuntu-run: [info] Uncompressing layer sha256:d6834b4a794c03efa2c998853e64969fa8851b11b2ade63292268872a37759d0 30 MB
    build_template_test.go:133: test-ubuntu-run: [info] Uncompressing layer sha256:8d74dba5335ce873299ac385e426bcf58d64ea12ab3d9937bce637193b5662a9 13 MB
    build_template_test.go:133: test-ubuntu-run: [info] Uncompressing layer sha256:8c4b1b28875140ed3abacaf16ad0d696f6bef912f52d2148f261a23e3349465b 168 B
    build_template_test.go:133: test-ubuntu-run: [info] Layers extracted
    build_template_test.go:133: test-ubuntu-run: [info] Root filesystem structure: bin, boot, dev, etc, home, lib, lib32, lib64, libx32, media, mnt, opt, proc, root, run, sbin, srv, sys, tmp, usr, var
    build_template_test.go:133: test-ubuntu-run: [info] Provisioning sandbox template
    build_template_test.go:133: test-ubuntu-run: [info] Provisioning was successful, cleaning up
    build_template_test.go:133: test-ubuntu-run: [info] Sandbox template provisioned
    build_template_test.go:133: test-ubuntu-run: [info] [base] DEFAULT USER user [49e586c2171254c6bc4a09e84eedac32dbcf113a158c24248129af2f49cbed74]
    build_template_test.go:133: test-ubuntu-run: [info] [builder 1/1] RUN echo 'Hello, World!' [c72b4f813c2a16b0fc1a1c5da7b1365a304cbac516b22dc304a71f70aae48ac0]
    build_template_test.go:133: test-ubuntu-run: [info] [builder 1/1] [stdout]: Hello, World!
    build_template_test.go:133: test-ubuntu-run: [info] [finalize] Finalizing template build [92c524e30533398ebb41ce04c2596130f0cdecc9aa328e28fdb16a1b11f61d62]
    build_template_test.go:133: test-ubuntu-run: [error] Build failed: An internal error occurred. Please try again or contact support with the build ID.
    build_template_test.go:166: Build failed: {<nil> An internal error occurred. Please try again or contact support with the build ID. <nil>}
--- FAIL: TestTemplateBuildRUN/Single_RUN_command (164.22s)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the user and team provisioning logic by extracting it from the handlers package into a new internal provisioning package. This includes moving bootstrap, team creation, and SSO validation logic into a dedicated provisioning Service, along with corresponding unit tests. The handlers have been updated to delegate these operations to the new service. No review comments were provided, so I have no feedback to offer.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@ben-fornefeld ben-fornefeld force-pushed the refactor/dashboard-api-provisioning branch from 0411e37 to 7b53dbf Compare July 6, 2026 20:29
Verbatim move of the user/team bootstrap and creation sagas out of
internal/handlers onto a provisioning.Service (authDB + userprofile provider +
billing sink + issuer URL). Only receivers, field names, and boundary-crossing
type names changed; transaction ordering, lock scope, and the
billing-before-external_id sequencing are untouched.

The gin-coupled invite guard is reshaped into ValidateInviteeOrganization,
returning an error for the handler to map instead of writing the response.

Dead code dropped rather than moved: bootstrapUser (test-only wrapper) and
defaultTeamNameFromProfile/emailPrefix (no production callers).
- APIStore drops teamProvisionSink; NewAPIStore (unchanged signature)
  constructs provisioning.Service internally, so main.go is untouched
- team_creation/admin_users_bootstrap/admin_teams_bootstrap delegate to the
  service; handleProvisioningError stays in handlers (error->HTTP mapping)
- the add-member SSO guard calls ValidateInviteeOrganization and maps the
  error via handleProvisioningError
- delete internal/handlers/utils_team_provisioning.go
- tests split: direct saga/SSO tests move to internal/provisioning with the
  same assertions; HTTP-level tests stay in handlers and construct the
  service with the existing fakes
The package is the identity-provider boundary, not just profile lookup —
call sites now read identity.Provider ("identity provider"). Fields follow:
userProfiles/profiles -> idp. The generated /admin/user-profiles API surface
is unchanged. Rename-only; no behavior change.
…o team tests

- identity is now a required value param of bootstrapUser (production always
  has one since the OIDC-only path); the nil-identity guards, the
  setOIDCIdentityExternalID wrapper, and the test-only nil-identity
  concurrency test go away — s.idp.SetIdentityExternalID is called directly
- inline the one-expression creatorContextFromSignupMetadata
- drop zero-value test constructors (newTestIdentityProvider et al)
- rename provisioning_error.go -> utils_provisioning.go per handlers convention
- move the two PostTeamsTeamIDMembers SSO-guard tests from sso_test.go into
  team_handlers_test.go where the other member tests live
Carries the base branch's identity.Registry design through the extraction:
provisioning.Service takes the issuer-keyed registry instead of a bare issuer
URL, BootstrapOIDCUser resolves the identity provider for the request's issuer
(unknown issuer -> 400), and the SSO guards resolve a user's organization
through the registry, which checks every identity deterministically.
@ben-fornefeld ben-fornefeld force-pushed the refactor/dashboard-api-provisioning branch from 7b53dbf to 8c349d7 Compare July 6, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant