Skip to content

feat: add a feature for an admin to reset the mfa of another user#4270

Open
tchoumi313 wants to merge 2 commits into
mainfrom
CA-1708-add-a-feature-for-an-admin-to-reset-the-mfa-of-another-user
Open

feat: add a feature for an admin to reset the mfa of another user#4270
tchoumi313 wants to merge 2 commits into
mainfrom
CA-1708-add-a-feature-for-an-admin-to-reset-the-mfa-of-another-user

Conversation

@tchoumi313
Copy link
Copy Markdown
Contributor

@tchoumi313 tchoumi313 commented Jun 5, 2026

What & why

Enable admin to reset the MFA of their users without having to open a ticket

Closes #

Test plan

image image

Checklist

  • PR title follows Conventional Commits (! set if this is a breaking change)
  • One focused change, branched off main
  • CLA accepted (first contribution only)

Backend — if you touched backend/

  • ruff format run, no new linter errors
  • Migrations generated with makemigrations and committed (or none needed)
  • New dependencies checked for known vulnerabilities and called out above

Frontend — if you touched frontend/

  • Svelte 5 syntax; pnpm run lint and pnpm run format run
  • New/changed UI strings added to frontend/messages/en.json (keep keys, preserve {tokens})
  • Clicked through the change in the dev server; screenshots attached for UI changes

Other — libraries, CI, infra

  • Library changes built via the tools/ script
  • CI / build changes run green

Tests & documentation — definition of done

  • Tests added or updated and passing locally — backend poetry run pytest, frontend pnpm run test / ./tests/e2e-tests.sh (if relevant)
  • Regression test added for bug fixes (if relevant)
  • product-docs/ updated, new pages listed in SUMMARY.md, vocabulary terms added (if relevant)
  • No tests or docs needed for this change — why:

Summary by CodeRabbit

Release Notes

  • New Features

    • Administrators can now reset multi-factor authentication (MFA) for users who have lost access to their second factors.
    • Added MFA reset interface on the user edit page with confirmation workflow.
  • Documentation

    • Updated MFA configuration documentation with admin recovery procedures and guidelines.

Adds a new admin-only endpoint that wipes all MFA authenticators (TOTP,WebAuthn, recovery codes) for a target user so they can re-enroll on next login, covering the lost-phone / lost-key recovery scenario.
…munity into CA-1708-add-a-feature-for-an-admin-to-reset-the-mfa-of-another-user
@tchoumi313 tchoumi313 self-assigned this Jun 5, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 5, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces an admin-initiated MFA reset feature allowing administrators to wipe multi-factor authentication from users who have lost their second factors. It includes a backend REST API endpoint (POST /api/iam/reset-mfa/), a frontend confirmation form, comprehensive test coverage, internationalization for 24 languages, and product documentation.

Changes

Admin MFA Reset Functionality

Layer / File(s) Summary
Backend API endpoint for reset-mfa
backend/iam/serializers.py, backend/iam/views.py, backend/iam/urls.py, backend/app_tests/api/test_api_reset_mfa.py
ResetMFASerializer accepts a target user ID; ResetMFAView is an authenticated, admin-only POST endpoint that deletes all Authenticator records for the user, rejects self-reset with HTTP 403, returns HTTP 400 when target has no MFA, and logs the deletion. Tests verify admin permissions, self-protection, no-MFA error handling, non-admin rejection, and unauthenticated access rejection.
User serializer MFA visibility
backend/core/serializers.py
UserWriteSerializer adds has_mfa_enabled as a read-only boolean field so frontend can detect when a user has MFA enrolled.
Frontend type schema
frontend/src/lib/utils/schemas.ts
UserEditSchema adds optional has_mfa_enabled?: boolean field.
User edit page reset-MFA action
frontend/src/routes/(app)/(internal)/users/[id=uuid]/edit/+page.svelte
Computes isAdmin, isSelf, and showResetMFA state; conditionally renders a reset-MFA card with link to reset-mfa sub-route when the admin is not editing themselves and the target user has MFA enabled.
Reset-MFA page server handler
frontend/src/routes/(app)/(internal)/users/[id=uuid]/edit/reset-mfa/+page.server.ts
load fetches the target user and returns a localized page title; actions.default POSTs to backend endpoint, parses error responses, sets flash messages, and redirects on success.
Reset-MFA page confirmation UI
frontend/src/routes/(app)/(internal)/users/[id=uuid]/edit/reset-mfa/+page.svelte
Renders a destructive action form requiring user to type the localized "yes" confirmation string; disables submit until input matches; uses use:enhance for SvelteKit form handling.
Internationalization
frontend/messages/*.json (24 languages)
Adds MFA reset message keys (resetMFA, resetMFA1/2, resetMFALinkText, resetMFAExplanation, resetMFAWarning, mfaResetSuccessfully, errorResettingMFA, cannotResetOwnMFA, userHasNoMFAEnabled) to all supported language catalogs.
Product documentation
product-docs/configuration/mfa.md, product-docs/configuration/organization/iam-model.md, product-docs/configuration/organization/users.md
Adds "Admin recovery" section to MFA docs detailing the step-by-step admin flow, confirmation requirement, and audit notes; documents MFA reset in IAM model and user management reference; notes that only Global - administrator members can perform resets and self-reset is blocked.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

High Value

Suggested reviewers

  • eric-intuitem
  • nas-tabchiche

Poem

🐰 A rabbit's tale of MFA reset,
When second factors are lost and beset,
Admins now wield their gentle paw,
To restore access with caution and law,
In twenty-four tongues, the message takes flight! 🌍✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: add a feature for an admin to reset the mfa of another user' clearly describes the main change: adding admin capability to reset user MFA. It follows Conventional Commits format and is specific about what was added.
Description check ✅ Passed The PR description includes a 'What & why' section explaining the feature purpose, a test plan with screenshots demonstrating the UI changes, and a completed checklist. However, the issue number is missing (shows 'Closes #' without a number) and some backend/CI items remain unchecked.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch CA-1708-add-a-feature-for-an-admin-to-reset-the-mfa-of-another-user

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 and usage tips.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 5, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
frontend/src/routes/(app)/(internal)/users/[id=uuid]/edit/reset-mfa/+page.server.ts (1)

8-12: 💤 Low value

Consider adding error handling for the user fetch.

If the user ID is invalid or the user doesn't exist, this fetch will fail without a try/catch block. While SvelteKit will handle the error, adding explicit error handling would provide better UX.

🛡️ Suggested error handling
 export const load: PageServerLoad = async ({ params, fetch }) => {
 	const objectEndpoint = `${BASE_API_URL}/users/${params.id}/object/`;
-	const object = await fetch(objectEndpoint).then((res) => res.json());
+	const res = await fetch(objectEndpoint);
+	if (!res.ok) {
+		throw error(res.status, 'User not found');
+	}
+	const object = await res.json();
 	return { object, title: m.resetMFA() };
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/src/routes/`(app)/(internal)/users/[id=uuid]/edit/reset-mfa/+page.server.ts
around lines 8 - 12, The load function (export const load: PageServerLoad)
currently fetches the user object without error handling; wrap the fetch to
`${BASE_API_URL}/users/${params.id}/object/` in a try/catch and check the
response.ok before calling res.json(), and on failure either throw a SvelteKit
error (with appropriate status and message) or return a controlled value (e.g.,
null plus an error flag) so the page can show a friendly UX; update the return
to still include title: m.resetMFA() and the handled error/object shape so
downstream code can render an error state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/iam/views.py`:
- Around line 492-500: Replace the two-step exists-then-delete with a single
delete-then-check: call Authenticator.objects.filter(user=target_user).delete()
first (assign to deleted_count, _), then if deleted_count == 0 return the same
Response({"error": "userHasNoMFAEnabled"}, status=status.HTTP_400_BAD_REQUEST);
otherwise continue with the existing flow (e.g., logging via logger.warning and
any subsequent cleanup) so you avoid the race between exists() and delete().

In `@frontend/messages/pl.json`:
- Line 2368: Replace the Polish term "składnika" with the correct MFA term
"czynnika" in the localization entries: update the value for key "resetMFA1"
from "Jeśli ten użytkownik utracił dostęp do drugiego składnika, możesz" to
"Jeśli ten użytkownik utracił dostęp do drugiego czynnika, możesz" and likewise
change the message at the nearby entry on line 2371 (the string that currently
says "Użytkownik straci dostęp do drugiego składnika...") to use "czynnika"
instead.

In `@product-docs/configuration/mfa.md`:
- Around line 83-85: Replace the hard-coded instruction "type `yes`" in the
confirmation step of the MFA reset docs (the line containing: "On the
confirmation page, type `yes` and submit." and its accompanying caption/figure)
with a locale-agnostic instruction such as "type the localized word for 'yes'
shown on screen" so the guidance matches the UI validation across locales;
update both the inline step text and the figure caption to refer to the
localized confirmation word.

---

Nitpick comments:
In
`@frontend/src/routes/`(app)/(internal)/users/[id=uuid]/edit/reset-mfa/+page.server.ts:
- Around line 8-12: The load function (export const load: PageServerLoad)
currently fetches the user object without error handling; wrap the fetch to
`${BASE_API_URL}/users/${params.id}/object/` in a try/catch and check the
response.ok before calling res.json(), and on failure either throw a SvelteKit
error (with appropriate status and message) or return a controlled value (e.g.,
null plus an error flag) so the page can show a friendly UX; update the return
to still include title: m.resetMFA() and the handled error/object shape so
downstream code can render an error state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 84584281-dc61-4c64-8e4c-5485d87a30f5

📥 Commits

Reviewing files that changed from the base of the PR and between 6707fb2 and f3266ca.

⛔ Files ignored due to path filters (2)
  • product-docs/.gitbook/assets/mfa-in-settings.png is excluded by !**/*.png
  • product-docs/.gitbook/assets/mfa-reset-confirmation.png is excluded by !**/*.png
📒 Files selected for processing (37)
  • backend/app_tests/api/test_api_reset_mfa.py
  • backend/core/serializers.py
  • backend/iam/serializers.py
  • backend/iam/urls.py
  • backend/iam/views.py
  • frontend/messages/ar.json
  • frontend/messages/cs.json
  • frontend/messages/da.json
  • frontend/messages/de.json
  • frontend/messages/el.json
  • frontend/messages/en.json
  • frontend/messages/es.json
  • frontend/messages/et.json
  • frontend/messages/fr.json
  • frontend/messages/hi.json
  • frontend/messages/hr.json
  • frontend/messages/hu.json
  • frontend/messages/id.json
  • frontend/messages/it.json
  • frontend/messages/ko.json
  • frontend/messages/lt.json
  • frontend/messages/nl.json
  • frontend/messages/pl.json
  • frontend/messages/pt.json
  • frontend/messages/ro.json
  • frontend/messages/sv.json
  • frontend/messages/tr.json
  • frontend/messages/uk.json
  • frontend/messages/ur.json
  • frontend/messages/zh.json
  • frontend/src/lib/utils/schemas.ts
  • frontend/src/routes/(app)/(internal)/users/[id=uuid]/edit/+page.svelte
  • frontend/src/routes/(app)/(internal)/users/[id=uuid]/edit/reset-mfa/+page.server.ts
  • frontend/src/routes/(app)/(internal)/users/[id=uuid]/edit/reset-mfa/+page.svelte
  • product-docs/configuration/mfa.md
  • product-docs/configuration/organization/iam-model.md
  • product-docs/configuration/organization/users.md

Comment thread backend/iam/views.py
Comment on lines +492 to +500
authenticators = Authenticator.objects.filter(user=target_user)
if not authenticators.exists():
return Response(
{"error": "userHasNoMFAEnabled"},
status=status.HTTP_400_BAD_REQUEST,
)

deleted_count, _ = authenticators.delete()
logger.warning(
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid the exists-then-delete race in MFA reset.

The current two-step check can return success if another request deletes authenticators between the queries. Delete first and branch on deleted rows.

Suggested patch
-        authenticators = Authenticator.objects.filter(user=target_user)
-        if not authenticators.exists():
-            return Response(
-                {"error": "userHasNoMFAEnabled"},
-                status=status.HTTP_400_BAD_REQUEST,
-            )
-
-        deleted_count, _ = authenticators.delete()
+        authenticators = Authenticator.objects.filter(user=target_user)
+        deleted_rows, _ = authenticators.delete()
+        if deleted_rows == 0:
+            return Response(
+                {"error": "userHasNoMFAEnabled"},
+                status=status.HTTP_400_BAD_REQUEST,
+            )
         logger.warning(
             "Admin reset MFA for another user",
             admin_id=str(request.user.id),
             admin_email=request.user.email,
             target_user_id=str(target_user.id),
             target_user_email=target_user.email,
-            deleted_authenticators=deleted_count,
+            deleted_authenticator_rows=deleted_rows,
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
authenticators = Authenticator.objects.filter(user=target_user)
if not authenticators.exists():
return Response(
{"error": "userHasNoMFAEnabled"},
status=status.HTTP_400_BAD_REQUEST,
)
deleted_count, _ = authenticators.delete()
logger.warning(
authenticators = Authenticator.objects.filter(user=target_user)
deleted_rows, _ = authenticators.delete()
if deleted_rows == 0:
return Response(
{"error": "userHasNoMFAEnabled"},
status=status.HTTP_400_BAD_REQUEST,
)
logger.warning(
"Admin reset MFA for another user",
admin_id=str(request.user.id),
admin_email=request.user.email,
target_user_id=str(target_user.id),
target_user_email=target_user.email,
deleted_authenticator_rows=deleted_rows,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/iam/views.py` around lines 492 - 500, Replace the two-step
exists-then-delete with a single delete-then-check: call
Authenticator.objects.filter(user=target_user).delete() first (assign to
deleted_count, _), then if deleted_count == 0 return the same Response({"error":
"userHasNoMFAEnabled"}, status=status.HTTP_400_BAD_REQUEST); otherwise continue
with the existing flow (e.g., logging via logger.warning and any subsequent
cleanup) so you avoid the race between exists() and delete().

Comment thread frontend/messages/pl.json
"builderQuestionTypeMultipleChoice": "Wielokrotny wybór",
"builderQuestionTypeDate": "Data",
"resetMFA": "Zresetuj MFA",
"resetMFA1": "Jeśli ten użytkownik utracił dostęp do drugiego składnika, możesz",
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Terminology: "składnik" should be "czynnik" for MFA context.

In lines 2368 and 2371, "drugiego składnika" (second component) is used, but the standard Polish IT terminology for multi-factor authentication is "drugi czynnik" (second factor). "Składnik" typically means component or ingredient, while "czynnik" means factor — the correct term in this security context.

Suggested correction:

  • Line 2368: "Jeśli ten użytkownik utracił dostęp do drugiego czynnika, możesz"
  • Line 2371: "Użytkownik straci dostęp do drugiego czynnika, dopóki nie zarejestruje go ponownie."

Also applies to: 2371-2371

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/messages/pl.json` at line 2368, Replace the Polish term "składnika"
with the correct MFA term "czynnika" in the localization entries: update the
value for key "resetMFA1" from "Jeśli ten użytkownik utracił dostęp do drugiego
składnika, możesz" to "Jeśli ten użytkownik utracił dostęp do drugiego czynnika,
możesz" and likewise change the message at the nearby entry on line 2371 (the
string that currently says "Użytkownik straci dostęp do drugiego składnika...")
to use "czynnika" instead.

Comment on lines +83 to +85
3. On the confirmation page, type `yes` and submit.

<figure><img src="../.gitbook/assets/mfa-reset-confirmation.png" alt=""><figcaption><p>The confirmation page requires typing <code>yes</code> before the reset can be submitted.</p></figcaption></figure>
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use locale-agnostic wording for confirmation input

The docs currently require typing literal yes, but the UI validates against the localized translation of “yes”. This instruction will be wrong outside English locales. Recommend changing to “type the localized word for ‘yes’ shown on screen”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@product-docs/configuration/mfa.md` around lines 83 - 85, Replace the
hard-coded instruction "type `yes`" in the confirmation step of the MFA reset
docs (the line containing: "On the confirmation page, type `yes` and submit."
and its accompanying caption/figure) with a locale-agnostic instruction such as
"type the localized word for 'yes' shown on screen" so the guidance matches the
UI validation across locales; update both the inline step text and the figure
caption to refer to the localized confirmation word.

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