diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 5095e7a7..179a27e4 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -4100,7 +4100,7 @@ paths: - hybrid responses: '201': - description: Authentication credential created successfully. The body is the created `AuthMethod` for all three credential types. For `EMAIL_OTP`, the email is the customer email tied to the internal account, and the response also carries `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. + description: Authentication credential created successfully. The body is the created `AuthMethod` for all three credential types. For `EMAIL_OTP`, the email is the customer email tied to the internal account. When the response is for adding EMAIL_OTP back to an existing wallet through the signed-retry flow, it also carries `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. content: application/json: schema: @@ -4336,7 +4336,7 @@ paths: description: | Complete the verification step for a previously created authentication credential and issue a session. - For `EMAIL_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from the credential's registration or re-issued via `POST /auth/credentials/{id}/challenge`. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. + For `EMAIL_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. In sandbox mode, the EMAIL_OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and sign a real `verificationToken` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email delivery. For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request. @@ -17611,14 +17611,14 @@ components: description: |- Strict wrapper around `AuthMethod`. Used directly as the registration response on `POST /auth/credentials` (all three credential types) and inside `AuthCredentialResponseOneOf` for the `EMAIL_OTP` branch of `POST /auth/credentials/{id}/challenge`. The only difference from `AuthMethod` is `unevaluatedProperties: false`, which disambiguates the oneOf against `PasskeyAuthChallenge` — without the strictness, an `AuthMethod` with extra fields would ambiguously match both branches. - For `EMAIL_OTP` credentials, the response also carries `otpEncryptionTargetBundle` so the client can HPKE-encrypt the OTP code in the subsequent `POST /auth/credentials/{id}/verify` call without the plaintext code ever transiting the server. + For `EMAIL_OTP` credentials, responses that initiate or reissue an OTP challenge carry `otpEncryptionTargetBundle` so the client can HPKE-encrypt the OTP code in the subsequent `POST /auth/credentials/{id}/verify` call without the plaintext code ever transiting the server. First-time EMAIL_OTP wallet bootstrap registration can omit it; call `POST /auth/credentials/{id}/challenge` if it is absent. allOf: - $ref: '#/components/schemas/AuthMethod' - type: object properties: otpEncryptionTargetBundle: type: string - description: HPKE encryption target bundle for the freshly initiated OTP challenge. Returned only for `EMAIL_OTP` credentials. The client generates an ephemeral P-256 keypair (the Target Encryption Key, or TEK) and uses this bundle as the recipient when HPKE-encrypting `{otp_code, public_key}`; the encrypted payload is submitted as `encryptedOtpBundle` on `POST /auth/credentials/{id}/verify`. The bundle is one-time-use per OTP issuance — re-issue via `POST /auth/credentials/{id}/challenge` to obtain a fresh bundle. The matching TEK private key must remain on the client and is used to sign the `verificationToken` returned on the subsequent signed-retry. Treat the bundle as opaque and pass it to your HPKE library; the Global Accounts client-keys guide shows how. + description: HPKE encryption target bundle for a freshly initiated OTP challenge. Returned only on `EMAIL_OTP` responses that initiate or reissue an OTP challenge, such as `POST /auth/credentials/{id}/challenge` and the add-EMAIL_OTP signed-retry response. It is omitted from first-time EMAIL_OTP wallet bootstrap registration; call `POST /auth/credentials/{id}/challenge` for the new credential if it is absent. The client generates an ephemeral P-256 keypair (the Target Encryption Key, or TEK) and uses this bundle as the recipient when HPKE-encrypting `{otp_code, public_key}`; the encrypted payload is submitted as `encryptedOtpBundle` on `POST /auth/credentials/{id}/verify`. The bundle is one-time-use per OTP issuance — re-issue via `POST /auth/credentials/{id}/challenge` to obtain a fresh bundle. The matching TEK private key must remain on the client and is used to sign the `verificationToken` returned on the subsequent signed-retry. Treat the bundle as opaque and pass it to your HPKE library; the Global Accounts client-keys guide shows how. example: '{"version":"v1.0.0","data":"7b227461726765745075626c6963...","dataSignature":"30450221...","enclaveQuorumPublic":"04a1b2c3..."}' unevaluatedProperties: false AuthSignedRequestChallenge: @@ -17648,7 +17648,7 @@ components: required: - type - encryptedOtpBundle - description: Verify an email-OTP credential via the secure two-leg flow. The client HPKE-encrypts the OTP code (together with its public key) under the `otpEncryptionTargetBundle` returned from the credential's registration or `POST /auth/credentials/{id}/challenge`, submits the result here, and receives `202` with a `payloadToSign` carrying a `verificationToken` bound to the client's public key. The client signs that token with the matching private key and retries this request with `Grid-Wallet-Signature` + `Request-Id` headers to obtain the session. Plaintext OTP codes are never sent over the wire. + description: Verify an email-OTP credential via the secure two-leg flow. The client HPKE-encrypts the OTP code (together with its public key) under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued, submits the result here, and receives `202` with a `payloadToSign` carrying a `verificationToken` bound to the client's public key. The client signs that token with the matching private key and retries this request with `Grid-Wallet-Signature` + `Request-Id` headers to obtain the session. Plaintext OTP codes are never sent over the wire. properties: type: type: string diff --git a/mintlify/snippets/global-accounts/authentication.mdx b/mintlify/snippets/global-accounts/authentication.mdx index fe5835b6..28361555 100644 --- a/mintlify/snippets/global-accounts/authentication.mdx +++ b/mintlify/snippets/global-accounts/authentication.mdx @@ -715,7 +715,7 @@ Requires an active session on an *existing* credential on the same account. The **Response (201):** a plain `AuthMethod`. - Activate the new credential the same way you would activate the first credential of that type — `EMAIL_OTP` and `OAUTH` go straight to `POST /auth/credentials/{id}/verify` with a fresh `clientPublicKey`; `PASSKEY` first calls `POST /auth/credentials/{id}/challenge` with the `clientPublicKey` to get a Grid-issued WebAuthn challenge, then `POST /auth/credentials/{id}/verify` with the assertion and the `Request-Id` header. + Activate the new credential the same way you would activate the first credential of that type — `OAUTH` goes straight to `POST /auth/credentials/{id}/verify` with a fresh `clientPublicKey`; `EMAIL_OTP` uses the `otpEncryptionTargetBundle` from the signed-retry registration response when present, or first calls `POST /auth/credentials/{id}/challenge` if the bundle is absent; `PASSKEY` first calls `POST /auth/credentials/{id}/challenge` with the `clientPublicKey` to get a Grid-issued WebAuthn challenge, then `POST /auth/credentials/{id}/verify` with the assertion and the `Request-Id` header. diff --git a/mintlify/snippets/global-accounts/client-keys.mdx b/mintlify/snippets/global-accounts/client-keys.mdx index dc16279c..2564c2d4 100644 --- a/mintlify/snippets/global-accounts/client-keys.mdx +++ b/mintlify/snippets/global-accounts/client-keys.mdx @@ -118,7 +118,7 @@ func generateClientKeyPair() -> ClientKeyPair { `EMAIL_OTP` credentials never send the OTP code in plaintext. Instead, the client HPKE-encrypts the code (together with its `publicKeyHex`) to an enclave key, so the code is unreadable in transit and Grid is only a pass-through. -Grid returns an `otpEncryptionTargetBundle` from `POST /auth/credentials` (registration) or `POST /auth/credentials/{id}/challenge` (re-issue). It's a signed enclave bundle whose `data` field is hex-encoded JSON carrying the enclave's HPKE target key as `targetPublic`. Pull out `targetPublic`, HPKE-encrypt `{ otp_code, public_key }` to it, and submit the library's `{ encappedPublic, ciphertext }` output as `encryptedOtpBundle` on `POST /auth/credentials/{id}/verify`. +Grid returns an `otpEncryptionTargetBundle` whenever it initiates or reissues an OTP challenge, including `POST /auth/credentials/{id}/challenge` and add-EMAIL_OTP signed-retry responses. First-time EMAIL_OTP wallet bootstrap registration can omit it; if the registration response has no bundle, call `POST /auth/credentials/{id}/challenge` for that credential before verifying. The bundle is a signed enclave bundle whose `data` field is hex-encoded JSON carrying the enclave's HPKE target key as `targetPublic`. Pull out `targetPublic`, HPKE-encrypt `{ otp_code, public_key }` to it, and submit the library's `{ encappedPublic, ciphertext }` output as `encryptedOtpBundle` on `POST /auth/credentials/{id}/verify`. Use an HPKE library so you don't hand-roll the suite, `info`, or AAD — `@turnkey/crypto`'s `hpkeEncrypt` + `formatHpkeBuf` produce exactly the shape the enclave expects: @@ -127,7 +127,7 @@ Use an HPKE library so you don't hand-roll the suite, `info`, or AAD — `@turnk import { hpkeEncrypt, formatHpkeBuf } from "@turnkey/crypto"; import { hexToBytes } from "@noble/hashes/utils"; -// otpEncryptionTargetBundle: from POST /auth/credentials or /challenge +// otpEncryptionTargetBundle: from the registration response when present, or /challenge // clientPublicKeyHex: the uncompressed public key you generated in step 1 // otp: the code the user typed ("000000" in sandbox) function buildEncryptedOtpBundle( diff --git a/openapi.yaml b/openapi.yaml index 5095e7a7..179a27e4 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4100,7 +4100,7 @@ paths: - hybrid responses: '201': - description: Authentication credential created successfully. The body is the created `AuthMethod` for all three credential types. For `EMAIL_OTP`, the email is the customer email tied to the internal account, and the response also carries `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. + description: Authentication credential created successfully. The body is the created `AuthMethod` for all three credential types. For `EMAIL_OTP`, the email is the customer email tied to the internal account. When the response is for adding EMAIL_OTP back to an existing wallet through the signed-retry flow, it also carries `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. content: application/json: schema: @@ -4336,7 +4336,7 @@ paths: description: | Complete the verification step for a previously created authentication credential and issue a session. - For `EMAIL_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from the credential's registration or re-issued via `POST /auth/credentials/{id}/challenge`. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. + For `EMAIL_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. In sandbox mode, the EMAIL_OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and sign a real `verificationToken` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email delivery. For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request. @@ -17611,14 +17611,14 @@ components: description: |- Strict wrapper around `AuthMethod`. Used directly as the registration response on `POST /auth/credentials` (all three credential types) and inside `AuthCredentialResponseOneOf` for the `EMAIL_OTP` branch of `POST /auth/credentials/{id}/challenge`. The only difference from `AuthMethod` is `unevaluatedProperties: false`, which disambiguates the oneOf against `PasskeyAuthChallenge` — without the strictness, an `AuthMethod` with extra fields would ambiguously match both branches. - For `EMAIL_OTP` credentials, the response also carries `otpEncryptionTargetBundle` so the client can HPKE-encrypt the OTP code in the subsequent `POST /auth/credentials/{id}/verify` call without the plaintext code ever transiting the server. + For `EMAIL_OTP` credentials, responses that initiate or reissue an OTP challenge carry `otpEncryptionTargetBundle` so the client can HPKE-encrypt the OTP code in the subsequent `POST /auth/credentials/{id}/verify` call without the plaintext code ever transiting the server. First-time EMAIL_OTP wallet bootstrap registration can omit it; call `POST /auth/credentials/{id}/challenge` if it is absent. allOf: - $ref: '#/components/schemas/AuthMethod' - type: object properties: otpEncryptionTargetBundle: type: string - description: HPKE encryption target bundle for the freshly initiated OTP challenge. Returned only for `EMAIL_OTP` credentials. The client generates an ephemeral P-256 keypair (the Target Encryption Key, or TEK) and uses this bundle as the recipient when HPKE-encrypting `{otp_code, public_key}`; the encrypted payload is submitted as `encryptedOtpBundle` on `POST /auth/credentials/{id}/verify`. The bundle is one-time-use per OTP issuance — re-issue via `POST /auth/credentials/{id}/challenge` to obtain a fresh bundle. The matching TEK private key must remain on the client and is used to sign the `verificationToken` returned on the subsequent signed-retry. Treat the bundle as opaque and pass it to your HPKE library; the Global Accounts client-keys guide shows how. + description: HPKE encryption target bundle for a freshly initiated OTP challenge. Returned only on `EMAIL_OTP` responses that initiate or reissue an OTP challenge, such as `POST /auth/credentials/{id}/challenge` and the add-EMAIL_OTP signed-retry response. It is omitted from first-time EMAIL_OTP wallet bootstrap registration; call `POST /auth/credentials/{id}/challenge` for the new credential if it is absent. The client generates an ephemeral P-256 keypair (the Target Encryption Key, or TEK) and uses this bundle as the recipient when HPKE-encrypting `{otp_code, public_key}`; the encrypted payload is submitted as `encryptedOtpBundle` on `POST /auth/credentials/{id}/verify`. The bundle is one-time-use per OTP issuance — re-issue via `POST /auth/credentials/{id}/challenge` to obtain a fresh bundle. The matching TEK private key must remain on the client and is used to sign the `verificationToken` returned on the subsequent signed-retry. Treat the bundle as opaque and pass it to your HPKE library; the Global Accounts client-keys guide shows how. example: '{"version":"v1.0.0","data":"7b227461726765745075626c6963...","dataSignature":"30450221...","enclaveQuorumPublic":"04a1b2c3..."}' unevaluatedProperties: false AuthSignedRequestChallenge: @@ -17648,7 +17648,7 @@ components: required: - type - encryptedOtpBundle - description: Verify an email-OTP credential via the secure two-leg flow. The client HPKE-encrypts the OTP code (together with its public key) under the `otpEncryptionTargetBundle` returned from the credential's registration or `POST /auth/credentials/{id}/challenge`, submits the result here, and receives `202` with a `payloadToSign` carrying a `verificationToken` bound to the client's public key. The client signs that token with the matching private key and retries this request with `Grid-Wallet-Signature` + `Request-Id` headers to obtain the session. Plaintext OTP codes are never sent over the wire. + description: Verify an email-OTP credential via the secure two-leg flow. The client HPKE-encrypts the OTP code (together with its public key) under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued, submits the result here, and receives `202` with a `payloadToSign` carrying a `verificationToken` bound to the client's public key. The client signs that token with the matching private key and retries this request with `Grid-Wallet-Signature` + `Request-Id` headers to obtain the session. Plaintext OTP codes are never sent over the wire. properties: type: type: string diff --git a/openapi/components/schemas/auth/AuthMethodResponse.yaml b/openapi/components/schemas/auth/AuthMethodResponse.yaml index b48035d9..8d306e80 100644 --- a/openapi/components/schemas/auth/AuthMethodResponse.yaml +++ b/openapi/components/schemas/auth/AuthMethodResponse.yaml @@ -9,10 +9,12 @@ description: >- `AuthMethod` with extra fields would ambiguously match both branches. - For `EMAIL_OTP` credentials, the response also carries - `otpEncryptionTargetBundle` so the client can HPKE-encrypt the OTP code - in the subsequent `POST /auth/credentials/{id}/verify` call without the - plaintext code ever transiting the server. + For `EMAIL_OTP` credentials, responses that initiate or reissue an OTP + challenge carry `otpEncryptionTargetBundle` so the client can + HPKE-encrypt the OTP code in the subsequent + `POST /auth/credentials/{id}/verify` call without the plaintext code ever + transiting the server. First-time EMAIL_OTP wallet bootstrap registration + can omit it; call `POST /auth/credentials/{id}/challenge` if it is absent. allOf: - $ref: ./AuthMethod.yaml - type: object @@ -20,19 +22,23 @@ allOf: otpEncryptionTargetBundle: type: string description: >- - HPKE encryption target bundle for the freshly initiated OTP - challenge. Returned only for `EMAIL_OTP` credentials. The client - generates an ephemeral P-256 keypair (the Target Encryption - Key, or TEK) and uses this bundle as the recipient when - HPKE-encrypting `{otp_code, public_key}`; the encrypted payload - is submitted as `encryptedOtpBundle` on + HPKE encryption target bundle for a freshly initiated OTP + challenge. Returned only on `EMAIL_OTP` responses that initiate + or reissue an OTP challenge, such as + `POST /auth/credentials/{id}/challenge` and the add-EMAIL_OTP + signed-retry response. It is omitted from first-time EMAIL_OTP + wallet bootstrap registration; call + `POST /auth/credentials/{id}/challenge` for the new credential if + it is absent. The client generates an ephemeral P-256 keypair + (the Target Encryption Key, or TEK) and uses this bundle as the + recipient when HPKE-encrypting `{otp_code, public_key}`; the + encrypted payload is submitted as `encryptedOtpBundle` on `POST /auth/credentials/{id}/verify`. The bundle is one-time-use per OTP issuance — re-issue via - `POST /auth/credentials/{id}/challenge` to obtain a fresh - bundle. The matching TEK private key must remain on the client - and is used to sign the `verificationToken` returned on the - subsequent signed-retry. Treat the bundle as opaque and pass it - to your HPKE library; the Global Accounts client-keys guide - shows how. + `POST /auth/credentials/{id}/challenge` to obtain a fresh bundle. + The matching TEK private key must remain on the client and is used + to sign the `verificationToken` returned on the subsequent + signed-retry. Treat the bundle as opaque and pass it to your HPKE + library; the Global Accounts client-keys guide shows how. example: '{"version":"v1.0.0","data":"7b227461726765745075626c6963...","dataSignature":"30450221...","enclaveQuorumPublic":"04a1b2c3..."}' unevaluatedProperties: false diff --git a/openapi/components/schemas/auth/EmailOtpCredentialVerifyRequestFields.yaml b/openapi/components/schemas/auth/EmailOtpCredentialVerifyRequestFields.yaml index 0281e089..a844a910 100644 --- a/openapi/components/schemas/auth/EmailOtpCredentialVerifyRequestFields.yaml +++ b/openapi/components/schemas/auth/EmailOtpCredentialVerifyRequestFields.yaml @@ -5,13 +5,13 @@ required: description: >- Verify an email-OTP credential via the secure two-leg flow. The client HPKE-encrypts the OTP code (together with its public key) under the - `otpEncryptionTargetBundle` returned from the credential's registration - or `POST /auth/credentials/{id}/challenge`, submits the result here, and - receives `202` with a `payloadToSign` carrying a `verificationToken` - bound to the client's public key. The client signs that token with the - matching private key and retries this request with `Grid-Wallet-Signature` - + `Request-Id` headers to obtain the session. Plaintext OTP codes are - never sent over the wire. + `otpEncryptionTargetBundle` returned from registration when present, or + from `POST /auth/credentials/{id}/challenge` when registration omitted it + or the OTP must be reissued, submits the result here, and receives `202` + with a `payloadToSign` carrying a `verificationToken` bound to the client's + public key. The client signs that token with the matching private key and + retries this request with `Grid-Wallet-Signature` + `Request-Id` headers to + obtain the session. Plaintext OTP codes are never sent over the wire. properties: type: type: string diff --git a/openapi/paths/auth/auth_credentials.yaml b/openapi/paths/auth/auth_credentials.yaml index 076b22b2..aa6b5ab8 100644 --- a/openapi/paths/auth/auth_credentials.yaml +++ b/openapi/paths/auth/auth_credentials.yaml @@ -89,10 +89,16 @@ post: description: >- Authentication credential created successfully. The body is the created `AuthMethod` for all three credential types. For `EMAIL_OTP`, - the email is the customer email tied to the internal account, and - the response also carries `otpEncryptionTargetBundle` — the HPKE - target bundle the client uses to encrypt the OTP attempt on the - subsequent `POST /auth/credentials/{id}/verify`. For `PASSKEY`, + the email is the customer email tied to the internal account. When + the response is for adding EMAIL_OTP back to an existing wallet + through the signed-retry flow, it also carries + `otpEncryptionTargetBundle` — the HPKE target bundle the client uses + to encrypt the OTP attempt on the subsequent + `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet + bootstrap responses may omit that bundle; if it is absent, call + `POST /auth/credentials/{id}/challenge` for the new credential to + issue a fresh OTP and receive `otpEncryptionTargetBundle` before + verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there diff --git a/openapi/paths/auth/auth_credentials_{id}_verify.yaml b/openapi/paths/auth/auth_credentials_{id}_verify.yaml index 7f4afb06..262cf094 100644 --- a/openapi/paths/auth/auth_credentials_{id}_verify.yaml +++ b/openapi/paths/auth/auth_credentials_{id}_verify.yaml @@ -7,11 +7,11 @@ post: For `EMAIL_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` - under the `otpEncryptionTargetBundle` returned from the credential's - registration or re-issued via - `POST /auth/credentials/{id}/challenge`. The server is a - pass-through and never sees the plaintext OTP code. On success the - response is `202` with a `payloadToSign` carrying the + under the `otpEncryptionTargetBundle` returned from registration + when present, or from `POST /auth/credentials/{id}/challenge` when + registration omitted it or the OTP must be reissued. The server is + a pass-through and never sees the plaintext OTP code. On success + the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the