refactor(dashboard-api): extract internal/provisioning package#3189
refactor(dashboard-api): extract internal/provisioning package#3189ben-fornefeld wants to merge 5 commits into
Conversation
PR SummaryMedium Risk Overview Reviewed by Cursor Bugbot for commit 8c349d7. Bugbot is set up for automated code reviews on this repo. Configure here. |
❌ 2 Tests Failed:
View the top 3 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
There was a problem hiding this comment.
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.
e3f8297 to
86cd4ed
Compare
0411e37 to
7b53dbf
Compare
86cd4ed to
71d2e87
Compare
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.
7b53dbf to
8c349d7
Compare
What
Follow-up to #3094 (based on
auth/sso-initial): extracts the team-provisioning/SSO domain out ofinternal/handlersinto a newinternal/provisioningpackage, following the extraction pattern this service already uses (teamprovision,identity) and the shapepackages/apiuses 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.gohad grown to 658 lines (~19% of all non-test handler code) — a full domain layer (bootstrap saga, team creation, SSO enrollment/guards) living asAPIStoremethods, 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
handlerskeeps: all HTTP endpoints (now uniformly thin),handleProvisioningError(error→HTTP mapping), authz guards, pagination helpers.APIStoredropsteamProvisionSink;NewAPIStore's signature is unchanged (it builds theServiceinternally), somain.gois untouched.handlers → provisioning → {identity, teamprovision, authdb}. No module imports gin or handlers.Deliberate deltas (the only non-move changes)
rejectInviteOutsideSSOOrg(wrote HTTP responses itself) becameValidateInviteeOrganizationreturning an error the handler maps viahandleProvisioningError. 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.bootstrapUser(wrapper, test-only caller) anddefaultTeamNameFromProfile/emailPrefix(no production callers).TestBootstrapOIDCUser_OryModeRejectsNonOryJWTIssuercollapsed intoUnknownIssuerReturnsBadRequest— itsAuthProvider.JWTconfig never reached the tested code (the issuer check reads onlyOryIssuerURL).Tests
Direct saga/SSO tests moved to
internal/provisioningwith unchanged assertions (constructed viaprovisioning.New+ the same fakes); HTTP-level tests stay inhandlers. Full DB-backed suites pass for both packages;go build/vet/gofmtclean.Not in scope
Extracting
team_members.goadd/remove invariants (internal/membership) and theadmin_users_delete.gosaga — deferred until DirectorySync/SCIM introduces a second caller.🤖 Generated with Claude Code
Also in this PR
internal/userprofileis renamed tointernal/identity— the package is the identity-provider boundary, not just profile lookup, and call sites now readidentity.Provider. Fields follow (userProfiles/profiles→idp). The generated/admin/user-profilesAPI surface is untouched. Rename-only commit (5186c99).