feat(idos): Gov ID branch — sessions wiring, consumer SDK, kyc-token, credentials endpoint#34
Open
calebtuttle wants to merge 6 commits intodevfrom
Open
feat(idos): Gov ID branch — sessions wiring, consumer SDK, kyc-token, credentials endpoint#34calebtuttle wants to merge 6 commits intodevfrom
calebtuttle wants to merge 6 commits intodevfrom
Conversation
…side init - Add 'idos' to the four idvProvider validation lists in endpoints.ts (POST /sessions/v3 paths and the setIdvProvider recovery handler). - Add an 'idos' branch to handleIdvSessionCreation that persists the session row with no external SDK call. idOS has no applicant/token/webhook lifecycle; the user grants access to a pre-existing credential later, fetched in GET /idos/credentials/v3/:_id/:nullifier. Refs docs/plans/2026-04-30-003-feat-idos-as-onfido-alternative-in-gov-id-flow-plan.md U1.
- src/services/idos/consumer.ts: module-scoped, lazily-initialized idOSConsumer with a narrow public surface (listGrants, decryptCredential, getSharedCredential). Reads IDOS_SIGNER (128-char hex nacl secret key) and IDOS_RECIPIENT_ENCRYPTION_PRIVATE_KEY on first use; throws with clear messages on missing/malformed env vars. Optional IDOS_NODE_URL and IDOS_CHAIN_ID overrides are forwarded when set. - src/services/idos/consumer.test.ts: covers env validation, init memoization, and node/chain override forwarding via bun:test mock.module on the SDK. - package.json: add @idos-network/consumer ^1.1.0 and tweetnacl ^1.0.3. The wrapper exists so the credentials endpoint (Slice 3 / parent plan U4) and any future idOS-side reads call into one well-defined seam — also the seam unit tests mock. Refs docs/plans/2026-04-30-003-feat-idos-as-onfido-alternative-in-gov-id-flow-plan.md U2.
Adds the endpoint the verify page (Slice 4) calls to mint the JWT it embeds in https://verify.fractal.id/kyc?token=... — the signing key stays on the server, and per-user walletAddress / externalUserId fields are filled in the payload. - src/services/idos/kyc-token.ts: signKycToken(env, opts) helper plus issueKycTokenProd / issueKycTokenSandbox HTTP handlers. Validates body (0x-prefixed 20-byte walletAddress, bounded externalUserId), rate-limits the endpoint key separately for prod and sandbox, and surfaces a clear 500 'not configured' message when IDOS_JWT_PRIVATE_KEY or IDOS_FRACTAL_CLIENT_ID is missing or malformed (no leak of the env-var validation messages otherwise). Sandbox env vars (IDOS_JWT_PRIVATE_KEY_SANDBOX, IDOS_FRACTAL_CLIENT_ID_SANDBOX) are read with a fallback to the prod values. - src/routes/idos.ts: new prod + sandbox routers. Slice 3 (parent plan U4) will extend these with the credentials endpoint. - src/index.ts: mount /idos and /sandbox/idos. - src/services/idos/kyc-token.test.ts: covers payload shape, ES512 alg, optional-field passthrough, sandbox env-var fallback, env-var validation errors, and HTTP handler 400/500 paths. Generates a P-521 key per run via crypto.generateKeyPairSync so no fixture key is checked in. No expiresIn on the JWT — per the parent plan U9, Fractal expects tokens without expiration. If short-lived JWTs are required later, signKycToken is the single seam to add expiresIn. Refs docs/plans/2026-04-30-003-feat-idos-as-onfido-alternative-in-gov-id-flow-plan.md U9.
Combines U3 (extractCreds parity) + U4 (HTTP endpoint) for the idOS branch
of the Gov ID flow.
U3 — services/idos/credentials/utils.ts
- extractCreds(vc) maps a decrypted VerifiableCredential<Subject> into the
Onfido-compatible { rawCreds, derivedCreds, fieldsInLeaf } shape.
- Hash composition order is byte-identical to Onfido's extractCreds for the
same logical inputs (nameHash, streetHash, addressHash, and the composite
nameDobCitySubdivisionZipStreetExpireHash). The parity is enforced by
utils.test.ts which runs both extractors over shared fixtures.
- ISO 8601 birthdates are normalized to YYYY-MM-DD before getDateAsInt
(Onfido's helper splits on '-' and chokes on full timestamps).
- expirationDate stays empty by design — Onfido's extractor currently zeros
it; mirroring keeps the composite hash compatible. Both must change in
lockstep if expiry is ever folded into the leaf.
- nationality fall-back when idDocumentCountry is absent.
U4 — services/idos/credentials/v3.ts
- Endpoint mirrors services/onfido/credentials/v3.ts structure: nullifier
format check, ObjectId parse, session lookup, VERIFICATION_FAILED guard,
5-day nullifier replay path, sybil-resistance UUID + UserVerifications
insert, issuev2KYC issuance, ISSUED status.
- New: reads dataId + userAddress from the query string (frontend supplies
them after a successful idOS access grant). dataId is filtered out of the
consumer's grant list via paginated lookup.
- Trust check pulls the on-chain credential record (idOSCredential
.issuer_auth_public_key) and rejects with 400 if it isn't in the
configured allow-list — done before extractCreds so we never hash fields
from an untrusted credential.
- Returns 404 (not VERIFICATION_FAILED) when no grant exists yet, so the
frontend can retry without dirtying the session.
- Sandbox + prod handlers via the same factory.
Supporting additions
- services/idos/issuers.ts: env-driven CustomIssuerType allow-list with
IDOS_ALLOWED_ISSUERS_JSON or IDOS_ALLOWED_ISSUER + IDOS_ALLOWED_ISSUER_PUBLIC_KEY.
Cached + lazily validated; throws clear errors on misconfig.
- schemas.ts + types.ts: extend NullifierAndCreds idvSessionIds with
idos.dataId so the 5-day replay lookup has a key. Mirrored on
SandboxNullifierAndCredsSchema.
- routes/idos.ts: register the new GET handler on prod + sandbox routers.
Refs docs/plans/2026-04-30-003-feat-idos-as-onfido-alternative-in-gov-id-flow-plan.md U3 + U4.
Fractal rejects 'basic+uniqueness+idos' with 'Can't create KYC session with both KYC and Uniqueness' — kyc:true already implies KYC, and the +uniqueness component requires a separate session. Switch level to 'basic+idos' (KYC + idOS profile creation). Uniqueness is enforced downstream by the sybil-resistance UUID + UserVerifications insert in services/idos/credentials/v3.ts, so this doesn't lower the bar — it just removes a session-type collision in Fractal's API.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the id-server side of the idOS Gov ID flow. Pairs with frontend PR holonym-foundation/human-id#196.
What lands
U1 — sessions (
ea79634)"idos"to the fouridvProvidervalidation lists insrc/services/sessions/endpoints.ts(POST/sessions/v3paths andsetIdvProvider).idosbranch tohandleIdvSessionCreationthat persists the session row with no external SDK call. idOS has no applicant / token / webhook lifecycle.U2 — consumer SDK wrapper (
56c7f1e)src/services/idos/consumer.ts: lazily-initializedidOSConsumerwith a narrow public surface (listGrants,decryptCredential,getSharedCredential).IDOS_SIGNER(128-char hex nacl secret key) andIDOS_RECIPIENT_ENCRYPTION_PRIVATE_KEYon first use; throws clear errors on missing/malformed env vars. OptionalIDOS_NODE_URLandIDOS_CHAIN_IDoverrides.package.json: add@idos-network/consumer ^1.1.0andtweetnacl ^1.0.3.U9 —
POST /idos/kyc-token(6c3f5dc)walletAddress, boundedexternalUserId), per-env rate limit, sandbox env-var fallback (IDOS_JWT_PRIVATE_KEY_SANDBOX,IDOS_FRACTAL_CLIENT_ID_SANDBOX).expiresInper the parent plan U9 (Fractal expects tokens without expiration). Single seam if that ever changes./idos/kyc-tokenand/sandbox/idos/kyc-token.U3 + U4 —
GET /idos/credentials/v3/:_id/:nullifier(4686c2f)services/idos/credentials/utils.ts:extractCreds(vc)maps a decryptedVerifiableCredential<VerifiableCredentialSubject>into the Onfido-compatible{ rawCreds, derivedCreds, fieldsInLeaf }shape. Hash composition is byte-identical to Onfido'sextractCredsfor equivalent inputs — enforced by parity tests against the Onfido extractor.services/idos/credentials/v3.ts: mirrorsservices/onfido/credentials/v3.tsstructurally — nullifier validation, ObjectId parse, session lookup,VERIFICATION_FAILEDguard, 5-day nullifier replay path, sybil-resistance UUID +UserVerificationsinsert,issuev2KYC,ISSUEDstatus. New: readsdataId+userAddressfrom the query string, filters the consumer's grant list bydata_id, and trust-checks the on-chain credential'sissuer_auth_public_keyagainst the configured allow-list before any field hashing.services/idos/issuers.ts: env-driven trust list (IDOS_ALLOWED_ISSUERS_JSONorIDOS_ALLOWED_ISSUER+IDOS_ALLOWED_ISSUER_PUBLIC_KEY).schemas.ts+types.ts: extendNullifierAndCreds.idvSessionIdswithidos.dataId(sandbox mirrored).Plan
Implements U1, U2, U3, U4, U9 from the parent plan in the human-id repo:
docs/plans/2026-04-30-003-feat-idos-as-onfido-alternative-in-gov-id-flow-plan.md(also visible on the linked frontend PR).Env vars introduced
IDOS_SIGNERIDOS_RECIPIENT_ENCRYPTION_PRIVATE_KEYIDOS_NODE_URL(optional)IDOS_CHAIN_ID(optional)IDOS_JWT_PRIVATE_KEYIDOS_FRACTAL_CLIENT_IDIDOS_JWT_PRIVATE_KEY_SANDBOX(optional)IDOS_FRACTAL_CLIENT_ID_SANDBOX(optional)IDOS_ALLOWED_ISSUERS_JSONorIDOS_ALLOWED_ISSUER+IDOS_ALLOWED_ISSUER_PUBLIC_KEY.env.examplewas not updated in this branch (permission-blocked when authored). Copy these into the deploy env-var contract before Slice U / staging smoke.Test plan
bun installcompletes (adds@idos-network/consumer+tweetnacl)bun test src/services/idos/consumer.test.ts— env validation + init memoizationbun test src/services/idos/kyc-token.test.ts— ES512 payload shape, sandbox env fallback, validation 400sbun test src/services/idos/credentials/utils.test.ts— Onfido parity for nameHash / streetHash / addressHash / nameDobCitySubdivisionZipStreetExpireHashbun --watch src/server.tsboots without idOS env vars set (lazy-init contract)POST /idos/kyc-tokenwith a validwalletAddressand verify the returned JWT decodes to the expected payload (against IDOS_FRACTAL_CLIENT_ID + ES512 alg)GET /idos/credentials/v3/:sid/:nullifierreturns issuance response equivalent to the Onfido path for the same logical inputNotes
🤖 Generated with Claude Code