From e66c867f1d53585ccae3019db5c887fc9ee3ed96 Mon Sep 17 00:00:00 2001 From: Dhruv Pareek Date: Tue, 21 Apr 2026 18:42:08 -0700 Subject: [PATCH] feat: add OAUTH branch to additional-credential challenge flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the OAUTH branch to `AuthCredentialAdditionalChallengeOneOf`, letting platforms register a second (or third, etc.) OAuth credential on an internal account that already has one. Completes the "add another credential" challenge/retry pattern for OAuth, matching the EMAIL_OTP flow already in the stack. **Flow** 1. `POST /auth/credentials` with `{ type: "OAUTH", accountId, oidcToken }` on an account that already has a credential. 2. Response is 202 with `{ type: "OAUTH", payloadToSign, requestId, expiresAt }`. 3. Client signs `payloadToSign` with the session private key of an existing verified credential on the same internal account and retries the request with `Grid-Wallet-Signature` + `Request-Id` headers. 4. Signed retry returns 201 with the created `AuthMethod`. **Schemas added** - `OauthCredentialAdditionalChallengeFields` — `{ type: "OAUTH" }` (variant single-value enum on `type`; no analogue to the `email` field on the EMAIL_OTP variant — providers are not distinguished at the challenge level). - `OauthCredentialAdditionalChallenge` — `allOf(AuthCredentialAdditionalChallenge, OauthCredentialAdditionalChallengeFields)`; wire shape is `{ type, payloadToSign, requestId, expiresAt }` (signing fields inherited from the base). **Wire-up** - `AuthCredentialAdditionalChallengeOneOf.yaml` discriminator map extended with `OAUTH → OauthCredentialAdditionalChallenge`. - OAuth example added to the 202 response on `POST /auth/credentials`. - `.stainless/stainless.yml` registers the two new schemas under `auth.credentials`. **Notes** - Multiple OAuth credentials per internal account are allowed (no `OAUTH_CREDENTIAL_ALREADY_EXISTS`); this PR documents the concrete wire shape Grid returns when the client hits that branch. - Final PR in the OAuth sub-stack on top of `04-20-feat_sign_embedded_wallet_transfers_with_grid-wallet-signature_on__quotes_execute`; together with the two prior PRs it covers create, verify, and additional-credential registration for OAuth. - Bundled `openapi.yaml` and `mintlify/openapi.yaml` regenerated via `make build`. --- .stainless/stainless.yml | 2 ++ mintlify/openapi.yaml | 24 +++++++++++++++++++ openapi.yaml | 24 +++++++++++++++++++ ...uthCredentialAdditionalChallengeOneOf.yaml | 2 ++ .../OauthCredentialAdditionalChallenge.yaml | 4 ++++ ...thCredentialAdditionalChallengeFields.yaml | 11 +++++++++ openapi/paths/auth/auth_credentials.yaml | 7 ++++++ 7 files changed, 74 insertions(+) create mode 100644 openapi/components/schemas/auth/OauthCredentialAdditionalChallenge.yaml create mode 100644 openapi/components/schemas/auth/OauthCredentialAdditionalChallengeFields.yaml diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml index 6976f945..af2154f9 100644 --- a/.stainless/stainless.yml +++ b/.stainless/stainless.yml @@ -348,6 +348,8 @@ resources: oauth_credential_create_request_fields: '#/components/schemas/OauthCredentialCreateRequestFields' oauth_credential_verify_request: '#/components/schemas/OauthCredentialVerifyRequest' oauth_credential_verify_request_fields: '#/components/schemas/OauthCredentialVerifyRequestFields' + oauth_credential_additional_challenge: '#/components/schemas/OauthCredentialAdditionalChallenge' + oauth_credential_additional_challenge_fields: '#/components/schemas/OauthCredentialAdditionalChallengeFields' exchange_rates: methods: list: diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index f84a6708..4aff87c4 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -3682,6 +3682,13 @@ paths: payloadToSign: '{"requestId":"7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21","type":"EMAIL_OTP","accountId":"InternalAccount:01HF3Z4QWERTY","expiresAt":"2026-04-08T15:35:00Z"}' requestId: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 expiresAt: '2026-04-08T15:35:00Z' + oauth: + summary: Additional OAuth credential challenge + value: + type: OAUTH + payloadToSign: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg== + requestId: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an `EMAIL_OTP` credential on an internal account that already has one — only one email OTP credential is supported per internal account at this time. content: @@ -13221,13 +13228,30 @@ components: allOf: - $ref: '#/components/schemas/AuthCredentialAdditionalChallenge' - $ref: '#/components/schemas/EmailOtpCredentialAdditionalChallengeFields' + OauthCredentialAdditionalChallengeFields: + type: object + required: + - type + properties: + type: + type: string + enum: + - OAUTH + description: Discriminator value identifying this as an additional-credential challenge for an OAuth credential. + OauthCredentialAdditionalChallenge: + title: OAuth Credential Additional Challenge + allOf: + - $ref: '#/components/schemas/AuthCredentialAdditionalChallenge' + - $ref: '#/components/schemas/OauthCredentialAdditionalChallengeFields' AuthCredentialAdditionalChallengeOneOf: oneOf: - $ref: '#/components/schemas/EmailOtpCredentialAdditionalChallenge' + - $ref: '#/components/schemas/OauthCredentialAdditionalChallenge' discriminator: propertyName: type mapping: EMAIL_OTP: '#/components/schemas/EmailOtpCredentialAdditionalChallenge' + OAUTH: '#/components/schemas/OauthCredentialAdditionalChallenge' AuthCredentialVerifyRequest: type: object required: diff --git a/openapi.yaml b/openapi.yaml index f84a6708..4aff87c4 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3682,6 +3682,13 @@ paths: payloadToSign: '{"requestId":"7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21","type":"EMAIL_OTP","accountId":"InternalAccount:01HF3Z4QWERTY","expiresAt":"2026-04-08T15:35:00Z"}' requestId: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 expiresAt: '2026-04-08T15:35:00Z' + oauth: + summary: Additional OAuth credential challenge + value: + type: OAUTH + payloadToSign: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg== + requestId: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an `EMAIL_OTP` credential on an internal account that already has one — only one email OTP credential is supported per internal account at this time. content: @@ -13221,13 +13228,30 @@ components: allOf: - $ref: '#/components/schemas/AuthCredentialAdditionalChallenge' - $ref: '#/components/schemas/EmailOtpCredentialAdditionalChallengeFields' + OauthCredentialAdditionalChallengeFields: + type: object + required: + - type + properties: + type: + type: string + enum: + - OAUTH + description: Discriminator value identifying this as an additional-credential challenge for an OAuth credential. + OauthCredentialAdditionalChallenge: + title: OAuth Credential Additional Challenge + allOf: + - $ref: '#/components/schemas/AuthCredentialAdditionalChallenge' + - $ref: '#/components/schemas/OauthCredentialAdditionalChallengeFields' AuthCredentialAdditionalChallengeOneOf: oneOf: - $ref: '#/components/schemas/EmailOtpCredentialAdditionalChallenge' + - $ref: '#/components/schemas/OauthCredentialAdditionalChallenge' discriminator: propertyName: type mapping: EMAIL_OTP: '#/components/schemas/EmailOtpCredentialAdditionalChallenge' + OAUTH: '#/components/schemas/OauthCredentialAdditionalChallenge' AuthCredentialVerifyRequest: type: object required: diff --git a/openapi/components/schemas/auth/AuthCredentialAdditionalChallengeOneOf.yaml b/openapi/components/schemas/auth/AuthCredentialAdditionalChallengeOneOf.yaml index 717a103b..9448c792 100644 --- a/openapi/components/schemas/auth/AuthCredentialAdditionalChallengeOneOf.yaml +++ b/openapi/components/schemas/auth/AuthCredentialAdditionalChallengeOneOf.yaml @@ -1,6 +1,8 @@ oneOf: - $ref: ./EmailOtpCredentialAdditionalChallenge.yaml + - $ref: ./OauthCredentialAdditionalChallenge.yaml discriminator: propertyName: type mapping: EMAIL_OTP: ./EmailOtpCredentialAdditionalChallenge.yaml + OAUTH: ./OauthCredentialAdditionalChallenge.yaml diff --git a/openapi/components/schemas/auth/OauthCredentialAdditionalChallenge.yaml b/openapi/components/schemas/auth/OauthCredentialAdditionalChallenge.yaml new file mode 100644 index 00000000..933d11fb --- /dev/null +++ b/openapi/components/schemas/auth/OauthCredentialAdditionalChallenge.yaml @@ -0,0 +1,4 @@ +title: OAuth Credential Additional Challenge +allOf: + - $ref: ./AuthCredentialAdditionalChallenge.yaml + - $ref: ./OauthCredentialAdditionalChallengeFields.yaml diff --git a/openapi/components/schemas/auth/OauthCredentialAdditionalChallengeFields.yaml b/openapi/components/schemas/auth/OauthCredentialAdditionalChallengeFields.yaml new file mode 100644 index 00000000..88923c51 --- /dev/null +++ b/openapi/components/schemas/auth/OauthCredentialAdditionalChallengeFields.yaml @@ -0,0 +1,11 @@ +type: object +required: + - type +properties: + type: + type: string + enum: + - OAUTH + description: >- + Discriminator value identifying this as an additional-credential + challenge for an OAuth credential. diff --git a/openapi/paths/auth/auth_credentials.yaml b/openapi/paths/auth/auth_credentials.yaml index 525ebea5..1bbac911 100644 --- a/openapi/paths/auth/auth_credentials.yaml +++ b/openapi/paths/auth/auth_credentials.yaml @@ -114,6 +114,13 @@ post: payloadToSign: '{"requestId":"7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21","type":"EMAIL_OTP","accountId":"InternalAccount:01HF3Z4QWERTY","expiresAt":"2026-04-08T15:35:00Z"}' requestId: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 expiresAt: '2026-04-08T15:35:00Z' + oauth: + summary: Additional OAuth credential challenge + value: + type: OAUTH + payloadToSign: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg== + requestId: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': description: >- Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when