@oxy.so/contracts 1.2.0 → 1.3.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/identity.js +3 -2
- package/dist/cjs/identityMove.js +111 -40
- package/dist/cjs/identityProof.js +164 -0
- package/dist/cjs/identityRecovery.js +51 -0
- package/dist/cjs/index.js +50 -21
- package/dist/cjs/inference/identifiers.js +3 -1
- package/dist/cjs/inference/providerConnection.js +1 -1
- package/dist/cjs/inference/request.js +15 -1
- package/dist/cjs/inference/streamEvents.js +24 -2
- package/dist/cjs/username.js +70 -2
- package/dist/cjs/webIdentityCarrier.js +112 -53
- package/dist/cjs/webauthn.js +14 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/identity.js +3 -2
- package/dist/esm/identityMove.js +105 -39
- package/dist/esm/identityProof.js +159 -0
- package/dist/esm/identityRecovery.js +48 -0
- package/dist/esm/index.js +11 -8
- package/dist/esm/inference/identifiers.js +2 -0
- package/dist/esm/inference/providerConnection.js +2 -2
- package/dist/esm/inference/request.js +14 -0
- package/dist/esm/inference/streamEvents.js +24 -2
- package/dist/esm/username.js +69 -1
- package/dist/esm/webIdentityCarrier.js +111 -52
- package/dist/esm/webauthn.js +14 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/accountGraph.d.ts +7 -7
- package/dist/types/agency.d.ts +24 -24
- package/dist/types/browserHub.d.ts +16 -16
- package/dist/types/deviceDirectory.d.ts +28 -28
- package/dist/types/externalIdentity.d.ts +7 -7
- package/dist/types/identity.d.ts +3 -2
- package/dist/types/identityMove.d.ts +119 -43
- package/dist/types/identityProof.d.ts +156 -0
- package/dist/types/identityRecovery.d.ts +246 -0
- package/dist/types/index.d.ts +13 -11
- package/dist/types/inference/identifiers.d.ts +2 -0
- package/dist/types/inference/providerConnection.d.ts +40 -40
- package/dist/types/inference/request.d.ts +84 -36
- package/dist/types/inference/streamEvents.d.ts +57 -1
- package/dist/types/keyRotation.d.ts +2 -2
- package/dist/types/oauth.d.ts +30 -30
- package/dist/types/sessionStatus.d.ts +6 -6
- package/dist/types/transparency.d.ts +10 -10
- package/dist/types/userResponse.d.ts +18 -18
- package/dist/types/username.d.ts +25 -2
- package/dist/types/webIdentityCarrier.d.ts +767 -159
- package/dist/types/webauthn.d.ts +208 -0
- package/package.json +1 -1
- package/dist/cjs/devicePairing.js +0 -138
- package/dist/esm/devicePairing.js +0 -135
- package/dist/types/devicePairing.d.ts +0 -130
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.webIdentityEnvelopeEstablishSchema = exports.webIdentityEnvelopePutSchema = exports.
|
|
3
|
+
exports.webIdentityEnvelopeEstablishSchema = exports.webauthnAssertionResponseSchema = exports.webIdentityEnvelopePutSchema = exports.webIdentityEnvelopeActionSchema = exports.webIdentityEnvelopeProofFieldsSchema = exports.webIdentityEnvelopeResponseSchema = exports.webIdentityHolderSchema = exports.webIdentityEnvelopeUploadSchema = exports.webIdentityEnvelopeSchema = exports.webIdentityWrapSchema = exports.webauthnRpIdSchema = exports.webauthnCredentialIdSchema = exports.webIdentityPublicKeySchema = exports.WEB_IDENTITY_SECRET_KINDS = exports.WEB_IDENTITY_ENVELOPE_VERSION = void 0;
|
|
4
4
|
/**
|
|
5
|
-
* Web identity
|
|
5
|
+
* Web identity holder contract — the sealed envelope that lets a browser hold an
|
|
6
|
+
* account's self-custody root without Oxy ever being able to use it (ADR 0024).
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* A root is a BIP-39 phrase (12–24 words) whose seed's first 32 bytes are the
|
|
9
|
+
* secp256k1 key — exactly the Commons derivation — or, for a few imported
|
|
10
|
+
* identities, a raw private key that never had a phrase. On the web it travels as:
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Commons identity are the same thing. On the web it travels as:
|
|
14
|
-
*
|
|
15
|
-
* entropy (16 bytes) ── XChaCha20-Poly1305 under a random DEK ──▶ sealedEntropy
|
|
16
|
-
* DEK ── XChaCha20-Poly1305 under KEK_i ──▶ wraps[i]
|
|
12
|
+
* secret ── XChaCha20-Poly1305 under a random DEK ──▶ sealedSecret
|
|
13
|
+
* DEK ── XChaCha20-Poly1305 under KEK_i ──▶ wraps[i]
|
|
17
14
|
* KEK_i = HKDF(PRF output of passkey i)
|
|
18
15
|
*
|
|
19
16
|
* The server stores the envelope and can open NONE of it: the PRF output never
|
|
20
|
-
* leaves the user's authenticator, and the
|
|
21
|
-
* associated data binds
|
|
22
|
-
* wrap to its credential
|
|
23
|
-
* open instead of decrypting into the wrong identity.
|
|
17
|
+
* leaves the user's authenticator, and the secret is never uploaded. The AEAD
|
|
18
|
+
* associated data binds the secret to the root's public key and kind, and each
|
|
19
|
+
* wrap to its credential and RP ID, so a re-labelled or transplanted envelope
|
|
20
|
+
* fails to open instead of decrypting into the wrong identity.
|
|
24
21
|
*
|
|
25
|
-
*
|
|
26
|
-
* ESM-safe (no `require()`).
|
|
22
|
+
* Platform-agnostic — zod only, ESM-safe (no `require()`).
|
|
27
23
|
*/
|
|
28
24
|
const zod_1 = require("zod");
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
const identityProof_1 = require("./identityProof");
|
|
26
|
+
/** The envelope scheme. A scheme change is a new literal, never a mutation. */
|
|
27
|
+
exports.WEB_IDENTITY_ENVELOPE_VERSION = 2;
|
|
28
|
+
/**
|
|
29
|
+
* What an envelope seals. A raw-key identity stays a raw-key identity:
|
|
30
|
+
* nothing ever derives or displays a phrase for it.
|
|
31
|
+
*/
|
|
32
|
+
exports.WEB_IDENTITY_SECRET_KINDS = ['mnemonic-entropy', 'raw-private-key'];
|
|
31
33
|
const hex = (bytes, label) => zod_1.z
|
|
32
34
|
.string()
|
|
33
35
|
.trim()
|
|
@@ -48,6 +50,13 @@ exports.webauthnCredentialIdSchema = zod_1.z
|
|
|
48
50
|
.min(16)
|
|
49
51
|
.max(1024)
|
|
50
52
|
.regex(/^[A-Za-z0-9_-]+$/, 'credentialId must be base64url');
|
|
53
|
+
/** A WebAuthn RP ID: a bare registrable host name, lowercase. */
|
|
54
|
+
exports.webauthnRpIdSchema = zod_1.z
|
|
55
|
+
.string()
|
|
56
|
+
.trim()
|
|
57
|
+
.min(1)
|
|
58
|
+
.max(253)
|
|
59
|
+
.regex(/^(localhost|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+)$/, 'rpId must be a lowercase host name');
|
|
51
60
|
/** One passkey's wrap of the envelope's data key. */
|
|
52
61
|
exports.webIdentityWrapSchema = zod_1.z.object({
|
|
53
62
|
credentialId: exports.webauthnCredentialIdSchema,
|
|
@@ -56,6 +65,13 @@ exports.webIdentityWrapSchema = zod_1.z.object({
|
|
|
56
65
|
/** The 32-byte DEK sealed under this passkey's KEK, with the 16-byte tag appended (48 bytes). */
|
|
57
66
|
wrappedKey: hex(48, 'wrappedKey'),
|
|
58
67
|
createdAt: zod_1.z.string().datetime(),
|
|
68
|
+
/** The RP ID the passkey was created under, asserted explicitly by every later ceremony (ADR 0024 D2). */
|
|
69
|
+
rpId: exports.webauthnRpIdSchema,
|
|
70
|
+
/**
|
|
71
|
+
* When this passkey's PRF output was shown to open the envelope. A wrap is a
|
|
72
|
+
* root HOLDER only once this is set; a login passkey never is by default.
|
|
73
|
+
*/
|
|
74
|
+
verifiedAt: zod_1.z.string().datetime().optional(),
|
|
59
75
|
});
|
|
60
76
|
/**
|
|
61
77
|
* The sealed identity as it is stored (server copy and local copy alike).
|
|
@@ -63,15 +79,27 @@ exports.webIdentityWrapSchema = zod_1.z.object({
|
|
|
63
79
|
* `wraps` holds one entry per passkey able to open it; at least one, and a
|
|
64
80
|
* bounded number so an envelope cannot grow without limit.
|
|
65
81
|
*/
|
|
66
|
-
exports.webIdentityEnvelopeSchema = zod_1.z
|
|
82
|
+
exports.webIdentityEnvelopeSchema = zod_1.z
|
|
83
|
+
.object({
|
|
67
84
|
version: zod_1.z.literal(exports.WEB_IDENTITY_ENVELOPE_VERSION),
|
|
68
85
|
algorithm: zod_1.z.literal('xchacha20poly1305'),
|
|
69
86
|
publicKey: exports.webIdentityPublicKeySchema,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
secretKind: zod_1.z.enum(exports.WEB_IDENTITY_SECRET_KINDS),
|
|
88
|
+
/** 24-byte nonce of the secret seal. */
|
|
89
|
+
secretNonce: hex(24, 'secretNonce'),
|
|
90
|
+
/**
|
|
91
|
+
* The sealed secret, tag appended: 16/20/24/28/32 bytes of BIP-39 entropy
|
|
92
|
+
* (12–24 words) or a 32-byte private key, plus 16.
|
|
93
|
+
*/
|
|
94
|
+
sealedSecret: zod_1.z
|
|
95
|
+
.string()
|
|
96
|
+
.trim()
|
|
97
|
+
.regex(/^(?:[0-9a-fA-F]{64}|[0-9a-fA-F]{72}|[0-9a-fA-F]{80}|[0-9a-fA-F]{88}|[0-9a-fA-F]{96})$/, 'sealedSecret has an unsupported length'),
|
|
74
98
|
wraps: zod_1.z.array(exports.webIdentityWrapSchema).min(1).max(10),
|
|
99
|
+
})
|
|
100
|
+
.refine((envelope) => envelope.secretKind === 'mnemonic-entropy' || envelope.sealedSecret.length === 96, {
|
|
101
|
+
message: 'a raw private key seals to 48 bytes',
|
|
102
|
+
path: ['sealedSecret'],
|
|
75
103
|
});
|
|
76
104
|
/**
|
|
77
105
|
* `PUT /identity/web-envelope` — store or replace the caller's envelope.
|
|
@@ -82,41 +110,72 @@ exports.webIdentityEnvelopeSchema = zod_1.z.object({
|
|
|
82
110
|
exports.webIdentityEnvelopeUploadSchema = zod_1.z.object({
|
|
83
111
|
envelope: exports.webIdentityEnvelopeSchema,
|
|
84
112
|
});
|
|
85
|
-
/**
|
|
113
|
+
/** A root holder as the status read reports it — metadata only, nothing that opens anything. */
|
|
114
|
+
exports.webIdentityHolderSchema = zod_1.z.object({
|
|
115
|
+
credentialId: exports.webauthnCredentialIdSchema,
|
|
116
|
+
rpId: exports.webauthnRpIdSchema,
|
|
117
|
+
verifiedAt: zod_1.z.string().datetime().nullable(),
|
|
118
|
+
createdAt: zod_1.z.string().datetime(),
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* `GET /identity/web-envelope` — the caller's envelope and the readiness facts
|
|
122
|
+
* ADR 0024 D5 keeps separate. A client decides what to show from these fields
|
|
123
|
+
* WITHOUT decrypting anything.
|
|
124
|
+
*/
|
|
86
125
|
exports.webIdentityEnvelopeResponseSchema = zod_1.z.object({
|
|
87
126
|
envelope: exports.webIdentityEnvelopeSchema.nullable(),
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
127
|
+
/** The revision a write must name as `expectedRevision`; `0` when there is no envelope. */
|
|
128
|
+
revision: zod_1.z.number().int().nonnegative(),
|
|
129
|
+
/** Whether the account has a linked root at all (it may live only in Commons). */
|
|
130
|
+
rootLinked: zod_1.z.boolean(),
|
|
131
|
+
/** The web wraps, as metadata. */
|
|
132
|
+
holders: zod_1.z.array(exports.webIdentityHolderSchema),
|
|
133
|
+
/** When the owner confirmed the recovery material is written down, or `null`. */
|
|
93
134
|
phraseConfirmedAt: zod_1.z.string().datetime().nullable(),
|
|
135
|
+
/** When the recovery material was shown to re-derive this root, or `null`. */
|
|
136
|
+
recoveryVerifiedAt: zod_1.z.string().datetime().nullable(),
|
|
94
137
|
updatedAt: zod_1.z.string().datetime().nullable(),
|
|
95
138
|
});
|
|
139
|
+
/** A root proof, plus the envelope revision the write expects to replace. */
|
|
140
|
+
exports.webIdentityEnvelopeProofFieldsSchema = zod_1.z.object({
|
|
141
|
+
proof: identityProof_1.identityProofSchema,
|
|
142
|
+
expectedRevision: zod_1.z.number().int().nonnegative(),
|
|
143
|
+
});
|
|
96
144
|
/**
|
|
97
|
-
* `POST /identity/web-envelope/phrase-confirmed` and
|
|
98
|
-
* `DELETE /identity/web-envelope`
|
|
99
|
-
* just a bearer: a stolen session must not be able to mark a phrase as saved or
|
|
100
|
-
* destroy the web copy of someone's identity.
|
|
101
|
-
*
|
|
102
|
-
* The signed message is `JSON.stringify({ action, userId, timestamp })` — the
|
|
103
|
-
* same scheme as `link_identity`.
|
|
145
|
+
* `POST /identity/web-envelope/phrase-confirmed`, `/recovery-verified` and
|
|
146
|
+
* `DELETE /identity/web-envelope` prove control of the root, not just a bearer.
|
|
104
147
|
*/
|
|
105
|
-
exports.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
});
|
|
109
|
-
/** `PUT /identity/web-envelope` body: the envelope plus a `web_envelope_put` identity-key proof. */
|
|
110
|
-
exports.webIdentityEnvelopePutSchema = exports.webIdentityEnvelopeUploadSchema.extend(exports.webIdentityEnvelopeProofSchema.shape);
|
|
148
|
+
exports.webIdentityEnvelopeActionSchema = exports.webIdentityEnvelopeProofFieldsSchema.strict();
|
|
149
|
+
/** `PUT /identity/web-envelope` body. */
|
|
150
|
+
exports.webIdentityEnvelopePutSchema = exports.webIdentityEnvelopeUploadSchema.extend(exports.webIdentityEnvelopeProofFieldsSchema.shape).strict();
|
|
111
151
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* Linking and storing as two calls would let a failure (or a closed tab) in
|
|
116
|
-
* between leave the account bound to a key that nothing carries — an identity
|
|
117
|
-
* lost at birth. `link` is a `link_identity` proof and the outer proof a
|
|
118
|
-
* `web_envelope_put` proof, both signed by the envelope's own key.
|
|
152
|
+
* A WebAuthn assertion by one of the account's EXISTING passkeys whose
|
|
153
|
+
* `clientDataJSON.challenge` is the proof challenge — the fresh use of the
|
|
154
|
+
* existing factor a keyless account needs before its first root is linked.
|
|
119
155
|
*/
|
|
120
|
-
exports.
|
|
121
|
-
|
|
122
|
-
|
|
156
|
+
exports.webauthnAssertionResponseSchema = zod_1.z
|
|
157
|
+
.object({
|
|
158
|
+
id: exports.webauthnCredentialIdSchema,
|
|
159
|
+
rawId: zod_1.z.string().min(1).max(2048),
|
|
160
|
+
type: zod_1.z.literal('public-key'),
|
|
161
|
+
response: zod_1.z
|
|
162
|
+
.object({
|
|
163
|
+
clientDataJSON: zod_1.z.string().min(1).max(8192),
|
|
164
|
+
authenticatorData: zod_1.z.string().min(1).max(8192),
|
|
165
|
+
signature: zod_1.z.string().min(1).max(2048),
|
|
166
|
+
userHandle: zod_1.z.string().max(2048).optional(),
|
|
167
|
+
})
|
|
168
|
+
.passthrough(),
|
|
169
|
+
clientExtensionResults: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
170
|
+
authenticatorAttachment: zod_1.z.string().optional(),
|
|
171
|
+
})
|
|
172
|
+
.passthrough();
|
|
173
|
+
/**
|
|
174
|
+
* `POST /identity/web-envelope/establish` body — an account's FIRST root, linked
|
|
175
|
+
* and stored with its envelope in ONE transaction: one root proof
|
|
176
|
+
* (`web_envelope_establish`, digest of the envelope) plus a fresh `assertion` by
|
|
177
|
+
* an existing passkey over the same challenge.
|
|
178
|
+
*/
|
|
179
|
+
exports.webIdentityEnvelopeEstablishSchema = exports.webIdentityEnvelopeUploadSchema
|
|
180
|
+
.extend({ proof: identityProof_1.identityProofSchema, assertion: exports.webauthnAssertionResponseSchema })
|
|
181
|
+
.strict();
|
package/dist/cjs/webauthn.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.webauthnLoginVerifyRequestSchema = exports.webauthnRegisterVerifyRequestSchema = exports.webauthnLoginOptionsRequestSchema = exports.webauthnRegisterOptionsRequestSchema = void 0;
|
|
16
16
|
const zod_1 = require("zod");
|
|
17
|
+
const identityProof_1 = require("./identityProof");
|
|
18
|
+
const webIdentityCarrier_1 = require("./webIdentityCarrier");
|
|
17
19
|
/**
|
|
18
20
|
* Device-session options shared by every first-party sign-in body
|
|
19
21
|
* (`deviceName`/`deviceFingerprint`). Mirrors what
|
|
@@ -59,6 +61,18 @@ exports.webauthnLoginOptionsRequestSchema = zod_1.z.object({
|
|
|
59
61
|
*/
|
|
60
62
|
exports.webauthnRegisterVerifyRequestSchema = zod_1.z.object({
|
|
61
63
|
username: zod_1.z.string().trim().min(1).max(60).optional(),
|
|
64
|
+
/**
|
|
65
|
+
* Sign-up only (ADR 0024 D4): the account's root, created on the holder BEFORE
|
|
66
|
+
* this request — sealed under the passkey being registered — and a root proof
|
|
67
|
+
* (`enroll_identity`) whose challenge is the registration challenge. The
|
|
68
|
+
* account, passkey, root and envelope are then created in one transaction.
|
|
69
|
+
*/
|
|
70
|
+
identity: zod_1.z
|
|
71
|
+
.object({
|
|
72
|
+
envelope: webIdentityCarrier_1.webIdentityEnvelopeSchema,
|
|
73
|
+
proof: identityProof_1.identityProofSchema,
|
|
74
|
+
})
|
|
75
|
+
.optional(),
|
|
62
76
|
...deviceSessionEnvelope,
|
|
63
77
|
});
|
|
64
78
|
/**
|