@oxyhq/contracts 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/devicePairing.js +138 -0
- package/dist/cjs/deviceSession.js +9 -0
- package/dist/cjs/index.js +21 -3
- package/dist/cjs/sessionStatus.js +15 -0
- package/dist/cjs/transparency.js +89 -0
- package/dist/cjs/userResponse.js +27 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/devicePairing.js +135 -0
- package/dist/esm/deviceSession.js +9 -0
- package/dist/esm/index.js +7 -1
- package/dist/esm/sessionStatus.js +15 -0
- package/dist/esm/transparency.js +86 -0
- package/dist/esm/userResponse.js +26 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/devicePairing.d.ts +130 -0
- package/dist/types/deviceSession.d.ts +11 -0
- package/dist/types/index.d.ts +6 -2
- package/dist/types/keyRecovery.d.ts +4 -4
- package/dist/types/keyRotation.d.ts +2 -2
- package/dist/types/sessionStatus.d.ts +27 -6
- package/dist/types/transparency.d.ts +392 -0
- package/dist/types/userResponse.d.ts +256 -0
- package/package.json +3 -3
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Device-to-device identity transfer contracts (b3 Feature 2 — "add a device").
|
|
4
|
+
*
|
|
5
|
+
* SINGLE SOURCE OF TRUTH for the wire shape of the short-lived, unauthenticated
|
|
6
|
+
* relay that carries E2E-encrypted key material from an existing (old) device to
|
|
7
|
+
* a fresh (new) device so both end up holding the SAME secp256k1 private key
|
|
8
|
+
* (key cloning). The relay is E2E-encrypted via an ephemeral secp256k1 ECDH
|
|
9
|
+
* handshake: the server stores only the two ephemeral public keys plus an opaque
|
|
10
|
+
* AEAD ciphertext + nonce and NEVER holds a decryption key.
|
|
11
|
+
*
|
|
12
|
+
* Flow:
|
|
13
|
+
* 1. New device (no identity) generates an ephemeral pair and calls
|
|
14
|
+
* `POST /identity/device-transfer/init { newEphPub, newDeviceLabel? }` →
|
|
15
|
+
* `{ pairingId, expiresAt }`. The QR carries ONLY `pairingId` (not
|
|
16
|
+
* self-contained — mirrors the QR sign-in `approve-info` resolution).
|
|
17
|
+
* 2. Old device (has identity) scans, resolves the request via
|
|
18
|
+
* `GET /identity/device-transfer/:pairingId` (returns `newEphPub` + label),
|
|
19
|
+
* derives `transferKey = HKDF(ECDH(oldEphPriv, newEphPub), pairingId,
|
|
20
|
+
* 'oxy-device-transfer-v1')`, AEAD-encrypts `{ privateKey, publicKey }`, and
|
|
21
|
+
* calls `POST /identity/device-transfer/:pairingId/approve` with the
|
|
22
|
+
* ciphertext PLUS a fresh signature over
|
|
23
|
+
* `{ action:'approve_device_transfer', pairingId, timestamp }` made with the
|
|
24
|
+
* CURRENT identity key (dual-proof: a bearer alone cannot exfiltrate the key).
|
|
25
|
+
* 3. New device (socket push or poll fallback) re-derives the same
|
|
26
|
+
* `transferKey` from `ECDH(newEphPriv, oldEphPub)`, decrypts, and imports the
|
|
27
|
+
* private key, then completes a NORMAL challenge/verify sign-in.
|
|
28
|
+
*
|
|
29
|
+
* The load-bearing response shapes are declared as explicit `interface`s (same
|
|
30
|
+
* `moduleResolution: node` rationale as `UserNameResponse` / the identity/civic
|
|
31
|
+
* contracts: a nested `z.infer<>` can degrade to `{}` under a consumer's
|
|
32
|
+
* `moduleResolution: "node"`), with the runtime schemas annotated
|
|
33
|
+
* `z.ZodType<Interface>`.
|
|
34
|
+
*
|
|
35
|
+
* Platform-agnostic — zod only, no react/react-native/expo, ESM-safe.
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.deviceTransferDenyResponseSchema = exports.deviceTransferApproveResponseSchema = exports.deviceTransferApproveRequestSchema = exports.deviceTransferInfoResponseSchema = exports.deviceTransferInitResponseSchema = exports.deviceTransferInitRequestSchema = exports.devicePairingStatusSchema = void 0;
|
|
39
|
+
const zod_1 = require("zod");
|
|
40
|
+
/* -------------------------------------------------------------------------- */
|
|
41
|
+
/* Shared field validators */
|
|
42
|
+
/* -------------------------------------------------------------------------- */
|
|
43
|
+
/** Lowercase/uppercase hex string (no `0x` prefix). */
|
|
44
|
+
const hexString = zod_1.z
|
|
45
|
+
.string()
|
|
46
|
+
.trim()
|
|
47
|
+
.regex(/^[0-9a-fA-F]+$/, 'must be a hex string');
|
|
48
|
+
/**
|
|
49
|
+
* A secp256k1 public key, hex-encoded — compressed (`02`/`03` + 32 bytes = 66
|
|
50
|
+
* chars) or uncompressed (`04` + 64 bytes = 130 chars). The exact curve-point
|
|
51
|
+
* validity is re-checked server-side; this only bounds the shape/length.
|
|
52
|
+
*/
|
|
53
|
+
const publicKeyHex = hexString.min(66).max(130);
|
|
54
|
+
/** DER-encoded ECDSA signature, hex (variable length, ~140–144 chars). */
|
|
55
|
+
const signatureHex = hexString.min(2).max(256);
|
|
56
|
+
/**
|
|
57
|
+
* The 24-byte XChaCha20-Poly1305 nonce, hex (exactly 48 chars). Matches
|
|
58
|
+
* `@oxyhq/core` `AEAD_NONCE_LENGTH` (24 bytes).
|
|
59
|
+
*/
|
|
60
|
+
const nonceHex = hexString.length(48);
|
|
61
|
+
/**
|
|
62
|
+
* The AEAD ciphertext (Poly1305 tag appended), hex. The plaintext is the small
|
|
63
|
+
* JSON `{ privateKey, publicKey }` (~200 bytes), so the ciphertext stays well
|
|
64
|
+
* under the cap; the bound blunts relay-abuse via oversized blobs.
|
|
65
|
+
*/
|
|
66
|
+
const ciphertextHex = hexString.min(2).max(8192);
|
|
67
|
+
/* -------------------------------------------------------------------------- */
|
|
68
|
+
/* Status */
|
|
69
|
+
/* -------------------------------------------------------------------------- */
|
|
70
|
+
/**
|
|
71
|
+
* Pairing lifecycle:
|
|
72
|
+
* - `pending` — created by the new device, awaiting the old device's approval.
|
|
73
|
+
* - `approved` — the old device sealed and posted the encrypted key material.
|
|
74
|
+
* - `denied` — the old device explicitly cancelled the transfer.
|
|
75
|
+
* - `expired` — the 3-minute TTL elapsed before approval.
|
|
76
|
+
*/
|
|
77
|
+
exports.devicePairingStatusSchema = zod_1.z.enum([
|
|
78
|
+
'pending',
|
|
79
|
+
'approved',
|
|
80
|
+
'denied',
|
|
81
|
+
'expired',
|
|
82
|
+
]);
|
|
83
|
+
/* -------------------------------------------------------------------------- */
|
|
84
|
+
/* POST /identity/device-transfer/init */
|
|
85
|
+
/* -------------------------------------------------------------------------- */
|
|
86
|
+
/** Request body for `POST /identity/device-transfer/init` (public). */
|
|
87
|
+
exports.deviceTransferInitRequestSchema = zod_1.z.object({
|
|
88
|
+
/** The new device's ephemeral secp256k1 public key (single-use). */
|
|
89
|
+
newEphPub: publicKeyHex,
|
|
90
|
+
/** Optional human-readable label for the new device (e.g. "iPhone 15"). */
|
|
91
|
+
newDeviceLabel: zod_1.z.string().trim().min(1).max(120).optional(),
|
|
92
|
+
});
|
|
93
|
+
exports.deviceTransferInitResponseSchema = zod_1.z.object({
|
|
94
|
+
pairingId: zod_1.z.string(),
|
|
95
|
+
expiresAt: zod_1.z.string(),
|
|
96
|
+
});
|
|
97
|
+
exports.deviceTransferInfoResponseSchema = zod_1.z.object({
|
|
98
|
+
pairingId: zod_1.z.string(),
|
|
99
|
+
newDeviceEphemeralPublicKey: zod_1.z.string(),
|
|
100
|
+
newDeviceLabel: zod_1.z.string().nullable(),
|
|
101
|
+
status: exports.devicePairingStatusSchema,
|
|
102
|
+
expiresAt: zod_1.z.string(),
|
|
103
|
+
oldDeviceEphemeralPublicKey: zod_1.z.string().nullable(),
|
|
104
|
+
ciphertext: zod_1.z.string().nullable(),
|
|
105
|
+
nonce: zod_1.z.string().nullable(),
|
|
106
|
+
});
|
|
107
|
+
/* -------------------------------------------------------------------------- */
|
|
108
|
+
/* POST /identity/device-transfer/:pairingId/approve */
|
|
109
|
+
/* -------------------------------------------------------------------------- */
|
|
110
|
+
/**
|
|
111
|
+
* Request body for `POST /identity/device-transfer/:pairingId/approve`
|
|
112
|
+
* (bearer-authenticated AND signature-proven). The `signature` covers
|
|
113
|
+
* `JSON.stringify({ action:'approve_device_transfer', pairingId, timestamp })`
|
|
114
|
+
* made with the caller's CURRENT identity key — dual-proof so a bearer token
|
|
115
|
+
* alone can never exfiltrate the private key.
|
|
116
|
+
*/
|
|
117
|
+
exports.deviceTransferApproveRequestSchema = zod_1.z.object({
|
|
118
|
+
/** The old device's ephemeral secp256k1 public key (single-use). */
|
|
119
|
+
oldEphPub: publicKeyHex,
|
|
120
|
+
/** AEAD ciphertext of `{ privateKey, publicKey }`, hex. */
|
|
121
|
+
ciphertext: ciphertextHex,
|
|
122
|
+
/** AEAD nonce, hex (24 bytes). */
|
|
123
|
+
nonce: nonceHex,
|
|
124
|
+
/** ECDSA (DER, hex) signature proving control of the CURRENT identity key. */
|
|
125
|
+
signature: signatureHex,
|
|
126
|
+
/** Signing timestamp (ms since epoch) — freshness-checked server-side. */
|
|
127
|
+
timestamp: zod_1.z.number().int().positive(),
|
|
128
|
+
});
|
|
129
|
+
exports.deviceTransferApproveResponseSchema = zod_1.z.object({
|
|
130
|
+
success: zod_1.z.boolean(),
|
|
131
|
+
pairingId: zod_1.z.string(),
|
|
132
|
+
status: exports.devicePairingStatusSchema,
|
|
133
|
+
});
|
|
134
|
+
exports.deviceTransferDenyResponseSchema = zod_1.z.object({
|
|
135
|
+
success: zod_1.z.boolean(),
|
|
136
|
+
pairingId: zod_1.z.string(),
|
|
137
|
+
status: exports.devicePairingStatusSchema,
|
|
138
|
+
});
|
|
@@ -32,10 +32,19 @@ exports.deviceSessionSyncSchema = zod_1.z.object({
|
|
|
32
32
|
* possession of the secret IS the proof of device ownership. The server matches
|
|
33
33
|
* `sha256(deviceSecret)` against the device's stored `secretHash` (constant-time)
|
|
34
34
|
* and mints a short access token for the device's active account.
|
|
35
|
+
*
|
|
36
|
+
* `accountId` pins the mint to ONE account of that device instead of whichever
|
|
37
|
+
* account is currently active. It exists for identity-bound clients (Commons),
|
|
38
|
+
* whose authenticated user is determined by a local cryptographic key and must
|
|
39
|
+
* never follow an account switch made by another app on the same device. The
|
|
40
|
+
* account must already be a member of the device session; the mint NEVER
|
|
41
|
+
* mutates `activeAccountId`, so pinning is read-only with respect to the device
|
|
42
|
+
* state every other app observes.
|
|
35
43
|
*/
|
|
36
44
|
exports.deviceTokenMintRequestSchema = zod_1.z.object({
|
|
37
45
|
deviceId: zod_1.z.string().min(1),
|
|
38
46
|
deviceSecret: zod_1.z.string().min(1),
|
|
47
|
+
accountId: zod_1.z.string().min(1).optional(),
|
|
39
48
|
});
|
|
40
49
|
/**
|
|
41
50
|
* Wire shape of a successful `POST /session/device/token`: the freshly-minted
|
package/dist/cjs/index.js
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* expo, no `require()` in the ESM build.
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.
|
|
15
|
-
exports.
|
|
16
|
-
exports.webauthnLoginVerifyRequestSchema = exports.webauthnRegisterVerifyRequestSchema = exports.webauthnLoginOptionsRequestSchema = exports.webauthnRegisterOptionsRequestSchema = exports.updateRolloutPatchSchema = exports.promoteRequestSchema = exports.rollbackToEmbeddedRequestSchema = exports.rollbackRequestSchema = exports.updateListResponseSchema = exports.channelListResponseSchema = exports.channelSchema = exports.rollbackToEmbeddedEntrySchema = exports.createUpdateResponseSchema = exports.updateSchema = void 0;
|
|
14
|
+
exports.validationOpenResultSchema = exports.validationOpenRequestSchema = exports.validationVerdictRecordSchema = exports.realLifeAttestationResultSchema = exports.realLifeAttestationRecordSchema = exports.signedPublicCardSchema = exports.publicCardSchema = exports.logPageResponseSchema = exports.chainHeadResponseSchema = exports.oxySignedRecordTypeSchema = exports.exportBundleSchema = exports.exportAttestationSchema = exports.authMethodsResponseSchema = exports.authMethodEntrySchema = exports.domainVerificationInstructionsSchema = exports.domainVerificationRequestSchema = exports.verifiedDomainSchema = exports.signedRecordEnvelopeSchema = exports.didDocumentSchema = exports.didServiceSchema = exports.verificationMethodSchema = exports.appAffinityEventsIngestSchema = exports.appAffinityEventSchema = exports.appAffinityEventTypeSchema = exports.appUserSignalIngestSchema = exports.appInterestInputSchema = exports.appEndorsementInputSchema = exports.recommendationResponseSchema = exports.recommendationItemSchema = exports.recommendationCountSchema = exports.recommendationRequestSchema = exports.recommendationSignalWeightsSchema = exports.recommendationBoostSchema = exports.recommendationExcludeTypeSchema = exports.sessionStatusSchema = exports.publicApplicationSchema = exports.applicationTypeSchema = exports.safeParseContract = exports.resolveUserId = exports.deviceLinkedSessionsResponseSchema = exports.deviceLinkedSessionSchema = exports.currentUserResponseSchema = exports.userProfileUpdateSchema = exports.userResponseSchema = exports.themePreferenceSchema = exports.userRelationshipSchema = exports.userNameSchema = exports.createAccountRequestSchema = exports.organizationCategorySchema = exports.ORGANIZATION_CATEGORIES = void 0;
|
|
15
|
+
exports.assetCompleteResponseSchema = exports.assetCompleteResultItemSchema = exports.assetCompleteRequestSchema = exports.assetInitResponseSchema = exports.assetUploadTicketSchema = exports.assetInitRequestSchema = exports.assetInitItemSchema = exports.rolloutPercentSchema = exports.runtimeVersionSchema = exports.channelNameSchema = exports.sha256HexSchema = exports.updateAssetStatusSchema = exports.updateStatusSchema = exports.updatePlatformSchema = exports.backupStatusResponseSchema = exports.backupUploadRequestSchema = exports.encryptedBackupEnvelopeSchema = exports.backupLookupIdSchema = exports.rotateKeyCompleteResponseSchema = exports.rotateKeyCompleteRequestSchema = exports.rotateKeyChallengeResponseSchema = exports.loginResultSchema = exports.sessionAccountsChangedEventSchema = exports.sessionAccountsChangedReasonSchema = exports.SESSION_ACCOUNTS_CHANGED_EVENT = exports.deviceHubTicketRedeemResponseSchema = exports.deviceHubTicketRedeemRequestSchema = exports.deviceHubTicketIssueResponseSchema = exports.deviceHubTicketIssueRequestSchema = exports.deviceTokenMintResponseSchema = exports.deviceTokenMintRequestSchema = exports.deviceSessionSyncSchema = exports.activeTokenSchema = exports.deviceSessionStateSchema = exports.sessionAccountSchema = exports.linkPreviewResponseSchema = exports.linkPreviewBatchResponseSchema = exports.linkPreviewBatchRequestSchema = exports.linkPreviewSchema = exports.credentialVerifyResultSchema = exports.credentialListResultSchema = exports.credentialIssueResultSchema = exports.verifiableCredentialResponseSchema = exports.credentialRecordSchema = exports.vouchResultSchema = exports.personhoodStatusResultSchema = exports.personhoodBreakdownSchema = exports.personhoodVouchRecordSchema = exports.validationVoteResultSchema = exports.validationRequestSummarySchema = void 0;
|
|
16
|
+
exports.transparencyCheckpointListSchema = exports.transparencyInclusionProofSchema = exports.transparencyCheckpointSchema = exports.transparencyAnchorSchema = exports.transparencyCheckpointSignatureSchema = exports.deviceTransferDenyResponseSchema = exports.deviceTransferApproveResponseSchema = exports.deviceTransferApproveRequestSchema = exports.deviceTransferInfoResponseSchema = exports.deviceTransferInitResponseSchema = exports.deviceTransferInitRequestSchema = exports.devicePairingStatusSchema = exports.webauthnLoginVerifyRequestSchema = exports.webauthnRegisterVerifyRequestSchema = exports.webauthnLoginOptionsRequestSchema = exports.webauthnRegisterOptionsRequestSchema = exports.updateRolloutPatchSchema = exports.promoteRequestSchema = exports.rollbackToEmbeddedRequestSchema = exports.rollbackRequestSchema = exports.updateListResponseSchema = exports.channelListResponseSchema = exports.channelSchema = exports.rollbackToEmbeddedEntrySchema = exports.createUpdateResponseSchema = exports.updateSchema = exports.createUpdateRequestSchema = exports.updateAssetRefSchema = void 0;
|
|
17
17
|
var accountGraph_1 = require("./accountGraph");
|
|
18
18
|
Object.defineProperty(exports, "ORGANIZATION_CATEGORIES", { enumerable: true, get: function () { return accountGraph_1.ORGANIZATION_CATEGORIES; } });
|
|
19
19
|
Object.defineProperty(exports, "organizationCategorySchema", { enumerable: true, get: function () { return accountGraph_1.organizationCategorySchema; } });
|
|
@@ -21,6 +21,8 @@ Object.defineProperty(exports, "createAccountRequestSchema", { enumerable: true,
|
|
|
21
21
|
var userResponse_1 = require("./userResponse");
|
|
22
22
|
// Schemas
|
|
23
23
|
Object.defineProperty(exports, "userNameSchema", { enumerable: true, get: function () { return userResponse_1.userNameSchema; } });
|
|
24
|
+
Object.defineProperty(exports, "userRelationshipSchema", { enumerable: true, get: function () { return userResponse_1.userRelationshipSchema; } });
|
|
25
|
+
Object.defineProperty(exports, "themePreferenceSchema", { enumerable: true, get: function () { return userResponse_1.themePreferenceSchema; } });
|
|
24
26
|
Object.defineProperty(exports, "userResponseSchema", { enumerable: true, get: function () { return userResponse_1.userResponseSchema; } });
|
|
25
27
|
Object.defineProperty(exports, "userProfileUpdateSchema", { enumerable: true, get: function () { return userResponse_1.userProfileUpdateSchema; } });
|
|
26
28
|
Object.defineProperty(exports, "currentUserResponseSchema", { enumerable: true, get: function () { return userResponse_1.currentUserResponseSchema; } });
|
|
@@ -162,3 +164,19 @@ Object.defineProperty(exports, "webauthnRegisterOptionsRequestSchema", { enumera
|
|
|
162
164
|
Object.defineProperty(exports, "webauthnLoginOptionsRequestSchema", { enumerable: true, get: function () { return webauthn_1.webauthnLoginOptionsRequestSchema; } });
|
|
163
165
|
Object.defineProperty(exports, "webauthnRegisterVerifyRequestSchema", { enumerable: true, get: function () { return webauthn_1.webauthnRegisterVerifyRequestSchema; } });
|
|
164
166
|
Object.defineProperty(exports, "webauthnLoginVerifyRequestSchema", { enumerable: true, get: function () { return webauthn_1.webauthnLoginVerifyRequestSchema; } });
|
|
167
|
+
var devicePairing_1 = require("./devicePairing");
|
|
168
|
+
// Schemas
|
|
169
|
+
Object.defineProperty(exports, "devicePairingStatusSchema", { enumerable: true, get: function () { return devicePairing_1.devicePairingStatusSchema; } });
|
|
170
|
+
Object.defineProperty(exports, "deviceTransferInitRequestSchema", { enumerable: true, get: function () { return devicePairing_1.deviceTransferInitRequestSchema; } });
|
|
171
|
+
Object.defineProperty(exports, "deviceTransferInitResponseSchema", { enumerable: true, get: function () { return devicePairing_1.deviceTransferInitResponseSchema; } });
|
|
172
|
+
Object.defineProperty(exports, "deviceTransferInfoResponseSchema", { enumerable: true, get: function () { return devicePairing_1.deviceTransferInfoResponseSchema; } });
|
|
173
|
+
Object.defineProperty(exports, "deviceTransferApproveRequestSchema", { enumerable: true, get: function () { return devicePairing_1.deviceTransferApproveRequestSchema; } });
|
|
174
|
+
Object.defineProperty(exports, "deviceTransferApproveResponseSchema", { enumerable: true, get: function () { return devicePairing_1.deviceTransferApproveResponseSchema; } });
|
|
175
|
+
Object.defineProperty(exports, "deviceTransferDenyResponseSchema", { enumerable: true, get: function () { return devicePairing_1.deviceTransferDenyResponseSchema; } });
|
|
176
|
+
var transparency_1 = require("./transparency");
|
|
177
|
+
// Schemas — transparency log (checkpoints + inclusion proofs)
|
|
178
|
+
Object.defineProperty(exports, "transparencyCheckpointSignatureSchema", { enumerable: true, get: function () { return transparency_1.transparencyCheckpointSignatureSchema; } });
|
|
179
|
+
Object.defineProperty(exports, "transparencyAnchorSchema", { enumerable: true, get: function () { return transparency_1.transparencyAnchorSchema; } });
|
|
180
|
+
Object.defineProperty(exports, "transparencyCheckpointSchema", { enumerable: true, get: function () { return transparency_1.transparencyCheckpointSchema; } });
|
|
181
|
+
Object.defineProperty(exports, "transparencyInclusionProofSchema", { enumerable: true, get: function () { return transparency_1.transparencyInclusionProofSchema; } });
|
|
182
|
+
Object.defineProperty(exports, "transparencyCheckpointListSchema", { enumerable: true, get: function () { return transparency_1.transparencyCheckpointListSchema; } });
|
|
@@ -93,6 +93,14 @@ exports.publicApplicationSchema = zod_1.z.object({
|
|
|
93
93
|
* current producer and are never `null`, but stay `.optional()` so the contract
|
|
94
94
|
* tolerates leaner shapes from other producers of this same payload without a
|
|
95
95
|
* coordinated bump.
|
|
96
|
+
*
|
|
97
|
+
* `pushSentAt` / `openedAt` are DELIVERY PROGRESS, not authorization state: they
|
|
98
|
+
* let a waiting surface render "Check Commons on your phone" → "Opened in
|
|
99
|
+
* Commons" without inventing competing statuses. `status` remains the only
|
|
100
|
+
* authority on whether the request is pending, authorized, cancelled or expired.
|
|
101
|
+
* Both are `.nullable().optional()` for the same reason as `sessionId` — the
|
|
102
|
+
* producer always emits the key with `null` until that step happens, and an
|
|
103
|
+
* older API that omits them entirely must degrade, not fail the parse.
|
|
96
104
|
*/
|
|
97
105
|
exports.sessionStatusSchema = zod_1.z.object({
|
|
98
106
|
status: zod_1.z.string(),
|
|
@@ -103,4 +111,11 @@ exports.sessionStatusSchema = zod_1.z.object({
|
|
|
103
111
|
sessionId: zod_1.z.string().nullable().optional(),
|
|
104
112
|
publicKey: zod_1.z.string().nullable().optional(),
|
|
105
113
|
userId: zod_1.z.string().nullable().optional(),
|
|
114
|
+
/**
|
|
115
|
+
* What approving this request does. Legacy rows read as `device_sign_in`.
|
|
116
|
+
* OAuth-bound sessions finalize into an authorization code (no `sessionId`).
|
|
117
|
+
*/
|
|
118
|
+
purpose: zod_1.z.enum(['device_sign_in', 'oauth_authorization']).optional(),
|
|
119
|
+
pushSentAt: zod_1.z.string().nullable().optional(),
|
|
120
|
+
openedAt: zod_1.z.string().nullable().optional(),
|
|
106
121
|
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transparencyCheckpointListSchema = exports.transparencyInclusionProofSchema = exports.transparencyCheckpointSchema = exports.transparencyAnchorSchema = exports.transparencyCheckpointSignatureSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
/**
|
|
6
|
+
* Transparency log — the public wire contract of the checkpoint surface.
|
|
7
|
+
*
|
|
8
|
+
* A checkpoint is the operator's signed commitment to EVERY subject's chain head
|
|
9
|
+
* at a point in time: "at `periodEnd` I committed to `root` over `treeSize`
|
|
10
|
+
* subjects, and the previous checkpoint hashed to `prevCheckpointHash`". Anyone
|
|
11
|
+
* can then ask for an inclusion proof of their own head and verify it against
|
|
12
|
+
* that root without trusting the server — which is what closes the one gap a
|
|
13
|
+
* per-subject hash chain cannot close on its own (the server serving two
|
|
14
|
+
* different histories, or quietly dropping a record).
|
|
15
|
+
*
|
|
16
|
+
* The Merkle math, the leaf/checkpoint signing bytes, and the proof verifier all
|
|
17
|
+
* live in `@oxyhq/protocol` (`src/transparency/`); this module only fixes the
|
|
18
|
+
* SHAPES that cross the wire, so a client and the API cannot drift on them.
|
|
19
|
+
*
|
|
20
|
+
* Digest fields are pinned to 64-char LOWERCASE hex on purpose: the digests are
|
|
21
|
+
* compared as strings against locally recomputed hashes, so accepting an
|
|
22
|
+
* upper-case or truncated variant would turn a real mismatch into a confusing
|
|
23
|
+
* verification failure at a distance.
|
|
24
|
+
*/
|
|
25
|
+
/** A SHA-256 digest in the exact form the protocol emits: 64 lowercase hex chars. */
|
|
26
|
+
const hexDigestSchema = zod_1.z.string().regex(/^[0-9a-f]{64}$/, 'Expected a 64-char lowercase hex digest');
|
|
27
|
+
/**
|
|
28
|
+
* One signer's endorsement of a checkpoint's signed fields.
|
|
29
|
+
*
|
|
30
|
+
* The operator and every independent witness produce this same shape over the
|
|
31
|
+
* SAME bytes, so the array on a checkpoint can grow without coordination.
|
|
32
|
+
*/
|
|
33
|
+
exports.transparencyCheckpointSignatureSchema = zod_1.z.object({
|
|
34
|
+
/** Uncompressed hex public key of the signer. */
|
|
35
|
+
publicKey: zod_1.z.string().min(1),
|
|
36
|
+
alg: zod_1.z.literal('ES256K-DER-SHA256'),
|
|
37
|
+
/** DER-encoded hex secp256k1 signature over the checkpoint signing input. */
|
|
38
|
+
signature: zod_1.z.string().min(1),
|
|
39
|
+
});
|
|
40
|
+
/** Where a checkpoint root was published on a public chain. */
|
|
41
|
+
exports.transparencyAnchorSchema = zod_1.z.object({
|
|
42
|
+
/** Chain/network identifier, e.g. `faircoin-main`. */
|
|
43
|
+
network: zod_1.z.string().min(1),
|
|
44
|
+
txid: zod_1.z.string().min(1),
|
|
45
|
+
confirmations: zod_1.z.number().int().nonnegative(),
|
|
46
|
+
/** When the anchoring transaction was broadcast (ms epoch). */
|
|
47
|
+
anchoredAt: zod_1.z.number().int().positive(),
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* A published checkpoint.
|
|
51
|
+
*
|
|
52
|
+
* `signatures` is non-empty by contract: an unsigned root commits nobody and
|
|
53
|
+
* must never be served as a checkpoint. `anchors` may be empty — a checkpoint is
|
|
54
|
+
* published immediately and anchored asynchronously, so "not yet anchored" is a
|
|
55
|
+
* normal, temporary state rather than an error.
|
|
56
|
+
*/
|
|
57
|
+
exports.transparencyCheckpointSchema = zod_1.z.object({
|
|
58
|
+
index: zod_1.z.number().int().nonnegative(),
|
|
59
|
+
/** End of the committed period (ms epoch). */
|
|
60
|
+
periodEnd: zod_1.z.number().int().positive(),
|
|
61
|
+
/** Number of subjects (leaves) committed. */
|
|
62
|
+
treeSize: zod_1.z.number().int().nonnegative(),
|
|
63
|
+
root: hexDigestSchema,
|
|
64
|
+
/** Hash of the previous checkpoint; `null` only at genesis. */
|
|
65
|
+
prevCheckpointHash: hexDigestSchema.nullable(),
|
|
66
|
+
signatures: zod_1.z.array(exports.transparencyCheckpointSignatureSchema).min(1),
|
|
67
|
+
anchors: zod_1.z.array(exports.transparencyAnchorSchema),
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* An inclusion proof for one subject against one checkpoint.
|
|
71
|
+
*
|
|
72
|
+
* Carries the leaf PREIMAGE (`subjectDid`, `seq`, `headRecordId`) as well as the
|
|
73
|
+
* `leaf` digest so the verifier re-derives the leaf itself rather than trusting
|
|
74
|
+
* the server's hash, then walks `proof` up to the checkpoint's `root`.
|
|
75
|
+
*/
|
|
76
|
+
exports.transparencyInclusionProofSchema = zod_1.z.object({
|
|
77
|
+
checkpoint: exports.transparencyCheckpointSchema,
|
|
78
|
+
subjectDid: zod_1.z.string().min(1),
|
|
79
|
+
seq: zod_1.z.number().int().nonnegative(),
|
|
80
|
+
headRecordId: hexDigestSchema,
|
|
81
|
+
leaf: hexDigestSchema,
|
|
82
|
+
leafIndex: zod_1.z.number().int().nonnegative(),
|
|
83
|
+
/** Audit path, leaf-adjacent sibling first; empty for a single-leaf tree. */
|
|
84
|
+
proof: zod_1.z.array(hexDigestSchema),
|
|
85
|
+
});
|
|
86
|
+
/** A page of the checkpoint chain, oldest first, for walking `prevCheckpointHash`. */
|
|
87
|
+
exports.transparencyCheckpointListSchema = zod_1.z.object({
|
|
88
|
+
checkpoints: zod_1.z.array(exports.transparencyCheckpointSchema),
|
|
89
|
+
});
|
package/dist/cjs/userResponse.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* `require()`).
|
|
31
31
|
*/
|
|
32
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.deviceLinkedSessionsResponseSchema = exports.deviceLinkedSessionSchema = exports.currentUserResponseSchema = exports.userProfileUpdateSchema = exports.userResponseSchema = exports.userNameSchema = void 0;
|
|
33
|
+
exports.deviceLinkedSessionsResponseSchema = exports.deviceLinkedSessionSchema = exports.currentUserResponseSchema = exports.userProfileUpdateSchema = exports.userResponseSchema = exports.themePreferenceSchema = exports.userRelationshipSchema = exports.userNameSchema = void 0;
|
|
34
34
|
exports.resolveUserId = resolveUserId;
|
|
35
35
|
exports.safeParseContract = safeParseContract;
|
|
36
36
|
const zod_1 = require("zod");
|
|
@@ -44,6 +44,14 @@ exports.userNameSchema = zod_1.z
|
|
|
44
44
|
displayName: zod_1.z.string().optional(),
|
|
45
45
|
})
|
|
46
46
|
.passthrough();
|
|
47
|
+
exports.userRelationshipSchema = zod_1.z.object({
|
|
48
|
+
isFollowing: zod_1.z.boolean(),
|
|
49
|
+
followsYou: zod_1.z.boolean(),
|
|
50
|
+
});
|
|
51
|
+
exports.themePreferenceSchema = zod_1.z.object({
|
|
52
|
+
mode: zod_1.z.enum(['light', 'dark', 'system']),
|
|
53
|
+
colorPreset: zod_1.z.string(),
|
|
54
|
+
});
|
|
47
55
|
/**
|
|
48
56
|
* The canonical user object emitted by `formatUserResponse`.
|
|
49
57
|
*
|
|
@@ -102,6 +110,19 @@ exports.userResponseSchema = zod_1.z
|
|
|
102
110
|
* Absent on personal, project, and bot accounts.
|
|
103
111
|
*/
|
|
104
112
|
organizationCategory: accountGraph_1.organizationCategorySchema.optional(),
|
|
113
|
+
/**
|
|
114
|
+
* The authenticated viewer's relationship to this profile. Present ONLY
|
|
115
|
+
* on single-profile fetches (`GET /profiles/username/:username`,
|
|
116
|
+
* `GET /users/:userId`) when the request is authenticated; OMITTED for
|
|
117
|
+
* anonymous requests and for the bulk `POST /users/by-ids` fan-out.
|
|
118
|
+
*/
|
|
119
|
+
relationship: exports.userRelationshipSchema.optional(),
|
|
120
|
+
/**
|
|
121
|
+
* Portable theme preference. Rides the self/session payload (cold boot),
|
|
122
|
+
* so it is present on the current-user DTO (`GET /users/me`,
|
|
123
|
+
* `GET /session/user/:sessionId`) and absent until the user sets it.
|
|
124
|
+
*/
|
|
125
|
+
themePreference: exports.themePreferenceSchema.optional(),
|
|
105
126
|
})
|
|
106
127
|
.passthrough();
|
|
107
128
|
exports.userProfileUpdateSchema = zod_1.z
|
|
@@ -142,6 +163,11 @@ exports.userProfileUpdateSchema = zod_1.z
|
|
|
142
163
|
notificationPreferences: zod_1.z.record(zod_1.z.unknown()).optional(),
|
|
143
164
|
userPreferences: zod_1.z.record(zod_1.z.unknown()).optional(),
|
|
144
165
|
privacySettings: zod_1.z.record(zod_1.z.unknown()).optional(),
|
|
166
|
+
/**
|
|
167
|
+
* Portable theme preference. Written through the same `PUT /users/me`
|
|
168
|
+
* settings-update path as `languages`/`userPreferences`.
|
|
169
|
+
*/
|
|
170
|
+
themePreference: exports.themePreferenceSchema.optional(),
|
|
145
171
|
})
|
|
146
172
|
.passthrough();
|
|
147
173
|
/**
|