MFA Recovery codes: allow users to download their recovery codes (#50267)

## What kind of change does this PR introduce?

After users have set up a new MFA (first or not), we must:

- check whether recovery codes have already been generated
- if there are none, generate recovery codes and display them, "forcing"
users to copy them
- if already generated, show them how many are still available

> [!NOTE]
> The _Delete my recovery codes_ button in last screenshot only appear
on local and staging environments

## How to test

- On an account that doesn't have recovery codes generated yet and has
an MFA added
- You should see an admonition suggesting to generate the codes

## Screenshots

<img width="729" height="306" alt="image"
src="https://github.com/user-attachments/assets/79ba3870-4ef8-4571-9fd6-36eed20c9c24"
/>

<img width="550" height="356" alt="image"
src="https://github.com/user-attachments/assets/1632611a-996a-470d-b6cd-a4693b0f4602"
/>

<img width="719" height="205" alt="image"
src="https://github.com/user-attachments/assets/73cef611-05cf-4fac-bbd2-243f9b28e48d"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added support for generating, copying, and confirming MFA recovery
codes.
- Added recovery-code status visibility, including remaining and
exhausted codes.
  - Added the ability to delete recovery codes with confirmation.
- Added clear loading, success, and error states for recovery-code
actions.
  - Recovery-code status refreshes after codes are generated or deleted.

- **Bug Fixes**
- Recovery-code notices now remain visible when all codes have been
used.
  - Recovery-code dialogs can now be closed after generation errors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Gildas Garcia authored and GitHub committed 2026-09-15 11:16:47 +02:00
1 parent 24f8549e41
commit 63bedef77f
12 files changed
+516 -61

No files matched your search

+51 -51
View File
@@ -16,20 +16,20 @@ catalogs:
specifier: ^10.59.0
version: 10.59.0
'@supabase/auth-js':
specifier: 2.112.4
version: 2.112.4
specifier: 2.116.0
version: 2.116.0
'@supabase/postgrest-js':
specifier: 2.112.4
version: 2.112.4
specifier: 2.116.0
version: 2.116.0
'@supabase/realtime-js':
specifier: 2.112.4
version: 2.112.4
specifier: 2.116.0
version: 2.116.0
'@supabase/ssr':
specifier: 0.10.2
version: 0.10.2
'@supabase/supabase-js':
specifier: 2.112.4
version: 2.112.4
specifier: 2.116.0
version: 2.116.0
'@tanstack/react-router':
specifier: ^1.169.2
version: 1.170.10
@@ -379,7 +379,7 @@ importers:
version: 10.59.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)(next@15.5.25(@babel/core@7.29.7(supports-color@8.1.1))(@opentelemetry/api@1.9.1)(@playwright/test@1.59.1)(@types/node@22.13.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(react@19.2.6)(supports-color@8.1.1)(vite@8.2.1(@types/node@22.13.14)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.77.4)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(webpack@5.105.4(esbuild@0.28.1))
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.112.4(@opentelemetry/api@1.9.1)
version: 2.116.0(@opentelemetry/api@1.9.1)
'@tanstack/react-query':
specifier: ~5.83.0
version: 5.83.0(react@19.2.6)
@@ -1040,7 +1040,7 @@ importers:
version: 1.0.32(@types/node@22.13.14)(supports-color@8.1.1)
'@supabase/auth-js':
specifier: 'catalog:'
version: 2.112.4
version: 2.116.0
'@supabase/mcp-server-supabase':
specifier: ^0.12.0
version: 0.12.0(@modelcontextprotocol/server@2.0.0)(zod@3.25.76)
@@ -1049,13 +1049,13 @@ importers:
version: link:../../packages/pg-meta
'@supabase/realtime-js':
specifier: 'catalog:'
version: 2.112.4
version: 2.116.0
'@supabase/shared-types':
specifier: 0.1.95
version: 0.1.95
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.112.4(@opentelemetry/api@1.9.1)
version: 2.116.0(@opentelemetry/api@1.9.1)
'@tanstack/react-devtools':
specifier: ^0.10.3
version: 0.10.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(solid-js@1.9.13)
@@ -1494,7 +1494,7 @@ importers:
version: 0.1.0(@opentelemetry/api@1.9.1)
'@supabase/postgrest-js':
specifier: 'catalog:'
version: 2.112.4
version: 2.116.0
'@supabase/supa-mdx-lint':
specifier: 0.2.6-alpha
version: 0.2.6-alpha
@@ -1636,10 +1636,10 @@ importers:
version: 1.6.0
'@supabase/ssr':
specifier: 'catalog:'
version: 0.10.2(@supabase/supabase-js@2.112.4(@opentelemetry/api@1.9.1))
version: 0.10.2(@supabase/supabase-js@2.116.0(@opentelemetry/api@1.9.1))
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.112.4(@opentelemetry/api@1.9.1)
version: 2.116.0(@opentelemetry/api@1.9.1)
'@tanstack/react-router':
specifier: 'catalog:'
version: 1.170.10(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
@@ -1786,10 +1786,10 @@ importers:
version: 10.59.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)(next@15.5.25(@babel/core@7.29.7(supports-color@8.1.1))(@opentelemetry/api@1.9.1)(@playwright/test@1.59.1)(@types/node@22.13.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(react@19.2.6)(supports-color@8.1.1)(vite@8.2.1(@types/node@22.13.14)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.77.4)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(webpack@5.105.4(esbuild@0.28.1))
'@supabase/ssr':
specifier: 'catalog:'
version: 0.10.2(@supabase/supabase-js@2.112.4(@opentelemetry/api@1.9.1))
version: 0.10.2(@supabase/supabase-js@2.116.0(@opentelemetry/api@1.9.1))
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.112.4(@opentelemetry/api@1.9.1)
version: 2.116.0(@opentelemetry/api@1.9.1)
'@typescript/native':
specifier: 'catalog:'
version: typescript@7.0.2
@@ -2066,13 +2066,13 @@ importers:
dependencies:
'@supabase/postgrest-js':
specifier: 'catalog:'
version: 2.112.4
version: 2.116.0
'@supabase/ssr':
specifier: 'catalog:'
version: 0.10.2(@supabase/supabase-js@2.112.4(@opentelemetry/api@1.9.1))
version: 0.10.2(@supabase/supabase-js@2.116.0(@opentelemetry/api@1.9.1))
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.112.4(@opentelemetry/api@1.9.1)
version: 2.116.0(@opentelemetry/api@1.9.1)
'@vueuse/core':
specifier: ^14.1.0
version: 14.1.0(vue@3.5.35(typescript@6.0.2))
@@ -2156,7 +2156,7 @@ importers:
version: 1.59.1
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.112.4(@opentelemetry/api@1.9.1)
version: 2.116.0(@opentelemetry/api@1.9.1)
cross-fetch:
specifier: ^4.1.0
version: 4.1.0(encoding@0.1.13)
@@ -2203,7 +2203,7 @@ importers:
version: 0.18.5
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.112.4(@opentelemetry/api@1.9.1)
version: 2.116.0(@opentelemetry/api@1.9.1)
ai:
specifier: 5.0.52
version: 5.0.52(zod@3.25.76)
@@ -2300,10 +2300,10 @@ importers:
dependencies:
'@supabase/auth-js':
specifier: 'catalog:'
version: 2.112.4
version: 2.116.0
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.112.4(@opentelemetry/api@1.9.1)
version: 2.116.0(@opentelemetry/api@1.9.1)
'@types/dat.gui':
specifier: ^0.7.12
version: 0.7.12
@@ -2781,7 +2781,7 @@ importers:
version: 0.1.6(encoding@0.1.13)(supports-color@8.1.1)
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.112.4(@opentelemetry/api@1.9.1)
version: 2.116.0(@opentelemetry/api@1.9.1)
'@tanstack/react-table':
specifier: 'catalog:'
version: 8.21.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
@@ -7958,8 +7958,8 @@ packages:
'@supabase-labs/y-supabase@0.1.0':
resolution: {integrity: sha512-1ItaKbcImgy7ueYfJcyVYihqCKi9RAee9MKBV8wcCqSMNsrWvw4ibhW8ai91kWsewsiWzIDXVPONgcUcxagk9g==}
'@supabase/auth-js@2.112.4':
resolution: {integrity: sha512-z8DesgwLzKM5PiT0yNmJU8VJyh1zAhYi+20Z7drdJQLXg/wWW4yGt/un+He5ERYUo94Vz66t5aeyr1DIDemI5A==}
'@supabase/auth-js@2.116.0':
resolution: {integrity: sha512-Cmosty12gyKGK9N3bQb+lMmuAFev5nmUzaR1AsmZHqKOAGzqX1VQzmp49CNPwOx/pw0H9Qqk4rs9yhwTlKpfDg==}
engines: {node: '>=22.0.0'}
'@supabase/cli-darwin-arm64@2.114.0':
@@ -8006,8 +8006,8 @@ packages:
cpu: [x64]
os: [win32]
'@supabase/functions-js@2.112.4':
resolution: {integrity: sha512-DQ0aVH8wSQAccVqNoEkec62qCu2QRNyoGN53RqsVZ1k6F1zq4/v8scrlR6LNT2RJmT97apiTmORijPVhErCS2g==}
'@supabase/functions-js@2.116.0':
resolution: {integrity: sha512-E+VOc2QDcni/fySqkBFiZhnoB3SGydEdZgFI6/dEAGAHx6yEhB46TN9qb2wXs+E+RSzOBV0R6dasiSlw4xlZAA==}
engines: {node: '>=22.0.0'}
'@supabase/mcp-server-supabase@0.12.0':
@@ -8026,12 +8026,12 @@ packages:
'@supabase/phoenix@0.4.5':
resolution: {integrity: sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==}
'@supabase/postgrest-js@2.112.4':
resolution: {integrity: sha512-uaubtPSeg2TR4wrtfQoQWgkTAe+a0qWX2KhmwvTfNl5mGN9+U7owiJt6abk3o/V6O899PSRD1yzxs5RlF4xTug==}
'@supabase/postgrest-js@2.116.0':
resolution: {integrity: sha512-kGpVZTDHxFTJS3tu+rU0iTAZ+4U0bcLVjxwCk8f3gRhjw3qdCZjTBlgYvc4kGH2XccmAzbkKwXL/mrNHMGSc+A==}
engines: {node: '>=22.0.0'}
'@supabase/realtime-js@2.112.4':
resolution: {integrity: sha512-vZ+j079SKrM0Xiq7MJCvQKLDpaH2kfKfLY68xuQE1sqsCsMmx1CyrDBJHsxZ3cX01VOs5SI9igmoZAF3BmdZxw==}
'@supabase/realtime-js@2.116.0':
resolution: {integrity: sha512-MHAnlXxi2s6yiJsZsQMfs2B3RFxeVfQWxerqYhIMqcCQV/FuY3LIeouPEkXw/ah7wUWMLYwempF9MOCUScyddg==}
engines: {node: '>=22.0.0'}
'@supabase/shared-types@0.1.95':
@@ -8045,8 +8045,8 @@ packages:
peerDependencies:
'@supabase/supabase-js': ^2.102.1
'@supabase/storage-js@2.112.4':
resolution: {integrity: sha512-lQ0JemuTlMIXVKgSci1qez8yPnM5hyDngeAfEBjZS2Om4D+Cus0EE5BE6glFobrxdyii1OF4UzWfF0zcQgDq5A==}
'@supabase/storage-js@2.116.0':
resolution: {integrity: sha512-6/3hR6vccBP6oGM5B6RfbwZcTCKmQOodd/ZWQdsw8yJsU5zO/a//oBL6yLnmgxcjnHSrelW8rsO7hL5DPybyUQ==}
engines: {node: '>=22.0.0'}
'@supabase/supa-mdx-lint-darwin@0.2.6-alpha':
@@ -8184,8 +8184,8 @@ packages:
resolution: {integrity: sha512-iqJHDk/ToyxFMa/um9A5gsp9jYN7iI0cIabNq9nyBpK/Yat/J9I2IIMueQwIMy3TsG6a2qdPyUkZkuPC37SdRg==}
hasBin: true
'@supabase/supabase-js@2.112.4':
resolution: {integrity: sha512-UiCX1udlFY1fQQrO7Z3GU7obQsju0w5Vk9mOOwalfo/+Gy+tahWVenSSuu5E/GTy/q//HxvGv2IrCdW66/61kw==}
'@supabase/supabase-js@2.116.0':
resolution: {integrity: sha512-YyWmKXt2NspV9iO8FPnlswUFJIRnrLd3oTCb+3ZyYRuKZtBH0xCUDgnUqoyA0fGUxpM/UhfwDjYf/dht/9bp7g==}
engines: {node: '>=22.0.0'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0'
@@ -24548,13 +24548,13 @@ snapshots:
'@supabase-labs/y-supabase@0.1.0(@opentelemetry/api@1.9.1)':
dependencies:
'@supabase/supabase-js': 2.112.4(@opentelemetry/api@1.9.1)
'@supabase/supabase-js': 2.116.0(@opentelemetry/api@1.9.1)
y-protocols: 1.0.7(yjs@13.6.29)
yjs: 13.6.29
transitivePeerDependencies:
- '@opentelemetry/api'
'@supabase/auth-js@2.112.4':
'@supabase/auth-js@2.116.0':
dependencies:
tslib: 2.8.1
@@ -24582,7 +24582,7 @@ snapshots:
'@supabase/cli-windows-x64@2.114.0':
optional: true
'@supabase/functions-js@2.112.4':
'@supabase/functions-js@2.116.0':
dependencies:
tslib: 2.8.1
@@ -24604,11 +24604,11 @@ snapshots:
'@supabase/phoenix@0.4.5': {}
'@supabase/postgrest-js@2.112.4':
'@supabase/postgrest-js@2.116.0':
dependencies:
tslib: 2.8.1
'@supabase/realtime-js@2.112.4':
'@supabase/realtime-js@2.116.0':
dependencies:
'@supabase/phoenix': 0.4.5
tslib: 2.8.1
@@ -24624,12 +24624,12 @@ snapshots:
- encoding
- supports-color
'@supabase/ssr@0.10.2(@supabase/supabase-js@2.112.4(@opentelemetry/api@1.9.1))':
'@supabase/ssr@0.10.2(@supabase/supabase-js@2.116.0(@opentelemetry/api@1.9.1))':
dependencies:
'@supabase/supabase-js': 2.112.4(@opentelemetry/api@1.9.1)
'@supabase/supabase-js': 2.116.0(@opentelemetry/api@1.9.1)
cookie: 1.0.2
'@supabase/storage-js@2.112.4':
'@supabase/storage-js@2.116.0':
dependencies:
iceberg-js: 0.8.1
tslib: 2.8.1
@@ -24730,13 +24730,13 @@ snapshots:
'@supabase/supa-mdx-lint-win32-x64': 0.3.2
node-pty: 1.0.0
'@supabase/supabase-js@2.112.4(@opentelemetry/api@1.9.1)':
'@supabase/supabase-js@2.116.0(@opentelemetry/api@1.9.1)':
dependencies:
'@supabase/auth-js': 2.112.4
'@supabase/functions-js': 2.112.4
'@supabase/postgrest-js': 2.112.4
'@supabase/realtime-js': 2.112.4
'@supabase/storage-js': 2.112.4
'@supabase/auth-js': 2.116.0
'@supabase/functions-js': 2.116.0
'@supabase/postgrest-js': 2.116.0
'@supabase/realtime-js': 2.116.0
'@supabase/storage-js': 2.116.0
optionalDependencies:
'@opentelemetry/api': 1.9.1