Add encrypted OTP flow#506
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
✱ Stainless preview builds for gridThis PR will update the cli csharp go kotlin openapi php python ruby typescript Edit this comment to update them. They will appear in their respective SDK's changelogs. ✅ grid-openapi studio · code · diff
✅ grid-ruby studio · code · diff
✅ grid-kotlin studio · code · diff
✅ grid-typescript studio · code · diff
✅ grid-python studio · code · diff
|
| 💡 Name/Renamed: 246 names were renamed due to language constraints, so fallback names will be used instead. |
✅ grid-go studio · code · diff
Your SDK build was successful.
generate ✅→build ✅→lint ❗→test ❗go get github.com/stainless-sdks/grid-go@bf3bff7276fd9961c2c41835c701d0a3ae18ab3b
✅ grid-cli studio · code · diff
Your SDK build had at least one "warning" diagnostic, but this did not represent a regression.
generate ⚠️→build ❗→lint ❗→test ❗
This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-05-29 23:29:37 UTC
Greptile SummaryThis PR introduces a V3 HPKE-based
Confidence Score: 4/5Safe to merge after clarifying whether the legacy otp+clientPublicKey flow is still accepted server-side and updating the schema accordingly. The PR description explicitly states the legacy EMAIL_OTP flow is "still functional, deprecated," yet the otp and clientPublicKey properties are completely removed from EmailOtpCredentialVerifyRequestFields.yaml rather than retained with deprecated:true. If the server still accepts those fields, the published schema no longer reflects that, breaking SDK generators and contract tests for clients on the old flow. openapi/components/schemas/auth/EmailOtpCredentialVerifyRequestFields.yaml — the removal of otp and clientPublicKey properties needs to be reconciled with the legacy flow's continued server-side support.
|
| Filename | Overview |
|---|---|
| openapi/components/schemas/auth/EmailOtpCredentialVerifyRequestFields.yaml | Switches required fields to encryptedOtpBundle for V3 flow, but removes otp and clientPublicKey entirely rather than marking them deprecated:true — contradicts PR description that the legacy flow is still functional. |
| openapi/paths/auth/auth_credentials_{id}_verify.yaml | Adds Grid-Wallet-Signature header, 202 response schema, updated 401 conditions, and two EMAIL_OTP request examples; payloadToSign example now uses a valid base64url signature segment. |
| openapi/components/schemas/auth/AuthMethodResponse.yaml | Adds optional otpEncryptionTargetBundle property for EMAIL_OTP credentials; correctly kept out of required since it only appears for one credential type. |
| openapi/components/schemas/auth/AuthSession.yaml | Clarifies that encryptedSessionSigningKey is omitted for EMAIL_OTP sessions (client holds the TEK private key); clean, accurate update. |
| openapi/components/schemas/auth/AuthSignedRequestChallenge.yaml | Extends description to cover the EMAIL_OTP verify retry use case and documents that the TEK keypair (not the session API keypair) is used for that specific operation. |
| openapi/paths/auth/auth_credentials.yaml | Updates 201 response description and example to include otpEncryptionTargetBundle; oidcToken example updated to a valid base64url signature. |
| openapi/paths/auth/auth_credentials_{id}_challenge.yaml | Updates EMAIL_OTP description to mention otpEncryptionTargetBundle returned on re-issue; adds bundle to the response example. |
Sequence Diagram
sequenceDiagram
participant C as Client
participant S as Grid Server
note over C,S: Registration
C->>S: POST /auth/credentials (EMAIL_OTP)
S-->>C: 201 AuthMethodResponse (otpEncryptionTargetBundle)
note over C,S: V3 Verification - Leg 1
C->>C: Generate ephemeral TEK keypair
C->>C: HPKE-encrypt clientPublicKey+OTP under otpEncryptionTargetBundle
C->>S: "POST /auth/credentials/{id}/verify body={encryptedOtpBundle}"
S->>S: Decrypt bundle, verify OTP code
S-->>C: 202 AuthSignedRequestChallenge (payloadToSign, requestId, expiresAt)
note over C,S: V3 Verification - Leg 2 signed retry
C->>C: Sign verificationToken with TEK private key
C->>S: "POST /auth/credentials/{id}/verify + Grid-Wallet-Signature + Request-Id"
S->>S: Verify TEK signature against bound pubkey
S-->>C: 200 AuthSession (TEK pubkey becomes session API key)
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
openapi/components/schemas/auth/EmailOtpCredentialVerifyRequestFields.yaml:1-4
**Deprecated fields dropped from schema while legacy flow remains live**
The PR description explicitly states the legacy `otp` + `clientPublicKey` flow is *"still functional, deprecated"*, yet those two properties are completely absent from this schema (not marked `deprecated: true` — removed entirely). Because this is the sole OpenAPI contract, SDK generators and contract-testing tools will have no record that `otp` or `clientPublicKey` are accepted fields. Any existing client still using the legacy flow will appear to be sending invalid request bodies according to the spec. The properties should remain in the schema marked with `deprecated: true` (and removed from `required`) until the server-side removal ships, so the schema accurately reflects what the server actually accepts.
Reviews (2): Last reviewed commit: "Add encrypted OTP flow" | Re-trigger Greptile
36a042d to
20444ac
Compare
20444ac to
2fbd5ec
Compare
|
2fbd5ec to
f9f2bec
Compare
| returned in the response to this public key. The key is ephemeral | ||
| and one-time-use per verification request. | ||
| example: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 | ||
| HPKE-encrypted payload binding the client's ephemeral public key |
There was a problem hiding this comment.
is there something that describes the format of this bundle or how to create it?
f9f2bec to
50e731f
Compare
50e731f to
2c892e7
Compare

TL;DR
Introduces a secure V3 HPKE-based
EMAIL_OTPverification flow and deprecates the legacy plaintext OTP flow.What changed?
POST /auth/credentials(registration)201response forEMAIL_OTPcredentials now includesotpEncryptionTargetBundle— a one-time HPKE target bundle the client uses to encrypt the OTP attempt before sending it to the server.POST /auth/credentials/{id}/challenge(re-issue)EMAIL_OTPresponse now returns a freshotpEncryptionTargetBundlealongside theAuthMethod, replacing the previous description that said there was no challenge body to surface.POST /auth/credentials/{id}/verify(verification)EMAIL_OTP: the client submits anencryptedOtpBundle(HPKE-encrypted payload containing the TEK public key and OTP code attempt). The server responds with202carrying apayloadToSign(verificationToken). The client signs the token with the TEK private key and retries withGrid-Wallet-SignatureandRequest-Idheaders to receive the issuedAuthSession. The TEK public key becomes the session API key on completion.EMAIL_OTPflow (plaintextotp+clientPublicKey) is now marked deprecated and will be removed in a future release.Grid-Wallet-Signaturerequest header, required on the signed retry leg of the V3EMAIL_OTPflow.Request-Idheader description to cover both theEMAIL_OTPsigned retry andPASSKEYassertion correlation use cases.202response schema (AuthSignedRequestChallenge) to the verify endpoint.401error conditions to coverEMAIL_OTPsigned retry failures (missing/malformed signature, key mismatch, expired challenge)."000000".Schema changes
AuthMethodResponse: addsotpEncryptionTargetBundleproperty.AuthSignedRequestChallenge: extended to cover theEMAIL_OTPverify retry use case; documents that the TEK keypair (not the session API keypair) is used to sign the stamp for this operation.EmailOtpCredentialVerifyRequestFields:otpandclientPublicKeymarked deprecated;encryptedOtpBundleadded;otpandclientPublicKeyremoved fromrequired.How to test?
V3 flow:
EMAIL_OTPcredential viaPOST /auth/credentialsand captureotpEncryptionTargetBundlefrom the response.{clientPublicKey, otpCodeAttempt}underotpEncryptionTargetBundleto produceencryptedOtpBundle.POST /auth/credentials/{id}/verifywithencryptedOtpBundleand expect a202response containingpayloadToSignandrequestId.payloadToSignwith the TEK private key and resubmit withGrid-Wallet-SignatureandRequest-Idheaders; expect a200AuthSession."000000"as the magic value.Legacy flow (still functional, deprecated):
POST /auth/credentials/{id}/verifywith plaintextotpandclientPublicKey; expect a200AuthSessionwithencryptedSessionSigningKey.Why make this change?
The legacy
EMAIL_OTPflow transmits the plaintext OTP code to the server, creating an unnecessary exposure surface. The V3 flow uses HPKE to encrypt the OTP code and the client's public key together so the plaintext code never transits the server. The TEK keypair generated by the client for encryption also becomes the session API key, binding authentication and session establishment into a single cryptographic operation.