mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
feat(auth): make passkey two-factor authentication opt-in (#2857)
This commit is contained in:
@@ -60,7 +60,7 @@ When the caller lacks `user:view:email`, `user:view:dob`, or `user:view:ip`, Flu
|
||||
| acls<sup>8</sup> | array[string] | Effective [Admin ACLs](/admin-api/#acl-registry), with at most 111 entries |
|
||||
| traits<sup>9</sup> | array[string] | The free-form operator labels set on the account, with at most 100 entries |
|
||||
| has_totp<sup>10</sup> | boolean | Whether a TOTP authenticator is registered |
|
||||
| authenticator_types<sup>10</sup> | array[integer] | Registered [authenticator types](/http-api/users/#authenticator-types), with at most 10 entries |
|
||||
| authenticator_types<sup>10</sup> <sup>13</sup> | array[integer] | Registered [authenticator types](/http-api/users/#authenticator-types), with at most 10 entries |
|
||||
| last_active_at | ?ISO8601 timestamp | The time of the last recorded activity, or null when none is recorded |
|
||||
| last_active_ip<sup>11</sup> | ?string | The IP address the account was last active from |
|
||||
| last_active_ip_reverse<sup>11</sup> <sup>12</sup> | ?string | The reverse DNS name of that IP address |
|
||||
@@ -90,6 +90,8 @@ When the caller lacks `user:view:email`, `user:view:dob`, or `user:view:ip`, Flu
|
||||
|
||||
<sup>12</sup> Resolved live from `last_active_ip` for each response, and null when the lookup fails or returns nothing. The reverse DNS result is cached for one day
|
||||
|
||||
<sup>13</sup> `WEBAUTHN` is present only while the account chose passkeys as a second factor. A registered credential does not add it, so `has_totp` false with an empty array still describes an account holding passkeys
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
@@ -1950,7 +1952,7 @@ The passkey or security key stops authenticating immediately and cannot be resto
|
||||
|
||||
### Side effects
|
||||
|
||||
The credential record is deleted. When it was the account's final WebAuthn credential, Fluxer removes the `WEBAUTHN` [authenticator type](/http-api/users/#authenticator-types) from the account, emits [User Update](/gateway/events/#user-update) to the account's own sessions, and copies the account's authenticator types onto the bot user of every application the account owns.
|
||||
The credential record is deleted. When it was the account's final WebAuthn credential and the account had enabled passkeys as a second factor, Fluxer removes the `WEBAUTHN` [authenticator type](/http-api/users/#authenticator-types) from the account, emits [User Update](/gateway/events/#user-update) to the account's own sessions, and copies the account's authenticator types onto the bot user of every application the account owns. An account that never enabled passkeys as a second factor holds no `WEBAUTHN` type to remove.
|
||||
|
||||
[WebAuthn Credentials Update](/gateway/events/#webauthn-credentials-update) is emitted to the target account with its remaining credentials.
|
||||
|
||||
@@ -1980,7 +1982,7 @@ Clears the account's TOTP authenticator, its registered authenticator type set,
|
||||
| 404 | [error response](/admin-api/#error-response) | `UNKNOWN_USER`, because the account does not exist |
|
||||
|
||||
:::caution[Stored WebAuthn credentials survive this operation]
|
||||
Clearing the authenticator type set removes `WEBAUTHN` from the advertised authenticators, but the credential records remain and no [WebAuthn Credentials Update](/gateway/events/#webauthn-credentials-update) is emitted. Remove those with [Delete user WebAuthn credential](#delete-user-webauthn-credential).
|
||||
Clearing the authenticator type set turns passkeys off as a second factor, but the credential records remain and no [WebAuthn Credentials Update](/gateway/events/#webauthn-credentials-update) is emitted. They still answer a sudo mode challenge and a passwordless login. Remove those with [Delete user WebAuthn credential](#delete-user-webauthn-credential).
|
||||
:::
|
||||
|
||||
### Side effects
|
||||
|
||||
@@ -156,7 +156,7 @@ Sudo mode is a short-lived proof that the account holder recently re-verified a
|
||||
|
||||
A sudo proof lasts five minutes. Present it in the `X-Fluxer-Sudo-Mode-JWT` request header. An invalid, expired, or account-mismatched token produces the same response as a missing one.
|
||||
|
||||
Fluxer issues a token only for an account holding a multi-factor authenticator, so a password-only account re-verifies for each operation that requires sudo mode. [Create WebAuthn registration options](/http-api/users/mfa/#create-webauthn-registration-options) and [Disable current account](/http-api/users/current-user/#disable-current-account) issue no sudo token and return no `X-Fluxer-Sudo-Mode-JWT` response header, even for a multi-factor account. A bot account satisfies sudo mode immediately. So does an account that has neither a password nor a multi-factor authenticator.
|
||||
Fluxer issues a token only for an account holding a TOTP secret or a registered WebAuthn credential, so a password-only account re-verifies for each operation that requires sudo mode. A passkey counts here whether or not the account enabled passkeys as a second factor. The `methods` object reports `totp`, `webauthn`, and `backup_codes`, and an account holding no TOTP secret presents an unconsumed backup code under `mfa_method` of `totp`. [Create WebAuthn registration options](/http-api/users/mfa/#create-webauthn-registration-options) and [Disable current account](/http-api/users/current-user/#disable-current-account) issue no sudo token and return no `X-Fluxer-Sudo-Mode-JWT` response header, even for an account that can prove a credential. A bot account satisfies sudo mode immediately. So does an account that has no password, no TOTP secret, and no registered WebAuthn credential.
|
||||
|
||||
:::note[A sudo proof covers every account session]
|
||||
Revoking the session that obtained a proof leaves that proof valid until it expires.
|
||||
|
||||
@@ -12,7 +12,7 @@ An application is an OAuth2 client owned by one user account, and every bot acco
|
||||
|
||||
Every route except [Get public application](#get-public-application) requires a credential. A user-only route rejects a bot token and an OAuth2 bearer with 403 `ACCESS_DENIED`. Every route on this page accepts an account that has an outstanding [required action](/http-api/users/#required-actions).
|
||||
|
||||
The sudo-gated routes read `X-Fluxer-Sudo-Mode-JWT`. A valid sudo token in that header, issued to the authenticated account, satisfies [sudo mode](/http-api/users/mfa/#sudo-mode) with no body proof. Fluxer returns the same token in the `X-Fluxer-Sudo-Mode-JWT` response header. Fluxer issues no token to an account with no authenticator, so that account proves sudo mode with `password` in the body.
|
||||
The sudo-gated routes read `X-Fluxer-Sudo-Mode-JWT`. A valid sudo token in that header, issued to the authenticated account, satisfies [sudo mode](/http-api/users/mfa/#sudo-mode) with no body proof. Fluxer returns the same token in the `X-Fluxer-Sudo-Mode-JWT` response header. Fluxer issues no token to an account holding neither a TOTP secret nor a registered WebAuthn credential, so that account proves sudo mode with `password` in the body.
|
||||
|
||||
:::caution[Credentials are shown once]
|
||||
A client secret is returned only by [Create application](#create-application) and [Reset client secret](#reset-client-secret), and a [bot token](/authentication/#token-formats) only by [Create application](#create-application) and [Reset bot token](#reset-bot-token).
|
||||
|
||||
@@ -135,16 +135,19 @@ The ticket and the method list a client needs to finish a login with a second fa
|
||||
| --- | --- | --- |
|
||||
| mfa | boolean | Always `true` |
|
||||
| ticket<sup>1</sup> | string | The ticket consumed by a TOTP or WebAuthn MFA completion |
|
||||
| allowed_methods<sup>2</sup> | array[string] | The methods available to this account, drawn from `totp` and `webauthn` (max 10 items) |
|
||||
| allowed_methods<sup>2</sup> | array[string] | The methods available to this account, drawn from `totp`, `webauthn`, and `backup_codes` (max 10 items) |
|
||||
| totp | boolean | Whether the account holds the time-based one-time password authenticator type |
|
||||
| webauthn | boolean | Whether at least one WebAuthn credential is registered |
|
||||
| webauthn<sup>3</sup> | boolean | Whether passkeys are enabled as a second factor |
|
||||
| backup_codes | boolean | Whether the account holds at least one unconsumed backup code |
|
||||
|
||||
<sup>1</sup> The ticket is retained for five minutes, is destroyed after five failed code attempts, and is consumed by the completion that issues the session
|
||||
|
||||
<sup>2</sup> The array lists the account's authenticators in the fixed order `totp` then `webauthn`, and it omits either value the account does not hold
|
||||
<sup>2</sup> The array lists the account's methods in the fixed order `totp`, `webauthn`, then `backup_codes`, and it omits any value the account cannot use
|
||||
|
||||
:::note[TOTP completion accepts an unconsumed backup code]
|
||||
There is no backup code completion route and `backup_code` never appears in `allowed_methods`. [Complete login with TOTP](#complete-login-with-totp) reads it from `code`, and only while the account holds the TOTP authenticator type. Fluxer never reports whether one remains.
|
||||
<sup>3</sup> The account holds the WebAuthn [authenticator type](/http-api/users/#authenticator-types), which it takes from [Set WebAuthn two-factor authentication](/http-api/users/mfa/#set-webauthn-two-factor-authentication). A registered credential alone does not set it, except on an account with no password credential, where the passkey is the primary credential and always counts
|
||||
|
||||
:::note[Backup codes have no completion route of their own]
|
||||
[Complete login with TOTP](#complete-login-with-totp) reads a backup code from `code`, which is what `backup_codes` in `allowed_methods` points a client at. An account whose only second factor is passkeys can present one there, so a client offers the code input alongside the passkey prompt.
|
||||
:::
|
||||
|
||||
<a id="webauthn-authentication-options"></a>
|
||||
@@ -574,7 +577,7 @@ MFA verification also permits 10 failed attempts per account in 15 minutes and d
|
||||
|
||||
<sup>1</sup> A validated authenticator code is claimed for 30 seconds, so the same code cannot be presented twice. A backup code is consumed permanently on the attempt that accepts it
|
||||
|
||||
An expired or unknown ticket returns the field code `SESSION_TIMEOUT` on `ticket`. An account with no TOTP enrolment returns the field code `TOTP_NOT_ENABLED` on `code`. Both an incorrect code and any code presented after the per-account or per-ticket attempt allowance is exhausted return the field code `INVALID_CODE` on `code`. A ticket that resolves to a bot account returns 403 `BOT_USER_AUTH_ENDPOINT_ACCESS_DENIED`.
|
||||
An expired or unknown ticket returns the field code `SESSION_TIMEOUT` on `ticket`. An account with no TOTP enrolment reads `code` as a backup code, and it returns the field code `TOTP_NOT_ENABLED` on `code` only when it holds no unconsumed backup code either. Both an incorrect code and any code presented after the per-account or per-ticket attempt allowance is exhausted return the field code `INVALID_CODE` on `code`. A ticket that resolves to a bot account returns 403 `BOT_USER_AUTH_ENDPOINT_ACCESS_DENIED`.
|
||||
|
||||
### Response
|
||||
|
||||
@@ -647,7 +650,7 @@ Consumes the MFA ticket, verifies the WebAuthn assertion against the challenge,
|
||||
|
||||
<sup>1</sup> The challenge is consumed before verification and is accepted only when its bound context, account, and ticket all match this request
|
||||
|
||||
An expired or unknown ticket returns the field code `SESSION_TIMEOUT` on `ticket`. A challenge mismatch, an unknown credential, a stored public key that cannot be decoded, a signature counter that the authenticator did not advance, and a failed signature verification all return 401 `PASSKEY_AUTHENTICATION_FAILED`. A verified assertion whose reported signature counter cannot be read returns 500 `INVALID_WEBAUTHN_AUTHENTICATION_COUNTER`.
|
||||
An expired or unknown ticket returns the field code `SESSION_TIMEOUT` on `ticket`. A ticket for an account that does not count passkeys as its second factor returns 400 `TWO_FACTOR_REQUIRED`, which is what a TOTP account holding registered credentials without [Set WebAuthn two-factor authentication](/http-api/users/mfa/#set-webauthn-two-factor-authentication) receives. A challenge mismatch, an unknown credential, a stored public key that cannot be decoded, a signature counter that the authenticator did not advance, and a failed signature verification all return 401 `PASSKEY_AUTHENTICATION_FAILED`. A verified assertion whose reported signature counter cannot be read returns 500 `INVALID_WEBAUTHN_AUTHENTICATION_COUNTER`.
|
||||
|
||||
This operation shares the MFA attempt allowances of [complete login with TOTP](#complete-login-with-totp). It permits 10 failed attempts per account in 15 minutes and destroys the ticket after 5 failed attempts. An exhausted allowance returns the field code `INVALID_CODE` on `ticket`.
|
||||
|
||||
@@ -656,7 +659,7 @@ This operation shares the MFA attempt allowances of [complete login with TOTP](#
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | [authentication token response](#authentication-token-response) | The ticket and assertion are accepted |
|
||||
| 400 | [error response](/http-api/#error-response) | The body or ticket is invalid |
|
||||
| 400 | [error response](/http-api/#error-response) | The body or ticket is invalid, or passkeys are not the account's second factor (`TWO_FACTOR_REQUIRED`) |
|
||||
| 401 | [error response](/http-api/#error-response) | Challenge or assertion verification fails |
|
||||
| 403 | [error response](/http-api/#error-response) | SSO enforcement rejects the operation or the ticket resolves to a bot account |
|
||||
| 404 | [error response](/http-api/#error-response) | The ticket resolves to an account that no longer exists |
|
||||
@@ -929,6 +932,8 @@ A token in the path reaches proxy logs, browser history, and referrer headers. A
|
||||
|
||||
Consumes a valid reset token and replaces the account password, then issues a new session or an MFA challenge. Authentication is not required. Returns an [authentication token response](#authentication-token-response) when the account has no second factor, and an [MFA challenge response](#mfa-challenge-response) when it has one.
|
||||
|
||||
Fluxer reads the second factor from the account as it stood before the reset. An account that held no password and holds at least one registered WebAuthn credential counts as having one, because the passkey was its primary credential, so it receives an MFA challenge with `webauthn` in `allowed_methods` and MUST prove the passkey before the reset yields a session.
|
||||
|
||||
### JSON body
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -943,7 +948,7 @@ An unknown or already consumed token, and a token bound to a deleted account, re
|
||||
Passwords are checked against a breached-password corpus, as they are during [registration](#register-an-account) and [email reversion](#revert-an-email-change).
|
||||
|
||||
:::caution[Resetting a password revokes every existing session]
|
||||
A successful reset terminates every authentication session on the account, including the one any other device is holding. It then issues one fresh session. An account with a second factor receives an MFA ticket, and completing MFA with that ticket creates the session.
|
||||
A successful reset terminates every authentication session on the account, including the one any other device is holding. It then issues one fresh session. An account with a second factor receives an MFA ticket, and completing MFA with that ticket creates the session. Setting a password on an account that had none does not release it from its passkey, so that account still completes MFA.
|
||||
:::
|
||||
|
||||
### Response
|
||||
@@ -961,7 +966,7 @@ A successful reset terminates every authentication session on the account, inclu
|
||||
|
||||
The operation replaces the password, records the change time, clears an expired temporary suspension, terminates every authentication session on the account, and consumes the presented reset token. Every terminated session loses its Gateway session as [shared behaviour](#shared-behaviour) states. Other outstanding reset tokens are not invalidated, so a second recovery link issued earlier still works.
|
||||
|
||||
An account with no second factor then receives one new session and its token. An account with a second factor receives a five-minute MFA ticket instead, and the MFA completion creates the session.
|
||||
An account with no second factor then receives one new session and its token. An account with a second factor receives a five-minute MFA ticket instead, and the MFA completion creates the session. An account that had no password and holds a registered WebAuthn credential is one of those, and its ticket is completed through [complete login with WebAuthn MFA](#complete-login-with-webauthn-mfa), or through [complete login with TOTP](#complete-login-with-totp) when `allowed_methods` lists `backup_codes`.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -1053,7 +1058,7 @@ The caller MUST send a valid sudo token in the `X-Fluxer-Sudo-Mode-JWT` header,
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| session_id_hashes<sup>1</sup> | array[string] | The session digests to delete (max 100 entries) |
|
||||
| password?<sup>2</sup> | string | The password proof for an account with no second factor (8-256 characters) |
|
||||
| password?<sup>2</sup> | string | The password proof for an account holding no TOTP secret and no registered WebAuthn credential (8-256 characters) |
|
||||
| mfa_method?<sup>3</sup> | string | The proof method, either `totp` or `webauthn` |
|
||||
| mfa_code? | string | The authenticator code or an unconsumed backup code when the method is `totp` (1-32 characters) |
|
||||
| webauthn_response? | [WebAuthn assertion](#webauthn-assertion) object | The assertion when the method is `webauthn` |
|
||||
@@ -1061,20 +1066,20 @@ The caller MUST send a valid sudo token in the `X-Fluxer-Sudo-Mode-JWT` header,
|
||||
|
||||
<sup>1</sup> Each value is the base64url `id_hash` from [list authentication sessions](#list-authentication-sessions). Fluxer ignores an unknown identifier, and an empty array deletes nothing and still returns 204
|
||||
|
||||
<sup>2</sup> The password is accepted only when the account has no second factor, and a value that does not match returns the field code `INVALID_PASSWORD`
|
||||
<sup>2</sup> The password is accepted only while the account holds neither a TOTP secret nor a registered WebAuthn credential, and a value that does not match returns the field code `INVALID_PASSWORD`
|
||||
|
||||
<sup>3</sup> The MFA proof is accepted only when the account has a second factor. Any failure returns the field code `INVALID_MFA_CODE` on `mfa_code`, and a successful proof issues a fresh sudo token
|
||||
<sup>3</sup> The MFA proof is accepted only while the account holds a TOTP secret or a registered WebAuthn credential, which a passkey satisfies whether or not passkeys are enabled as a second factor. Any failure returns the field code `INVALID_MFA_CODE` on `mfa_code`, and a successful proof issues a fresh sudo token
|
||||
|
||||
A `totp` method reads `mfa_code` as an authenticator code and accepts an unconsumed backup code in its place. A `webauthn` method reads `webauthn_response` and `webauthn_challenge` together, and it accepts only a challenge that was issued for the sudo context.
|
||||
A `totp` method reads `mfa_code` as an authenticator code and accepts an unconsumed backup code in its place, and it reads the value as a backup code alone while the account holds no TOTP secret. A `webauthn` method reads `webauthn_response` and `webauthn_challenge` together, and it accepts only a challenge that was issued for the sudo context.
|
||||
|
||||
:::caution[The caller can delete its own session]
|
||||
The operation deletes exactly the sessions identified by the supplied `id_hash` values. A client that wants to keep its own session MUST omit the `id_hash` of the entry whose `current` is true. Revoking the calling credential still returns 204.
|
||||
:::
|
||||
|
||||
Missing or unusable proof returns 403 `SUDO_MODE_REQUIRED`, whose error body has top-level `has_mfa` and `methods` members, and `methods` reports whether `totp` and `webauthn` are available.
|
||||
Missing or unusable proof returns 403 `SUDO_MODE_REQUIRED`, whose error body has top-level `has_mfa` and `methods` members, and `methods` reports whether `totp`, `webauthn`, and `backup_codes` are available.
|
||||
|
||||
:::note[An unclaimed account needs no proof]
|
||||
An account that has neither a password nor a second factor satisfies sudo mode with no body proof and no sudo token, and Fluxer issues none. Every other account MUST present a valid sudo token, a password, or an MFA proof.
|
||||
An account that has no password, no TOTP secret, and no registered WebAuthn credential satisfies sudo mode with no body proof and no sudo token, and Fluxer issues none. Every other account MUST present a valid sudo token, a password, or an MFA proof.
|
||||
:::
|
||||
|
||||
### Response
|
||||
@@ -1091,7 +1096,7 @@ An account that has neither a password nor a second factor satisfies sudo mode w
|
||||
|
||||
### Side effects
|
||||
|
||||
Each named session is deleted and loses its Gateway session as [shared behaviour](#shared-behaviour) states. A fresh MFA proof issues a new sudo token, while an accepted incoming token is echoed unchanged. Fluxer sets whichever token results in the `X-Fluxer-Sudo-Mode-JWT` response header. A password proof on an account with no second factor issues no token, so the header is not set unless the request already had one. No Gateway Dispatch is emitted.
|
||||
Each named session is deleted and loses its Gateway session as [shared behaviour](#shared-behaviour) states. A fresh MFA proof issues a new sudo token, while an accepted incoming token is echoed unchanged. Fluxer sets whichever token results in the `X-Fluxer-Sudo-Mode-JWT` response header. A password proof, which only an account holding neither a TOTP secret nor a registered WebAuthn credential can give, issues no token, so the header is not set unless the request already had one. No Gateway Dispatch is emitted.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -515,15 +515,15 @@ The body is optional. Fluxer reads it only when `delete_messages` is set and no
|
||||
| --- | --- | --- |
|
||||
| password?<sup>1</sup> | string | The account password, offered as the sudo proof |
|
||||
| mfa_method?<sup>2</sup> | string | The verification method, either `totp` or `webauthn` |
|
||||
| mfa_code? | string | The authenticator code (1-32 characters), supplied with the `totp` method |
|
||||
| mfa_code? | string | The authenticator code or an unconsumed backup code (1-32 characters), supplied with the `totp` method |
|
||||
| webauthn_response? | [WebAuthn assertion](/http-api/authentication/#webauthn-assertion) object | The WebAuthn assertion for sudo verification |
|
||||
| webauthn_challenge? | string | The challenge (1-256 characters) bound to the sudo mode assertion |
|
||||
|
||||
<sup>1</sup> Accepted only for an account with no configured second factor. A rejected password returns 400 `INVALID_FORM_BODY` with the code `INVALID_PASSWORD` on the path `password`
|
||||
<sup>1</sup> Accepted only for an account holding no TOTP secret and no registered WebAuthn credential. A rejected password returns 400 `INVALID_FORM_BODY` with the code `INVALID_PASSWORD` on the path `password`
|
||||
|
||||
<sup>2</sup> Accepted only for an account with a configured second factor. A rejected code or assertion returns 400 `INVALID_FORM_BODY` with the code `INVALID_MFA_CODE` on the path `mfa_code`
|
||||
<sup>2</sup> Accepted only for an account holding a TOTP secret or a registered WebAuthn credential. The `totp` method reads `mfa_code` as a backup code alone while the account holds no TOTP secret. A rejected code or assertion returns 400 `INVALID_FORM_BODY` with the code `INVALID_MFA_CODE` on the path `mfa_code`
|
||||
|
||||
An account that stores neither a password nor a second factor is verified without any proof.
|
||||
An account that stores no password, no TOTP secret, and no registered WebAuthn credential is verified without any proof.
|
||||
|
||||
### Response
|
||||
|
||||
|
||||
@@ -410,7 +410,7 @@ Transfers guild ownership to another current member and returns the updated [gui
|
||||
- The target must be a current member.
|
||||
- A bot target returns 400 `CANNOT_TRANSFER_OWNERSHIP_TO_BOT`.
|
||||
|
||||
The operation requires the [sudo verification fields](/http-api/guilds/#sudo-verification-fields). A bot credential needs no proof. An account without a usable proof receives 403 `SUDO_MODE_REQUIRED`, whose payload names its available second factors.
|
||||
The operation requires the [sudo verification fields](/http-api/guilds/#sudo-verification-fields). A bot credential needs no proof. An account without a usable proof receives 403 `SUDO_MODE_REQUIRED`, whose payload names its available proofs in `methods`, reporting `totp`, `webauthn`, and `backup_codes`.
|
||||
|
||||
### Path parameters
|
||||
|
||||
@@ -451,7 +451,7 @@ The operation changes only the guild's `owner_id`. The outgoing owner keeps ever
|
||||
|
||||
It records a [`GUILD_UPDATE`](/http-api/guild-audit-logs/#audit-actions) audit entry naming the previous and new owner in its change list, emits [Guild Audit Log Entry Create](/gateway/events/#guild-audit-log-entry-create), and emits [Guild Update](/gateway/events/#guild-update) to every session that can see the guild. A request naming the current owner changes nothing and records no audit entry. The audit log response has no `options` member for this entry.
|
||||
|
||||
An account with an enrolled second factor also receives a reissued sudo proof. A bot credential and an account whose only proof was a password receive `X-Fluxer-Sudo-Mode-JWT` only when the request itself has one.
|
||||
An account holding a TOTP secret or a registered WebAuthn credential also receives a reissued sudo proof. A bot credential and an account whose only proof was a password receive `X-Fluxer-Sudo-Mode-JWT` only when the request itself has one.
|
||||
|
||||
### Rate limit
|
||||
|
||||
|
||||
@@ -470,17 +470,17 @@ These fields prove [sudo mode](/http-api/users/mfa/#sudo-mode) when an operation
|
||||
| --- | --- | --- |
|
||||
| password?<sup>1</sup> | string | Current account password |
|
||||
| mfa_method?<sup>2</sup> | string | MFA method, either `totp` or `webauthn` |
|
||||
| mfa_code? | string | Authenticator code when the method is `totp` (1-32 characters) |
|
||||
| mfa_code? | string | Authenticator code or unconsumed backup code when the method is `totp` (1-32 characters) |
|
||||
| webauthn_response? | [WebAuthn assertion](/http-api/authentication/#webauthn-assertion) object | Assertion when the method is `webauthn` |
|
||||
| webauthn_challenge? | string | Challenge bound to the WebAuthn assertion |
|
||||
|
||||
<sup>1</sup> The password is accepted only when the account has no second factor, and it returns the field code `INVALID_PASSWORD` when it does not match
|
||||
<sup>1</sup> The password is accepted only while the account holds neither a TOTP secret nor a registered WebAuthn credential, and it returns the field code `INVALID_PASSWORD` when it does not match
|
||||
|
||||
<sup>2</sup> The MFA proof is accepted only when the account has a second factor, and it returns the field code `INVALID_MFA_CODE` on any failure
|
||||
<sup>2</sup> The MFA proof is accepted only while the account holds a TOTP secret or a registered WebAuthn credential, and it returns the field code `INVALID_MFA_CODE` on any failure
|
||||
|
||||
A bot credential satisfies sudo mode without any proof, and so does an account with no password or second factor. Every other account that supplies no usable proof receives 403 `SUDO_MODE_REQUIRED`, whose error object has `has_mfa` and a `methods` object reporting whether `totp` and `webauthn` are available.
|
||||
A bot credential satisfies sudo mode without any proof, and so does an account with no password, no TOTP secret, and no registered WebAuthn credential. Every other account that supplies no usable proof receives 403 `SUDO_MODE_REQUIRED`, whose error object has `has_mfa` and a `methods` object reporting whether `totp`, `webauthn`, and `backup_codes` are available.
|
||||
|
||||
Fluxer issues a newly generated proof in the `X-Fluxer-Sudo-Mode-JWT` header of the success response, and only for an account that has a second factor. A proof supplied on the request is echoed back in that same header.
|
||||
Fluxer issues a newly generated proof in the `X-Fluxer-Sudo-Mode-JWT` header of the success response, and only for an account holding a TOTP secret or a registered WebAuthn credential. A proof supplied on the request is echoed back in that same header.
|
||||
|
||||
## Create guild
|
||||
|
||||
|
||||
@@ -1592,8 +1592,8 @@ Deletes every message the caller authored in one channel. Returns 202 with an em
|
||||
|
||||
- User session and bot credentials are accepted, and a bot credential satisfies the sudo requirement without any proof.
|
||||
- The caller must be able to resolve the channel and be in [sudo mode](/http-api/users/mfa/#sudo-mode), established by the `X-Fluxer-Sudo-Mode-JWT` header or by supplying a proof in the body.
|
||||
- A caller who satisfies none of those receives 403 `SUDO_MODE_REQUIRED` with `has_mfa` and the available `methods`.
|
||||
- An account that holds no verifiable credential at all, meaning it is not a bot, has no enrolled authenticator, and has no stored password, satisfies the sudo requirement with an empty body.
|
||||
- A caller who satisfies none of those receives 403 `SUDO_MODE_REQUIRED` with `has_mfa` and the available `methods`, which report `totp`, `webauthn`, and `backup_codes`.
|
||||
- An account that holds no verifiable credential at all, meaning it is not a bot, has no TOTP secret, no registered WebAuthn credential, and no stored password, satisfies the sudo requirement with an empty body.
|
||||
|
||||
The deletion runs inside the request despite the 202 status, so every matching message is gone by the time the response is returned.
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ The private representation of the current account, returned by [Get current user
|
||||
|
||||
<sup>8</sup> The banner hash is not returned at all while the account lacks the animated banner entitlement, which every profile banner requires
|
||||
|
||||
<sup>9</sup> `mfa_enabled` is true exactly when at least one authenticator is configured. An account with no authenticator omits `authenticator_types`
|
||||
<sup>9</sup> `mfa_enabled` is true exactly when at least one authenticator is configured. An account with no authenticator reports `authenticator_types` as an empty array, and only a bearer read drops the field
|
||||
|
||||
<sup>10</sup> The value is forced to `0` and `premium_since` is forced to `null` while premium entitlements are not active
|
||||
|
||||
@@ -222,6 +222,8 @@ A scheduled deletion of every message the account has sent. The field is this ob
|
||||
|
||||
The value `1` is unassigned. Any stored authenticator value outside this registry is excluded from `authenticator_types`.
|
||||
|
||||
`WEBAUTHN` is present only while the account chose passkeys as a second factor through [Set WebAuthn two-factor authentication](/http-api/users/mfa/#set-webauthn-two-factor-authentication). Registering a credential does not add it, so an account can hold passkeys without the type.
|
||||
|
||||
## Premium types
|
||||
|
||||
| Value | Name | Description |
|
||||
|
||||
@@ -322,7 +322,7 @@ The caller proves sudo mode either with the sudo fields below or with an existin
|
||||
|
||||
<sup>4</sup> Supplying both bounds requires `start_date` strictly earlier than `end_date`, and an equal pair fails validation on `end_date`
|
||||
|
||||
<sup>5</sup> These fields are the [sudo verification object](/http-api/users/mfa/#sudo-verification-object). Which combination is accepted depends on the account's configured authenticators
|
||||
<sup>5</sup> These fields are the [sudo verification object](/http-api/users/mfa/#sudo-verification-object). Which combination is accepted depends on whether the account holds a TOTP secret or a registered WebAuthn credential
|
||||
|
||||
The `selected` scope requires at least one of the context toggles to be true, and a request that disables them all fails validation on `include_dms`. The `inaccessible_only` scope ignores the toggles and the guild filter.
|
||||
|
||||
@@ -341,7 +341,7 @@ Neither scope includes the caller's personal notes channel, so this operation ca
|
||||
|
||||
### Side effects
|
||||
|
||||
An account holding an MFA authenticator that proved sudo mode with MFA receives a fresh proof in the response header. A request that already had a valid proof gets that same token echoed back without an extended lifetime.
|
||||
An account holding a TOTP secret or a registered WebAuthn credential that proved sudo mode with MFA receives a fresh proof in the response header. A request that already had a valid proof gets that same token echoed back without an extended lifetime.
|
||||
|
||||
As deletion progresses, each affected channel emits [Message Delete Bulk](/gateway/events/#message-delete-bulk) in batches of at most 100 message IDs. The deleted messages' attachments are permanently removed.
|
||||
|
||||
|
||||
@@ -16,11 +16,12 @@ Security-sensitive fields and destructive lifecycle operations on this page requ
|
||||
|
||||
| Account state | Accepted proof |
|
||||
| --- | --- |
|
||||
| No authenticator configured | The current `password` |
|
||||
| An authenticator configured | `mfa_method` of `totp` with `mfa_code`, or `mfa_method` of `webauthn` with `webauthn_response` and `webauthn_challenge` |
|
||||
| No password credential and no authenticator | None, the requirement is already satisfied |
|
||||
| No TOTP secret and no registered WebAuthn credential | The current `password` |
|
||||
| A TOTP secret or a registered WebAuthn credential | `mfa_method` of `totp` with `mfa_code`, or `mfa_method` of `webauthn` with `webauthn_response` and `webauthn_challenge` |
|
||||
| A registered WebAuthn credential and no TOTP secret | `mfa_method` of `webauthn` as above, or `mfa_method` of `totp` with `mfa_code` holding an unconsumed backup code |
|
||||
| No password credential and neither of those | None, the requirement is already satisfied |
|
||||
|
||||
Once an account configures an authenticator, its `password` stops working here.
|
||||
Once an account holds a TOTP secret or a registered WebAuthn credential, its `password` stops working here. A passkey counts whether or not the account enabled passkeys as a second factor. The `methods` object on a 403 `SUDO_MODE_REQUIRED` reports `backup_codes` so a client knows whether the code input is worth offering.
|
||||
|
||||
## Get current user
|
||||
|
||||
|
||||
@@ -418,11 +418,11 @@ The body extends the [sudo verification object](/http-api/users/mfa/#sudo-verifi
|
||||
| webauthn_response?<sup>4</sup> | [WebAuthn assertion](/http-api/users/mfa/#webauthn-assertion-object) object | The assertion produced for the supplied challenge |
|
||||
| webauthn_challenge?<sup>4</sup> | string | The challenge returned by [create sudo WebAuthn authentication options](/http-api/users/mfa/#create-sudo-webauthn-authentication-options) (1-256 characters) |
|
||||
|
||||
<sup>1</sup> Considered only while the account holds no MFA authenticator, and ignored once TOTP or a WebAuthn credential exists
|
||||
<sup>1</sup> Considered only while the account holds no TOTP secret and no registered WebAuthn credential, and ignored once either exists
|
||||
|
||||
<sup>2</sup> Required when the account holds any MFA authenticator, unless the request already presents an accepted sudo proof
|
||||
<sup>2</sup> Required when the account holds a TOTP secret or a registered WebAuthn credential, unless the request already presents an accepted sudo proof
|
||||
|
||||
<sup>3</sup> Required when `mfa_method` is `totp`. The value is a current authenticator code or an unconsumed backup code
|
||||
<sup>3</sup> Required when `mfa_method` is `totp`. The value is a current authenticator code or an unconsumed backup code, and Fluxer reads it as a backup code alone while the account holds no TOTP secret
|
||||
|
||||
<sup>4</sup> Both fields are required together when `mfa_method` is `webauthn`
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
Multi-factor authentication asks for a second proof of identity after the account password. Fluxer accepts a TOTP authenticator app and WebAuthn credentials such as a passkey. A one-use backup code works instead of a TOTP code, and [sudo mode](#sudo-mode) reuses these factors to protect every security-sensitive operation in the API.
|
||||
|
||||
Registering a WebAuthn credential does not make passkeys a second factor. A registered credential answers a [sudo mode](#sudo-mode) challenge and a passwordless login on its own, and only [Set WebAuthn two-factor authentication](#set-webauthn-two-factor-authentication) adds the WebAuthn [authenticator type](/http-api/users/#authenticator-types) that makes a passkey a required second factor at password login.
|
||||
|
||||
Every route here requires a non-bot user session. Fluxer rejects an account in suspicious activity state with 403 `ACCOUNT_SUSPICIOUS_ACTIVITY`, including on the sudo routes.
|
||||
|
||||
:::caution[A challenge is one-use and context-bound]
|
||||
@@ -28,13 +30,15 @@ Fluxer rejects an accepted code for 30 seconds if it is sent again. The skew kee
|
||||
|
||||
Regenerating backup codes deletes the stored set and then issues exactly 10 replacements, so any code the user had already saved stops working.
|
||||
|
||||
Enabling TOTP issues 10 codes without deleting anything first. An account that already generated a set through [List MFA backup codes](#list-mfa-backup-codes) while holding no TOTP secret keeps those codes alongside the 10 the enable response returns. Every one stays redeemable. Disabling TOTP deletes every backup code without issuing replacements.
|
||||
Enabling TOTP issues 10 codes without deleting anything first. An account that already generated a set through [List MFA backup codes](#list-mfa-backup-codes) while holding no TOTP secret keeps those codes alongside the 10 the enable response returns. Every one stays redeemable. Disabling TOTP deletes every backup code only when the account is left with no second factor, so an account that keeps passkeys as a second factor keeps its whole set.
|
||||
|
||||
[List MFA backup codes](#list-mfa-backup-codes) reads the current set back at any time and reports which codes are already consumed. Consuming a code is irreversible. An account that cannot prove [sudo mode](#sudo-mode) reads the same set through the [backup codes challenge](#backup-codes-challenge).
|
||||
|
||||
A backup code has only these entry points: the `mfa_code` field of the [sudo verification object](#sudo-verification-object) with `mfa_method` set to `totp`, the `code` field of [Disable TOTP MFA](#disable-totp-mfa), and the `code` field of [complete login with TOTP](/http-api/authentication/#complete-login-with-totp). Every other code field rejects it. All accept a current authenticator code or an unconsumed backup code. [Enable TOTP MFA](#enable-totp-mfa) validates only against the secret being enrolled.
|
||||
|
||||
Every entry point requires the account to hold a TOTP secret. An account whose only authenticator is WebAuthn holds no TOTP secret, so none of the entry points accepts a backup code, though the account can still regenerate the set.
|
||||
Only the `code` field of [Disable TOTP MFA](#disable-totp-mfa) requires the account to hold a TOTP secret, so an account whose only second factor is passkeys never reaches that one. The other two accept an unconsumed backup code from such an account: the [sudo verification object](#sudo-verification-object) reads `mfa_code` as a backup code whenever the account holds no TOTP secret, and [complete login with TOTP](/http-api/authentication/#complete-login-with-totp) reads `code` the same way. Together they are the recovery path when no passkey is at hand. Any account reads and replaces its set through [List MFA backup codes](#list-mfa-backup-codes) with `regenerate` set to true, whether or not it holds a TOTP secret, because that route needs sudo mode alone.
|
||||
|
||||
[Set WebAuthn two-factor authentication](#set-webauthn-two-factor-authentication) mints 10 codes when it turns the toggle on for an account holding none, so an account whose only second factor is passkeys always has a set to fall back on.
|
||||
|
||||
:::caution[An uppercase backup code with spaces still matches]
|
||||
Fluxer lowercases the submitted value and each stored code, then removes every character outside `a-z` and `0-9`. `abcdefgh` and `AbCd EfGh` both match the issued code `abcd-efgh`.
|
||||
@@ -77,21 +81,22 @@ Fluxer refuses a resend for 30 seconds after the previous send on the same ticke
|
||||
|
||||
Sudo mode is a short-lived proof that the human in front of the session is still the account holder. An operation that requires it accepts a valid `X-Fluxer-Sudo-Mode-JWT` request header or the [sudo verification object](#sudo-verification-object) fields inside the JSON body.
|
||||
|
||||
A sudo token lasts five minutes and works only for the account that obtained it, across that account's sessions. Treat it as opaque. A new token is issued only after an MFA proof from an account with an enrolled authenticator.
|
||||
A sudo token lasts five minutes and works only for the account that obtained it, across that account's sessions. Treat it as opaque. A new token is issued only after an MFA proof from an account holding a TOTP secret or a registered WebAuthn credential.
|
||||
|
||||
:::caution[The echoed header repeats the request value]
|
||||
When Fluxer issues no fresh token it echoes the request's `X-Fluxer-Sudo-Mode-JWT` value back unchanged and never re-signs it, so the five-minute window never slides. Fluxer echoes the value even when it fails verification.
|
||||
:::
|
||||
|
||||
The accepted proof depends on the authenticators the account has configured.
|
||||
The accepted proof depends on what the account can present, which is a stored TOTP secret or a registered WebAuthn credential. The [authenticator types](/http-api/users/#authenticator-types) the account advertises do not enter into it, so a passkey proves sudo mode whether or not passkeys are enabled as a second factor.
|
||||
|
||||
| Account state | Accepted proof |
|
||||
| --- | --- |
|
||||
| No authenticator | `password` |
|
||||
| Any authenticator | `mfa_method` with its matching fields, because `password` is no longer accepted |
|
||||
| Neither an authenticator nor a password credential | Nothing, and eligible sudo operations pass |
|
||||
| Neither a TOTP secret nor a registered WebAuthn credential | `password` |
|
||||
| A TOTP secret or a registered WebAuthn credential | `mfa_method` with its matching fields, because `password` is no longer accepted |
|
||||
| A registered WebAuthn credential and no TOTP secret | The same, and `mfa_method` of `totp` then reads `mfa_code` as an unconsumed backup code |
|
||||
| Neither of those and no password credential | Nothing, and eligible sudo operations pass |
|
||||
|
||||
The `totp` method reads `mfa_code` as a current authenticator code or an unconsumed backup code and requires the account to hold a TOTP secret. The `webauthn` method reads `webauthn_response` and `webauthn_challenge` together and requires a registered credential.
|
||||
The `totp` method reads `mfa_code` as a current authenticator code or an unconsumed backup code. An account holding no TOTP secret is left with the backup code alone, which is how an account whose only second factor is passkeys proves sudo mode with no passkey at hand. The `webauthn` method reads `webauthn_response` and `webauthn_challenge` together and requires a registered credential.
|
||||
|
||||
A request that has no accepted proof fails with 403 `SUDO_MODE_REQUIRED`. Its body has the [sudo mode methods object](#sudo-mode-methods-object) members, so a client can tell which proof to ask the user for. A proof that is present but wrong fails instead with 400 `INVALID_FORM_BODY` and a [validation error](/http-api/#validation-error-object) entry. A mismatched password produces path `password` with code `INVALID_PASSWORD`. A rejected TOTP code, backup code, or WebAuthn assertion produces path `mfa_code` with code `INVALID_MFA_CODE`, so a client cannot tell which of the three was rejected.
|
||||
|
||||
@@ -107,7 +112,7 @@ In a `SUDO_MODE_REQUIRED` error response, `has_mfa` and `methods` are at the top
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| has_mfa | boolean | Whether the account has the TOTP or the WebAuthn authenticator type |
|
||||
| has_mfa | boolean | Whether the account can satisfy a sudo mode challenge |
|
||||
| methods | [sudo mode method availability](#sudo-mode-method-availability-object) object | Authenticators the account can present |
|
||||
|
||||
## Sudo mode method availability object
|
||||
@@ -117,10 +122,13 @@ In a `SUDO_MODE_REQUIRED` error response, `has_mfa` and `methods` are at the top
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| totp<sup>1</sup> | boolean | Whether the account has a TOTP secret and the TOTP authenticator type |
|
||||
| webauthn | boolean | Whether the account has the WebAuthn authenticator type |
|
||||
| webauthn | boolean | Whether the account has at least one registered WebAuthn credential |
|
||||
| backup_codes<sup>2</sup> | boolean | Whether the account holds at least one unconsumed backup code |
|
||||
|
||||
<sup>1</sup> Both conditions are required, so an account holding a stored secret without the [authenticator type](/http-api/users/#authenticator-types) reports `false`
|
||||
|
||||
<sup>2</sup> There is no `backup_codes` method. The value tells a client to offer the code input, which a backup code reaches under `mfa_method` of `totp` like any other code, and to label that input as a backup code when `totp` is `false`. It reports the stored codes alone, so a client reads it only while `has_mfa` is true, because an account that cannot answer a sudo challenge proves sudo mode with its `password` however many codes it holds
|
||||
|
||||
## Sudo verification object
|
||||
|
||||
Operations that require sudo mode merge these fields into their own JSON body. Every field is optional at the boundary, because the accepted combination depends on the account state described in [sudo mode](#sudo-mode).
|
||||
@@ -135,11 +143,11 @@ Operations that require sudo mode merge these fields into their own JSON body. E
|
||||
| webauthn_response?<sup>4</sup> | [WebAuthn assertion](#webauthn-assertion-object) object | Assertion produced for the supplied challenge |
|
||||
| webauthn_challenge?<sup>4</sup> | string | Challenge returned by [create sudo WebAuthn authentication options](#create-sudo-webauthn-authentication-options) (1-256 characters) |
|
||||
|
||||
<sup>1</sup> Considered only while the account has no MFA authenticator, and ignored once TOTP or a WebAuthn credential exists
|
||||
<sup>1</sup> Considered only while the account holds no TOTP secret and no registered WebAuthn credential, and ignored once either exists
|
||||
|
||||
<sup>2</sup> Required when the account has any MFA authenticator, unless a valid sudo token is already present
|
||||
<sup>2</sup> Required when the account holds a TOTP secret or a registered WebAuthn credential, unless a valid sudo token is already present
|
||||
|
||||
<sup>3</sup> Required when `mfa_method` is `totp`, with a current authenticator code or an unconsumed backup code
|
||||
<sup>3</sup> Required when `mfa_method` is `totp`, with a current authenticator code or an unconsumed backup code. Fluxer reads it as a backup code alone while the account holds no TOTP secret
|
||||
|
||||
<sup>4</sup> Both fields are required together when `mfa_method` is `webauthn`
|
||||
|
||||
@@ -439,8 +447,22 @@ Fluxer emits no other PublicKeyCredential request options member, so `hints` and
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| totp | boolean | Whether the account has a TOTP secret and the TOTP authenticator type, as in the [sudo mode method availability](#sudo-mode-method-availability-object) object |
|
||||
| webauthn | boolean | Whether the account has the WebAuthn authenticator type |
|
||||
| has_mfa | boolean | Whether the account has the TOTP or the WebAuthn authenticator type, as in the [sudo mode methods](#sudo-mode-methods-object) object |
|
||||
| webauthn | boolean | Whether the account has at least one registered WebAuthn credential |
|
||||
| backup_codes | boolean | Whether the account holds at least one unconsumed backup code, as in the [sudo mode method availability](#sudo-mode-method-availability-object) object |
|
||||
| has_mfa | boolean | Whether the account can satisfy a sudo mode challenge, as in the [sudo mode methods](#sudo-mode-methods-object) object |
|
||||
|
||||
## WebAuthn two-factor object
|
||||
|
||||
The account as it stands after [Set WebAuthn two-factor authentication](#set-webauthn-two-factor-authentication), together with any backup codes that operation minted.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| user | [user](/http-api/users/#user-object) object | The updated account, with `authenticator_types` reflecting the change |
|
||||
| backup_codes<sup>1</sup> | ?array[[MFA backup code](#mfa-backup-code-object) object] | The 10 codes minted by this call, or null when none were minted |
|
||||
|
||||
<sup>1</sup> Present with codes only when the call turned the toggle on for an account holding no backup code. A call that turned the toggle off, or that found a set already in place, reports null
|
||||
|
||||
## Enable TOTP MFA
|
||||
|
||||
@@ -504,8 +526,8 @@ The body extends the [sudo verification object](#sudo-verification-object) with
|
||||
|
||||
<sup>1</sup> A wrong value returns `INVALID_CODE` on the path `code` with a sudo token, and `INVALID_MFA_CODE` on the path `mfa_code` when `code` proves sudo mode
|
||||
|
||||
:::caution[Backup codes do not survive TOTP removal]
|
||||
Disabling TOTP invalidates every backup code on the account, including the code that proved this request. An account that keeps a WebAuthn credential is left with none, and needs [List MFA backup codes](#list-mfa-backup-codes) with `regenerate` set to true to issue a new set.
|
||||
:::caution[Backup codes survive only while a second factor remains]
|
||||
Disabling TOTP invalidates every backup code on the account, including the code that proved this request, unless the account keeps passkeys enabled as a second factor. An account that keeps that toggle on keeps its whole set. An account left with no second factor is left with no codes, and needs [List MFA backup codes](#list-mfa-backup-codes) with `regenerate` set to true to issue a new set. A registered WebAuthn credential alone does not preserve the set.
|
||||
:::
|
||||
|
||||
### Response
|
||||
@@ -518,7 +540,7 @@ Disabling TOTP invalidates every backup code on the account, including the code
|
||||
|
||||
### Side effects
|
||||
|
||||
TOTP is disabled and every backup code stops working. Fluxer removes the TOTP authenticator type, along with the unassigned legacy authenticator value `1` when the account still held it. Fluxer also copies the account's authenticator types to every bot account the user owns. [User Update](/gateway/events/#user-update) goes to the user's sessions and each owned bot whose authenticator types changed.
|
||||
TOTP is disabled. Every backup code stops working unless the account keeps passkeys enabled as a second factor, in which case the whole set survives. Fluxer removes the TOTP authenticator type, along with the unassigned legacy authenticator value `1` when the account still held it. Fluxer also copies the account's authenticator types to every bot account the user owns. [User Update](/gateway/events/#user-update) goes to the user's sessions and each owned bot whose authenticator types changed.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -530,7 +552,7 @@ TOTP is disabled and every backup code stops working. Fluxer removes the TOTP au
|
||||
|
||||
Returns an [MFA backup codes](#mfa-backup-codes-object) object, replacing the set first when `regenerate` is true. [Sudo mode](#sudo-mode) is required.
|
||||
|
||||
Neither a verified email nor an authenticator is required, so an account with no MFA at all can prove sudo mode with its password and use this operation.
|
||||
Neither a verified email nor an authenticator is required, so an account with no MFA at all can prove sudo mode with its password and use this operation. An account whose only second factor is passkeys proves sudo mode with a passkey, or with one of these codes as `mfa_code` under `mfa_method` of `totp`, and reads or replaces its set the same way. [Complete login with TOTP](/http-api/authentication/#complete-login-with-totp) redeems one of those codes later.
|
||||
|
||||
A backup code sent as `mfa_code` to satisfy sudo mode is consumed. With `regenerate` false it comes back with `consumed` set to true. With `regenerate` true it is deleted with the rest of the previous set and does not appear.
|
||||
|
||||
@@ -751,7 +773,9 @@ Fluxer issues a registration challenge for the current user. It expires after fi
|
||||
|
||||
<RouteHeader method="POST" path="/v1/users/@me/mfa/webauthn/credentials" />
|
||||
|
||||
Consumes a registration challenge, adds one WebAuthn credential to the current account, and returns 204 with an empty body. Emits a [WebAuthn Credentials Update](/gateway/events/#webauthn-credentials-update) Gateway event, and a [User Update](/gateway/events/#user-update) event when this is the account's first WebAuthn credential.
|
||||
Consumes a registration challenge, adds one WebAuthn credential to the current account, and returns 204 with an empty body. Emits a [WebAuthn Credentials Update](/gateway/events/#webauthn-credentials-update) Gateway event.
|
||||
|
||||
Registration changes no [authenticator type](/http-api/users/#authenticator-types). The credential answers a sudo mode challenge and a passwordless login straight away, and passkeys become a second factor at password login only through [Set WebAuthn two-factor authentication](#set-webauthn-two-factor-authentication).
|
||||
|
||||
The account needs a verified email and fewer than 10 registered credentials. This route accepts no sudo verification fields of its own. Fluxer verifies the attestation against the instance relying party identifier and its allowed origins, and does not require user verification.
|
||||
|
||||
@@ -777,9 +801,9 @@ Fluxer consumes the registration challenge before it re-checks the credential li
|
||||
|
||||
### Side effects
|
||||
|
||||
The credential is added to the account. When it is the account's first WebAuthn credential, Fluxer also copies the account's authenticator types, which now include WebAuthn, to every bot account the user owns.
|
||||
The credential is added to the account. No authenticator type changes, and no bot account the user owns is touched.
|
||||
|
||||
[WebAuthn Credentials Update](/gateway/events/#webauthn-credentials-update) reaches the user's sessions with the complete current credential summaries. When the authenticator types changed, [User Update](/gateway/events/#user-update) also reaches the user and each affected owned bot.
|
||||
[WebAuthn Credentials Update](/gateway/events/#webauthn-credentials-update) reaches the user's sessions with the complete current credential summaries.
|
||||
|
||||
### Rate limit
|
||||
|
||||
@@ -827,7 +851,7 @@ The credential name is replaced and no authenticator type changes. [WebAuthn Cre
|
||||
|
||||
<RouteHeader method="DELETE" path="/v1/users/@me/mfa/webauthn/credentials/{credential_id}" mfa />
|
||||
|
||||
Deletes one WebAuthn credential owned by the current account and returns 204 with an empty body. [Sudo mode](#sudo-mode) is required. Emits a [WebAuthn Credentials Update](/gateway/events/#webauthn-credentials-update) Gateway event, and a [User Update](/gateway/events/#user-update) event when this was the account's final WebAuthn credential.
|
||||
Deletes one WebAuthn credential owned by the current account and returns 204 with an empty body. [Sudo mode](#sudo-mode) is required. Emits a [WebAuthn Credentials Update](/gateway/events/#webauthn-credentials-update) Gateway event, and a [User Update](/gateway/events/#user-update) event when this was the account's final WebAuthn credential and passkeys were enabled as a second factor.
|
||||
|
||||
A verified email is not required.
|
||||
|
||||
@@ -842,7 +866,7 @@ A verified email is not required.
|
||||
The body is a [sudo verification object](#sudo-verification-object). An existing sudo proof is sent in the `X-Fluxer-Sudo-Mode-JWT` request header.
|
||||
|
||||
:::caution[Removing the final credential removes an authenticator]
|
||||
Deleting the account's last WebAuthn credential removes the WebAuthn authenticator type. Without TOTP the account then has no MFA authenticator, and sudo mode falls back to the account password.
|
||||
Deleting the account's last WebAuthn credential removes the WebAuthn authenticator type from an account that enabled passkeys as a second factor with [Set WebAuthn two-factor authentication](#set-webauthn-two-factor-authentication). Without TOTP the account then has no second factor. Either way it can no longer answer a WebAuthn sudo challenge, and without a TOTP secret sudo mode falls back to the account password.
|
||||
:::
|
||||
|
||||
### Response
|
||||
@@ -855,7 +879,7 @@ Deleting the account's last WebAuthn credential removes the WebAuthn authenticat
|
||||
|
||||
### Side effects
|
||||
|
||||
The credential is deleted. When it was the final one, the WebAuthn authenticator type is also removed from every bot account owned by the user.
|
||||
The credential is deleted. When it was the final one and the account had passkeys enabled as a second factor, the WebAuthn authenticator type is removed from the account and from every bot account owned by the user.
|
||||
|
||||
[WebAuthn Credentials Update](/gateway/events/#webauthn-credentials-update) reaches the user's sessions with the remaining credential summaries. When the authenticator types changed, [User Update](/gateway/events/#user-update) also reaches the user and each affected owned bot.
|
||||
|
||||
@@ -863,6 +887,57 @@ The credential is deleted. When it was the final one, the WebAuthn authenticator
|
||||
|
||||
10 requests per minute for each authenticated user, on the `mfa:webauthn:delete` bucket.
|
||||
|
||||
## Set WebAuthn two-factor authentication
|
||||
|
||||
<RouteHeader method="PUT" path="/v1/users/@me/mfa/webauthn/two-factor" mfa />
|
||||
|
||||
Turns passkeys on or off as a second factor for the current account and returns a [WebAuthn two-factor](#webauthn-two-factor-object) object. [Sudo mode](#sudo-mode) is required. Emits a [User Update](/gateway/events/#user-update) Gateway event.
|
||||
|
||||
Enabling adds the WebAuthn [authenticator type](/http-api/users/#authenticator-types), so password login then asks for a passkey. Disabling removes it and leaves every registered credential in place, still usable for sudo mode and for passwordless login.
|
||||
|
||||
### Limitations
|
||||
|
||||
- Enabling while the account has no registered WebAuthn credential is refused with 400 `NO_PASSKEYS_REGISTERED`.
|
||||
|
||||
### JSON body
|
||||
|
||||
The body extends the [sudo verification object](#sudo-verification-object) with `enabled`, and the sudo fields it merges in are repeated below. An existing sudo proof is sent in the `X-Fluxer-Sudo-Mode-JWT` request header.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| enabled | boolean | Whether passkeys count as a second factor at password login |
|
||||
| password?<sup>1</sup> | string | Account password (8-256 characters) |
|
||||
| mfa_method?<sup>2</sup> | string | MFA method, either `totp` or `webauthn` |
|
||||
| mfa_code?<sup>3</sup> | string | Authenticator code or unconsumed backup code (1-32 characters) |
|
||||
| webauthn_response?<sup>4</sup> | [WebAuthn assertion](#webauthn-assertion-object) object | Assertion produced for the supplied challenge |
|
||||
| webauthn_challenge?<sup>4</sup> | string | Challenge returned by [create sudo WebAuthn authentication options](#create-sudo-webauthn-authentication-options) (1-256 characters) |
|
||||
|
||||
<sup>1</sup> Considered only while the account holds no TOTP secret and no registered WebAuthn credential, and ignored once either exists
|
||||
|
||||
<sup>2</sup> Required when the account holds a TOTP secret or a registered WebAuthn credential, unless a valid sudo token is already present
|
||||
|
||||
<sup>3</sup> Required when `mfa_method` is `totp`, with a current authenticator code or an unconsumed backup code. Fluxer reads it as a backup code alone while the account holds no TOTP secret
|
||||
|
||||
<sup>4</sup> Both fields are required together when `mfa_method` is `webauthn`
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
| --- | --- | --- |
|
||||
| 200 | [WebAuthn two-factor](#webauthn-two-factor-object) object | The authenticator type was set to the requested state |
|
||||
| 400 | [error response](/http-api/#error-response) | Enabling was requested with no registered credential and the request returns `NO_PASSKEYS_REGISTERED` |
|
||||
| 403 | [error response](/http-api/#error-response) | Sudo mode was not proven and the request returns `SUDO_MODE_REQUIRED` |
|
||||
|
||||
### Side effects
|
||||
|
||||
The WebAuthn authenticator type is added or removed, and Fluxer copies the account's authenticator types to every bot account the user owns. [User Update](/gateway/events/#user-update) reaches the user's sessions and each owned bot whose authenticator types changed.
|
||||
|
||||
Enabling on an account that holds no backup code issues 10, which the response returns. They are the recovery path for an account whose only second factor is passkeys, and [complete login with TOTP](/http-api/authentication/#complete-login-with-totp) redeems one. An account that already holds a set keeps it and the response reports null. A request that asks for the state the account already has changes nothing and issues no code.
|
||||
|
||||
### Rate limit
|
||||
|
||||
10 requests per minute for each authenticated user, on the `mfa:webauthn:two_factor` bucket.
|
||||
|
||||
## Get sudo MFA methods
|
||||
|
||||
<RouteHeader method="GET" path="/v1/users/@me/sudo/mfa-methods" />
|
||||
|
||||
Reference in New Issue
Block a user