Repository navigation
feat(auth): update a user's MFA factor as an admin - #609
Merged
Merged
Conversation
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.
Summary
A service-role caller can now update a user's MFA factor: rename it, or change the phone number of a phone factor.
Capability:
auth.admin.update_mfa_factor— Admin Update MFA Factor. Spec: specs/auth/admin/update_mfa_factor.md.Public API
New types:
Supabase.Gotrue.UpdateFactor.FactorChanges(sealed record, init-only):string? FriendlyName,string? Phone.Supabase.Gotrue.Mfa.Factor.Phone(string?,phone): additive member on the existing factor, so callers see the number they set;ListFactorsreturns it too.Adding a member to
IGotrueAdminClient<TUser>andIGotrueApi<TUser, TSession>(both public, all members abstract, no default implementations) breaks third-party implementers of those interfaces, the same asSignOutAsyncand the custom-provider members did; callers are unaffected (CONVENTIONS §7).Behavior
PUT /admin/users/{userId}/factors/{factorId}with the service-role key (Authorization: Bearer <serviceKey>).userIdandfactorIdmust be canonical hyphenated UUIDs (either case), checked before the wire: null throwsArgumentNullException, anything elseArgumentExceptionnaming the parameter. The ids are sent as given.changesnull throwsArgumentNullException. Only the members set are sent (friendly_name,phone); none set sends{}, which the server answers with the factor unchanged. An empty value cannot clear a setting. The server ignoresphoneon a factor that is not a phone factor. The SDK does not check E.164; the server does.Factor. A 200 with no factor throwsGotrueException("The auth server returned no factor.").GotrueException):ErrorCodeReasonuser_not_foundUnknownmfa_factor_not_foundUnknownvalidation_failedUnknownnot_adminAdminTokenRequiredUnknownfriendly_name/phoneandupdated_at, and writes an audit-log entry.Deviations from supabase-js
None: supabase-js has no counterpart. Against the spec, only the canonical UUID form is accepted (the spec allows braced, bare and URN forms too).
Deliberately different from the neighbouring
ListFactors/DeleteFactor(legacy shape kept as is):Asyncsuffix andCancellationToken, positional ids instead of a mutable params class, an immutable record for the changes, a non-nullable return, and the spec's UUID check before the wire.Tests
UpdateFactorTestsmfa_factor_not_found,user_not_found, duplicate friendly name,AdminTokenRequiredagainst the live stackUpdateFactorContractTestsmfa_factor_not_foundandvalidation_failederror codes, empty body, null changes,ConfigureAwait(false), cancellationUpdateFactorApprovalTestsUpdateFactorIdTestsChanged-line coverage: 100% (26 lines). Mutation: 9/9 tested (2 block-removal mutants ignored by the Stryker config) on changed lines, 0 survivors.
Notes
validation_failedon a non-E.164 number) is pinned by contract and approval tests only. The local stack has phone MFA off, and the CLI refuses[auth.mfa.phone] enroll_enabled = truewithoutverify_enabled = true(Invalid MFA config: auth.mfa.phone.enroll_enabled requires verify_enabled), sosupabase/config.tomlis unchanged.openapi.yamlseems to drift from the admin handlers in several places auth#2859); the fields come fromadminUserUpdateFactorParamsininternal/api/admin.go.mfa_factor_name_conflict) but not on this update, so a duplicate hits the(friendly_name, user_id)unique index instead.ListFactors/DeleteFactorlack the spec's UUID check; adding it changes their error contract, so it is left for a separate change.