Skip to content

feat(auth): add admin MFA factor listing and deletion - #1357

Open
AndroidPoet wants to merge 1 commit into
supabase:mainfrom
AndroidPoet:feat/auth-admin-mfa-factors
Open

AndroidPoet wants to merge 1 commit into
supabase:mainfrom
AndroidPoet:feat/auth-admin-mfa-factors

Conversation

@AndroidPoet

Copy link
Copy Markdown
Contributor

Adds auth.admin.mfa with listFactors(forUser:) and deleteFactor(id:forUser:), over the two admin MFA endpoints Auth already serves (GET and DELETE /admin/users/{user_id}/factors). A backend can now see which factors a user has enrolled and remove one, for example when the user has lost their authenticator and cannot sign in to unenroll it themselves.

let factors = try await supabase.auth.admin.mfa.listFactors(forUser: user.id)
try await supabase.auth.admin.mfa.deleteFactor(id: factors[0].id, forUser: user.id)

Marks auth.admin.list_mfa_factors and auth.admin.delete_mfa_factor implemented in sdk-compliance.yaml.

Shape

  • Labels follow the convention in refactor!: align the public API with the Swift API Design Guidelines #1355: the thing being acted on is id: and comes first, the owning user is forUser:, the same as deletePasskey(id:forUser:) there. The admin passkey methods on main still take userId:, so this PR is ahead of them until refactor!: align the public API with the Swift API Design Guidelines #1355 lands, and refactor!: align the public API with the Swift API Design Guidelines #1355 has nothing to rename here.
  • deleteFactor returns nothing, like deleteUser, deleteClient and deletePasskey. Auth echoes the deleted factor back, but the caller already has it, and not decoding the body means a delete that succeeded on the server can never surface as a decoding error.
  • id is a String because Factor.id is one, so factor.id passes straight through, as it does for AuthMFA.unenroll.
  • listFactors returns [Factor], the model auth.mfa.listFactors() already decodes. Its optional friendlyName matters here, since Auth omits friendly_name when it is empty.
  • AuthAdmin.mfa and AuthAdminMFA go in the top-level supporting_symbols list rather than under either capability, because both capabilities hang off them. That is what the capability matrix docs ask for with shared symbols, and the new-symbol check fails without them.

Worth knowing

Deleting a factor also downgrades that user's sessions to AAL1 on the server (DowngradeSessionsToAAL1 in adminUserDeleteFactor), so a signed-in user loses AAL2 immediately. The doc comment says so.

Testing

AuthAdminMFATests covers request snapshots for both calls, a populated list, an empty list, and a factor without a friendly name.

I also ran both methods against a local Auth (v2.185.0) with a throwaway test that is not part of this PR:

  • Listing decoded a verified factor with a name and an unverified one without, as friendlyName == nil.
  • Deleting one left only the other; deleting it again threw AuthError with status 404 (Factor not found).
  • A user with no factors lists as []. Worth checking because the handler sends user.Factors directly, and a nil Go slice would encode as null.

The capability matrix checks from validate-sdk-compliance-swift.yml (at the pinned capability-matrix-v1.6.0) pass locally against main, with symbol graphs dumped and normalized the same way CI does.

$ swift test --skip IntegrationTests   # exit 0
8 test runs, all passed: 1331 tests in 135 suites, 333 of them in AuthTests

$ swift-format lint --recursive --strict Sources Tests   # exit 0
$ ./scripts/spell-check.sh   # exit 0, 406 files checked, 0 issues
$ ./scripts/test-docs.sh   # exit 0, no warnings from the new files

Review first

Namespace or flat. This follows the JS client (auth.admin.mfa.listFactors) and mirrors auth.mfa on the user side, next to the existing auth.admin.oauth. Admin passkeys went flat on AuthAdmin instead, so if you would rather have admin.listFactors(forUser:) to match them, it is a small move.

Adds `auth.admin.mfa` with `listFactors(forUser:)` and
`deleteFactor(id:forUser:)`, covering the two capabilities sdk-compliance.yaml
tracked as not_implemented: `auth.admin.list_mfa_factors` and
`auth.admin.delete_mfa_factor`.
@AndroidPoet
AndroidPoet requested review from a team and grdsdev as code owners September 16, 2026 16:30
@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 71de9c16-27e0-40e4-8cba-1fd481c9b1e1

📥 Commits

Reviewing files that changed from the base of the PR and between a61ef23 and c5cbe77.

📒 Files selected for processing (4)
  • Sources/Auth/AuthAdmin.swift
  • Sources/Auth/AuthAdminMFA.swift
  • Tests/AuthTests/AuthAdminMFATests.swift
  • sdk-compliance.yaml

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Summary

Summary by CodeRabbit

  • New Features
    • Added server-side administration for user multi-factor authentication.
    • Administrators can list a user’s enrolled MFA factors.
    • Administrators can delete a user’s MFA factor.
  • Documentation
    • Updated authentication administration documentation to include MFA management.
  • Tests
    • Added coverage for listing and deleting MFA factors, including empty results and factor names.

Walkthrough

The change adds the public AuthAdmin.mfa namespace and the AuthAdminMFA type. The type lists a user’s MFA factors with GET and deletes a factor with DELETE. Both operations resolve the configured client and require the secret key. Tests cover factor decoding, empty results, missing friendly names, and deletion. The compliance manifest marks both features as implemented.

Sequence Diagram(s)

sequenceDiagram
  participant Application
  participant AuthAdminMFA
  participant Dependencies
  participant APIClient
  participant MFAAPI
  Application->>AuthAdminMFA: listFactors or deleteFactor
  AuthAdminMFA->>Dependencies: resolve client configuration
  AuthAdminMFA->>APIClient: build authenticated request
  APIClient->>MFAAPI: GET or DELETE admin factor endpoint
  MFAAPI-->>APIClient: factor response
  APIClient-->>AuthAdminMFA: decoded factors or completion
  AuthAdminMFA-->>Application: return result
Loading

Priority: ⬇️ Low

Change: Feature

Merge Risk: ⚪ Minimal · up to c5cbe

The admin MFA list and delete APIs follow the established endpoint contract, with no actionable merge-blocking issue identified.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

1 participant