@oxy.so/contracts 2.1.0 → 3.0.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.
Files changed (57) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/accountEmail.js +79 -0
  3. package/dist/cjs/deviceBoot.js +2 -2
  4. package/dist/cjs/deviceSession.js +73 -16
  5. package/dist/cjs/identity.js +1 -3
  6. package/dist/cjs/identityLink.js +92 -0
  7. package/dist/cjs/identityProof.js +6 -29
  8. package/dist/cjs/index.js +82 -65
  9. package/dist/cjs/reputation.js +10 -55
  10. package/dist/cjs/signIn.js +304 -0
  11. package/dist/esm/.tsbuildinfo +1 -1
  12. package/dist/esm/accountEmail.js +76 -0
  13. package/dist/esm/deviceBoot.js +2 -2
  14. package/dist/esm/deviceSession.js +72 -15
  15. package/dist/esm/identity.js +1 -3
  16. package/dist/esm/identityLink.js +87 -0
  17. package/dist/esm/identityProof.js +6 -29
  18. package/dist/esm/index.js +13 -16
  19. package/dist/esm/reputation.js +9 -54
  20. package/dist/esm/signIn.js +299 -0
  21. package/dist/types/.tsbuildinfo +1 -1
  22. package/dist/types/accountEmail.d.ts +90 -0
  23. package/dist/types/agency.d.ts +2 -2
  24. package/dist/types/deviceBoot.d.ts +2 -2
  25. package/dist/types/deviceSession.d.ts +131 -19
  26. package/dist/types/externalIdentity.d.ts +4 -4
  27. package/dist/types/identity.d.ts +4 -8
  28. package/dist/types/identityLink.d.ts +146 -0
  29. package/dist/types/identityProof.d.ts +15 -42
  30. package/dist/types/index.d.ts +10 -12
  31. package/dist/types/inference/accountBilling.d.ts +2 -2
  32. package/dist/types/inference/embeddings.d.ts +16 -16
  33. package/dist/types/inference/entitlement.d.ts +2 -2
  34. package/dist/types/inference/errors.d.ts +8 -8
  35. package/dist/types/inference/inbox.d.ts +8 -8
  36. package/dist/types/inference/providerConnection.d.ts +20 -20
  37. package/dist/types/inference/request.d.ts +16 -16
  38. package/dist/types/inference/streamEvents.d.ts +20 -20
  39. package/dist/types/keyRecovery.d.ts +4 -4
  40. package/dist/types/oauth.d.ts +16 -16
  41. package/dist/types/reputation.d.ts +32 -127
  42. package/dist/types/sessionStatus.d.ts +2 -2
  43. package/dist/types/signIn.d.ts +717 -0
  44. package/dist/types/userResponse.d.ts +2 -2
  45. package/package.json +1 -1
  46. package/dist/cjs/identityMove.js +0 -156
  47. package/dist/cjs/identityRecovery.js +0 -51
  48. package/dist/cjs/webIdentityCarrier.js +0 -181
  49. package/dist/cjs/webauthn.js +0 -85
  50. package/dist/esm/identityMove.js +0 -148
  51. package/dist/esm/identityRecovery.js +0 -48
  52. package/dist/esm/webIdentityCarrier.js +0 -178
  53. package/dist/esm/webauthn.js +0 -82
  54. package/dist/types/identityMove.d.ts +0 -185
  55. package/dist/types/identityRecovery.d.ts +0 -246
  56. package/dist/types/webIdentityCarrier.d.ts +0 -1130
  57. package/dist/types/webauthn.d.ts +0 -285
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Email codes and tickets (ADR 0030).
3
+ *
4
+ * A web account is a username and an email; a password and an authenticator
5
+ * app are optional and added later. The email is proven by a 6-digit code sent
6
+ * to it, and the proof is a short-lived one-use ticket the next step spends:
7
+ *
8
+ * - `signup`: the ticket lets `POST /auth/signup` create the account with that
9
+ * email. `start` answers the same whether or not the address already has an
10
+ * account, so it never tells anyone which emails have an Oxy account.
11
+ *
12
+ * Signing in by email (`signin`) and re-verifying before a sensitive change
13
+ * (`reauth`) use the same code, through their own routes (`signIn.ts`).
14
+ */
15
+ import { z } from 'zod';
16
+ /**
17
+ * - `signup`: above.
18
+ * - `signin`: the code (and link) of an email sign-in (`POST /auth/signin/email/start`).
19
+ * - `reauth`: a signed-in person proving it is them before a sensitive step
20
+ * (`POST /users/me/reauth/email`): a password, an authenticator, deleting the
21
+ * account, linking Commons.
22
+ */
23
+ export declare const EMAIL_VERIFICATION_PURPOSES: readonly ["signup", "signin", "reauth"];
24
+ export type EmailVerificationPurpose = (typeof EMAIL_VERIFICATION_PURPOSES)[number];
25
+ /** Digits in a code. */
26
+ export declare const EMAIL_CODE_LENGTH = 6;
27
+ /** How long a code can be confirmed. */
28
+ export declare const EMAIL_CODE_TTL_MS: number;
29
+ /** Wrong codes before a verification is spent and a new code is needed. */
30
+ export declare const EMAIL_CODE_MAX_ATTEMPTS = 5;
31
+ /** How long a confirmed code's ticket can be spent. */
32
+ export declare const EMAIL_TICKET_TTL_MS: number;
33
+ /** An email address as the API stores it: trimmed and lowercase. */
34
+ export declare const emailAddressSchema: z.ZodString;
35
+ /** An opaque one-use ticket (32 random bytes, base64url). */
36
+ export declare const emailTicketSchema: z.ZodString;
37
+ /** `POST /auth/email/verify/start` */
38
+ export declare const emailVerificationStartRequestSchema: z.ZodObject<{
39
+ purpose: z.ZodLiteral<"signup">;
40
+ email: z.ZodString;
41
+ }, "strict", z.ZodTypeAny, {
42
+ purpose: "signup";
43
+ email: string;
44
+ }, {
45
+ purpose: "signup";
46
+ email: string;
47
+ }>;
48
+ export type EmailVerificationStartRequest = z.infer<typeof emailVerificationStartRequestSchema>;
49
+ export interface EmailVerificationStartResponse {
50
+ /** Names this verification in `confirm`. Returned whether or not a code was sent. */
51
+ verificationId: string;
52
+ /** Unix milliseconds after which the code is refused. */
53
+ expiresAt: number;
54
+ }
55
+ export declare const emailVerificationStartResponseSchema: z.ZodType<EmailVerificationStartResponse>;
56
+ /** `POST /auth/email/verify/confirm` */
57
+ export declare const emailVerificationConfirmRequestSchema: z.ZodObject<{
58
+ verificationId: z.ZodString;
59
+ code: z.ZodString;
60
+ }, "strict", z.ZodTypeAny, {
61
+ code: string;
62
+ verificationId: string;
63
+ }, {
64
+ code: string;
65
+ verificationId: string;
66
+ }>;
67
+ export type EmailVerificationConfirmRequest = z.infer<typeof emailVerificationConfirmRequestSchema>;
68
+ export interface EmailVerificationConfirmResponse {
69
+ ticket: string;
70
+ /** Unix milliseconds after which the ticket is refused. */
71
+ expiresAt: number;
72
+ }
73
+ export declare const emailVerificationConfirmResponseSchema: z.ZodType<EmailVerificationConfirmResponse>;
74
+ /**
75
+ * Stable error codes (`error.code` in the API error body). Clients map these
76
+ * through their localization, never the English message.
77
+ */
78
+ export declare const EMAIL_VERIFICATION_ERROR_CODES: {
79
+ /** The code is wrong, or its verification expired or was spent. */
80
+ readonly codeInvalid: "EMAIL_CODE_INVALID";
81
+ /** Too many wrong codes: request a new one. */
82
+ readonly tooManyAttempts: "EMAIL_CODE_TOO_MANY_ATTEMPTS";
83
+ /** The ticket is unknown, expired, spent, or for another email or purpose. */
84
+ readonly ticketInvalid: "EMAIL_TICKET_INVALID";
85
+ /** A sign-up without a confirmed email. */
86
+ readonly ticketRequired: "EMAIL_TICKET_REQUIRED";
87
+ /** This server cannot send mail. */
88
+ readonly unavailable: "EMAIL_UNAVAILABLE";
89
+ };
90
+ export type EmailVerificationErrorCode = (typeof EMAIL_VERIFICATION_ERROR_CODES)[keyof typeof EMAIL_VERIFICATION_ERROR_CODES];
@@ -190,6 +190,7 @@ export declare const delegationGrantSchema: z.ZodObject<{
190
190
  createdAt: z.ZodString;
191
191
  updatedAt: z.ZodString;
192
192
  }, "strict", z.ZodTypeAny, {
193
+ expiresAt: string | null;
193
194
  ownerAccountId: string;
194
195
  id: string;
195
196
  actor: {
@@ -223,11 +224,11 @@ export declare const delegationGrantSchema: z.ZodObject<{
223
224
  }[];
224
225
  maximumAutonomy: "read_only" | "draft" | "execute_on_request" | "autonomous";
225
226
  canRedelegate: boolean;
226
- expiresAt: string | null;
227
227
  revokedAt: string | null;
228
228
  createdAt: string;
229
229
  updatedAt: string;
230
230
  }, {
231
+ expiresAt: string | null;
231
232
  ownerAccountId: string;
232
233
  id: string;
233
234
  actor: {
@@ -251,7 +252,6 @@ export declare const delegationGrantSchema: z.ZodObject<{
251
252
  capabilityPackages: ("security" | "finance" | "read" | "create" | "publish" | "communicate" | "administer" | "delegate")[];
252
253
  capabilities: string[];
253
254
  maximumAutonomy: "read_only" | "draft" | "execute_on_request" | "autonomous";
254
- expiresAt: string | null;
255
255
  createdAt: string;
256
256
  updatedAt: string;
257
257
  toolOverrides?: {
@@ -4,8 +4,8 @@
4
4
  * SINGLE SOURCE OF TRUTH for the first-party login result (the session arm). The
5
5
  * API validates its OUTPUT against this schema; every consumer (`@oxy.so/core`'s
6
6
  * auth mixin) validates its INPUT against the same definition, so producer and
7
- * consumers cannot drift. Sign-in is passkey (WebAuthn) or Commons handoff —
8
- * password and 2FA were removed, so the only outcome is a completed session.
7
+ * consumers cannot drift. This is the session arm every sign-in
8
+ * ends in (email code or link, password, authenticator, Commons handoff).
9
9
  *
10
10
  * The device transport is `deviceId` + `deviceSecret` + `POST /session/device/token`
11
11
  * (see `deviceSession.ts`). The legacy cookie/bootstrap/refresh-family lanes were
@@ -166,9 +166,10 @@ export type DeviceSessionSync = z.infer<typeof deviceSessionSyncSchema>;
166
166
  /**
167
167
  * Request body for `POST /session/device/token` — the client presents the
168
168
  * `deviceId` it stored first-party plus the opaque `deviceSecret`. NO bearer:
169
- * possession of the secret IS the proof of device ownership. The server matches
170
- * `sha256(deviceSecret)` against the device's stored `secretHash` (constant-time)
171
- * and mints a short access token for the device's active account.
169
+ * possession of the secret IS the proof of device ownership. The server looks
170
+ * `sha256(deviceSecret)` up among the device's holder credentials (one per app
171
+ * or origin that joined the shared DeviceSession) and mints a short access
172
+ * token for the device's active account.
172
173
  *
173
174
  * `accountId` pins the mint to ONE account of that device instead of whichever
174
175
  * account is currently active. It exists for identity-bound clients (Commons),
@@ -196,8 +197,8 @@ export declare const deviceTokenMintRequestSchema: z.ZodObject<{
196
197
  * short access token for the active account, its expiry, the device secret the
197
198
  * client must persist (`nextDeviceSecret` — on mint this echoes the presented
198
199
  * secret unchanged so concurrent refreshes from multiple origins do not race),
199
- * and the projected device-session state. Sign-in rotates the secret via
200
- * `issueDeviceSecret`; mint does not.
200
+ * and the projected device-session state. Nothing rotates: each sign-in issues
201
+ * a NEW holder credential and leaves the others valid.
201
202
  */
202
203
  export declare const deviceTokenMintResponseSchema: z.ZodObject<{
203
204
  accessToken: z.ZodString;
@@ -330,12 +331,11 @@ export type SessionAccountsChangedEvent = z.infer<typeof sessionAccountsChangedE
330
331
  * derived server-side from it) and consumed afterwards only by native
331
332
  * background code, which has no JS runtime to mint a token for itself.
332
333
  *
333
- * Deliberately a SEPARATE credential from the rotating `deviceSecret`: that one
334
- * rotates on every mint, so background code presenting it would become a second
335
- * writer of a value the JS runtime depends on, and background code killed
336
- * mid-rotation would silently sign the user out on the next cold start. Against
337
- * this credential background code is the sole writer, and it can never rotate
338
- * anything JS reads.
334
+ * Deliberately a SEPARATE credential from the holder `deviceSecret`: that one
335
+ * is device-wide and mints for whichever account is active, while this one is
336
+ * bound to ONE account and expires, so a widget worker never holds a
337
+ * credential that reaches every account on the device. Background code is its
338
+ * sole writer and never touches anything JS reads.
339
339
  *
340
340
  * The raw `secret` is returned exactly once, at provision time — never stored
341
341
  * retrievably, never logged, never re-read. A caller that loses it provisions
@@ -353,13 +353,13 @@ export declare const deviceBackgroundCredentialResponseSchema: z.ZodObject<{
353
353
  accountId: z.ZodString;
354
354
  expiresAt: z.ZodString;
355
355
  }, "strip", z.ZodTypeAny, {
356
- accountId: string;
357
356
  expiresAt: string;
357
+ accountId: string;
358
358
  deviceId: string;
359
359
  secret: string;
360
360
  }, {
361
- accountId: string;
362
361
  expiresAt: string;
362
+ accountId: string;
363
363
  deviceId: string;
364
364
  secret: string;
365
365
  }>;
@@ -368,10 +368,10 @@ export declare const deviceBackgroundCredentialResponseSchema: z.ZodObject<{
368
368
  * native background code with NO bearer and NO cookies: possession of the
369
369
  * background `secret` IS the proof, as it is for the device-secret mint.
370
370
  *
371
- * Unlike that mint this one NEVER rotates the presented secret (hence no
372
- * `next…` field to persist in the response), so background code interrupted
373
- * anywhere between request and response leaves the credential intact and
374
- * usable on its next run.
371
+ * Like that mint this one NEVER rotates the presented secret, and it carries
372
+ * no `next…` field at all, so background code interrupted anywhere between
373
+ * request and response leaves the credential intact and usable on its next
374
+ * run.
375
375
  */
376
376
  export declare const deviceBackgroundTokenRequestSchema: z.ZodObject<{
377
377
  deviceId: z.ZodString;
@@ -398,14 +398,126 @@ export declare const deviceBackgroundTokenResponseSchema: z.ZodObject<{
398
398
  expiresAt: z.ZodString;
399
399
  accountId: z.ZodString;
400
400
  }, "strip", z.ZodTypeAny, {
401
- accountId: string;
402
401
  expiresAt: string;
402
+ accountId: string;
403
403
  accessToken: string;
404
404
  }, {
405
- accountId: string;
406
405
  expiresAt: string;
406
+ accountId: string;
407
407
  accessToken: string;
408
408
  }>;
409
409
  export type DeviceBackgroundCredentialResponse = z.infer<typeof deviceBackgroundCredentialResponseSchema>;
410
410
  export type DeviceBackgroundTokenRequest = z.infer<typeof deviceBackgroundTokenRequestSchema>;
411
411
  export type DeviceBackgroundTokenResponse = z.infer<typeof deviceBackgroundTokenResponseSchema>;
412
+ /**
413
+ * Proof that the caller holds a device: the `deviceId` it stored first-party and
414
+ * one of that device's holder secrets. Sent with `POST /session/device/join-code`
415
+ * and, optionally, with a sign-in (`POST /auth/session/claim`, the email,
416
+ * password and second-factor sign-ins, `POST /auth/signup`), where
417
+ * a valid proof puts the new session on THAT device so every app holding it sees
418
+ * the account. An invalid proof on a sign-in is ignored, never an error.
419
+ */
420
+ export declare const deviceProofSchema: z.ZodObject<{
421
+ deviceId: z.ZodString;
422
+ deviceSecret: z.ZodString;
423
+ }, "strip", z.ZodTypeAny, {
424
+ deviceId: string;
425
+ deviceSecret: string;
426
+ }, {
427
+ deviceId: string;
428
+ deviceSecret: string;
429
+ }>;
430
+ /**
431
+ * `POST /session/device/register` — auth.oxy.so only. No body. A new, empty
432
+ * DeviceSession with a server-chosen `deviceId` and ONE holder credential for
433
+ * auth.oxy.so. The raw secret is returned exactly once.
434
+ */
435
+ export declare const deviceRegisterResponseSchema: z.ZodObject<{
436
+ deviceId: z.ZodString;
437
+ deviceSecret: z.ZodString;
438
+ }, "strip", z.ZodTypeAny, {
439
+ deviceId: string;
440
+ deviceSecret: string;
441
+ }, {
442
+ deviceId: string;
443
+ deviceSecret: string;
444
+ }>;
445
+ /**
446
+ * `POST /session/device/join-code` — auth.oxy.so only (the bridge window). Proves
447
+ * auth.oxy.so's device and asks for a one-use code an official app redeems to
448
+ * join it. The code is bound to the application (`clientId`), its exact
449
+ * registered `redirectUri` and the app's PKCE challenge, and lives about a
450
+ * minute.
451
+ */
452
+ export declare const deviceJoinCodeRequestSchema: z.ZodObject<{
453
+ deviceId: z.ZodString;
454
+ deviceSecret: z.ZodString;
455
+ } & {
456
+ clientId: z.ZodString;
457
+ redirectUri: z.ZodString;
458
+ codeChallenge: z.ZodString;
459
+ codeChallengeMethod: z.ZodLiteral<"S256">;
460
+ }, "strip", z.ZodTypeAny, {
461
+ deviceId: string;
462
+ deviceSecret: string;
463
+ clientId: string;
464
+ redirectUri: string;
465
+ codeChallenge: string;
466
+ codeChallengeMethod: "S256";
467
+ }, {
468
+ deviceId: string;
469
+ deviceSecret: string;
470
+ clientId: string;
471
+ redirectUri: string;
472
+ codeChallenge: string;
473
+ codeChallengeMethod: "S256";
474
+ }>;
475
+ export declare const deviceJoinCodeResponseSchema: z.ZodObject<{
476
+ code: z.ZodString;
477
+ /** Seconds until the code expires. */
478
+ expiresIn: z.ZodNumber;
479
+ }, "strip", z.ZodTypeAny, {
480
+ code: string;
481
+ expiresIn: number;
482
+ }, {
483
+ code: string;
484
+ expiresIn: number;
485
+ }>;
486
+ /**
487
+ * `POST /session/device/join` — called by the app's own origin with the code the
488
+ * bridge window posted to it and the PKCE verifier only the app holds. Returns a
489
+ * NEW holder credential for the browser's device; the app then mints through the
490
+ * ordinary `POST /session/device/token`.
491
+ */
492
+ export declare const deviceJoinRequestSchema: z.ZodObject<{
493
+ code: z.ZodString;
494
+ codeVerifier: z.ZodString;
495
+ clientId: z.ZodString;
496
+ redirectUri: z.ZodString;
497
+ }, "strip", z.ZodTypeAny, {
498
+ code: string;
499
+ clientId: string;
500
+ redirectUri: string;
501
+ codeVerifier: string;
502
+ }, {
503
+ code: string;
504
+ clientId: string;
505
+ redirectUri: string;
506
+ codeVerifier: string;
507
+ }>;
508
+ export declare const deviceJoinResponseSchema: z.ZodObject<{
509
+ deviceId: z.ZodString;
510
+ deviceSecret: z.ZodString;
511
+ }, "strip", z.ZodTypeAny, {
512
+ deviceId: string;
513
+ deviceSecret: string;
514
+ }, {
515
+ deviceId: string;
516
+ deviceSecret: string;
517
+ }>;
518
+ export type DeviceProof = z.infer<typeof deviceProofSchema>;
519
+ export type DeviceRegisterResponse = z.infer<typeof deviceRegisterResponseSchema>;
520
+ export type DeviceJoinCodeRequest = z.infer<typeof deviceJoinCodeRequestSchema>;
521
+ export type DeviceJoinCodeResponse = z.infer<typeof deviceJoinCodeResponseSchema>;
522
+ export type DeviceJoinRequest = z.infer<typeof deviceJoinRequestSchema>;
523
+ export type DeviceJoinResponse = z.infer<typeof deviceJoinResponseSchema>;
@@ -253,6 +253,7 @@ export declare const resolveExternalIdentityResponseSchema: z.ZodEffects<z.ZodOb
253
253
  sourceUserId: string;
254
254
  }[];
255
255
  redirectedUserIds: string[];
256
+ email?: string | undefined;
256
257
  kind?: "bot" | "personal" | "organization" | "project" | "channel" | undefined;
257
258
  bio?: string | undefined;
258
259
  avatar?: string | null | undefined;
@@ -263,7 +264,6 @@ export declare const resolveExternalIdentityResponseSchema: z.ZodEffects<z.ZodOb
263
264
  did?: string | undefined;
264
265
  verifiedDomains?: import("./identity").VerifiedDomain[] | undefined;
265
266
  _id?: string | undefined;
266
- email?: string | undefined;
267
267
  phone?: string | undefined;
268
268
  address?: string | undefined;
269
269
  birthday?: string | undefined;
@@ -307,6 +307,7 @@ export declare const resolveExternalIdentityResponseSchema: z.ZodEffects<z.ZodOb
307
307
  sourceUserId: string;
308
308
  }[];
309
309
  redirectedUserIds: string[];
310
+ email?: string | undefined;
310
311
  kind?: "bot" | "personal" | "organization" | "project" | "channel" | undefined;
311
312
  bio?: string | undefined;
312
313
  avatar?: string | null | undefined;
@@ -317,7 +318,6 @@ export declare const resolveExternalIdentityResponseSchema: z.ZodEffects<z.ZodOb
317
318
  did?: string | undefined;
318
319
  verifiedDomains?: import("./identity").VerifiedDomain[] | undefined;
319
320
  _id?: string | undefined;
320
- email?: string | undefined;
321
321
  phone?: string | undefined;
322
322
  address?: string | undefined;
323
323
  birthday?: string | undefined;
@@ -361,6 +361,7 @@ export declare const resolveExternalIdentityResponseSchema: z.ZodEffects<z.ZodOb
361
361
  sourceUserId: string;
362
362
  }[];
363
363
  redirectedUserIds: string[];
364
+ email?: string | undefined;
364
365
  kind?: "bot" | "personal" | "organization" | "project" | "channel" | undefined;
365
366
  bio?: string | undefined;
366
367
  avatar?: string | null | undefined;
@@ -371,7 +372,6 @@ export declare const resolveExternalIdentityResponseSchema: z.ZodEffects<z.ZodOb
371
372
  did?: string | undefined;
372
373
  verifiedDomains?: import("./identity").VerifiedDomain[] | undefined;
373
374
  _id?: string | undefined;
374
- email?: string | undefined;
375
375
  phone?: string | undefined;
376
376
  address?: string | undefined;
377
377
  birthday?: string | undefined;
@@ -415,6 +415,7 @@ export declare const resolveExternalIdentityResponseSchema: z.ZodEffects<z.ZodOb
415
415
  sourceUserId: string;
416
416
  }[];
417
417
  redirectedUserIds: string[];
418
+ email?: string | undefined;
418
419
  kind?: "bot" | "personal" | "organization" | "project" | "channel" | undefined;
419
420
  bio?: string | undefined;
420
421
  avatar?: string | null | undefined;
@@ -425,7 +426,6 @@ export declare const resolveExternalIdentityResponseSchema: z.ZodEffects<z.ZodOb
425
426
  did?: string | undefined;
426
427
  verifiedDomains?: import("./identity").VerifiedDomain[] | undefined;
427
428
  _id?: string | undefined;
428
- email?: string | undefined;
429
429
  phone?: string | undefined;
430
430
  address?: string | undefined;
431
431
  birthday?: string | undefined;
@@ -315,18 +315,14 @@ export type DomainVerificationInstructions = z.infer<typeof domainVerificationIn
315
315
  /**
316
316
  * One linked authentication method. Mirrors a `User.authMethods[]` entry.
317
317
  * `verificationMethodId` is present for `identity` methods (a key), linking the
318
- * auth method to its DID verification-method fragment. For `webauthn` methods
319
- * `credentialId` identifies the specific passkey (one entry per registered
320
- * credential) and `name` is its user-facing label; a passkey is NOT a DID
321
- * verification method, so it carries no `verificationMethodId` (a passkey-only
322
- * account stays custodial).
318
+ * auth method to its DID verification-method fragment. A key is the only
319
+ * linked method: an email, a password and an authenticator are sign-in
320
+ * factors of the account, not DID verification methods.
323
321
  */
324
322
  export interface AuthMethodEntry {
325
- type: 'identity' | 'webauthn';
323
+ type: 'identity';
326
324
  linkedAt: string | Date;
327
325
  verificationMethodId?: string;
328
- credentialId?: string;
329
- name?: string;
330
326
  }
331
327
  export declare const authMethodEntrySchema: z.ZodType<AuthMethodEntry>;
332
328
  /**
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Linking Commons to an account without a key (ADR 0029 D3) — the two-device
3
+ * relay.
4
+ *
5
+ * An account without a key links a Commons root once, and becomes
6
+ * self-custodied: its email is deleted and its phrase in Commons is how it gets
7
+ * back in. The authority is the same as `POST /auth/link` (ADR 0024 D8): a root
8
+ * proof (`link_identity`) by the key Commons holds over a one-use challenge,
9
+ * and the account's own confirmation — a code just sent to its email (plus its
10
+ * authenticator code when it has one). Only the transport is new, because the
11
+ * two factors live on two devices:
12
+ *
13
+ * 1. The signed-in account (the "Link Commons" panel of `@oxy.so/services`)
14
+ * opens a link request → `{ linkId, challenge }`, shown as a QR
15
+ * (`oxycommons://link?id=…&c=…`).
16
+ * 2. Commons scans it, reads the request (the account's id and username),
17
+ * signs the root proof over the challenge and posts it with its key.
18
+ * 3. Both screens show the same 6-digit code, derived from the link id and
19
+ * that key (`deriveIdentityLinkCode` in `@oxy.so/core`); the person checks
20
+ * they match, so a photographed QR cannot slip another key in.
21
+ * 4. The panel completes with the email code: the account gains the root,
22
+ * loses the email, and Commons signs in with it.
23
+ *
24
+ * The server stores only the challenge's hash; the challenge travels in the QR.
25
+ */
26
+ import { z } from 'zod';
27
+ export declare const IDENTITY_LINK_STATUSES: readonly ["pending", "signed", "completed", "cancelled"];
28
+ export type IdentityLinkStatus = (typeof IDENTITY_LINK_STATUSES)[number];
29
+ /** The scheme and host Commons routes a link QR to. */
30
+ export declare const IDENTITY_LINK_QR_PREFIX = "oxycommons://link";
31
+ export declare const identityLinkIdSchema: z.ZodString;
32
+ /** The QR auth.oxy.so shows: the request's id and the challenge Commons signs. */
33
+ export declare function buildIdentityLinkQrPayload(linkId: string, challenge: string): string;
34
+ /** The request a scanned code names, or `null` for anything that is not a link QR. */
35
+ export declare function parseIdentityLinkQrPayload(raw: string): {
36
+ linkId: string;
37
+ challenge: string;
38
+ } | null;
39
+ /** `POST /identity/link` */
40
+ export interface IdentityLinkCreateResponse {
41
+ linkId: string;
42
+ /** The one-use `link_identity` proof challenge, hex. */
43
+ challenge: string;
44
+ /** Unix milliseconds. */
45
+ expiresAt: number;
46
+ qrPayload: string;
47
+ }
48
+ export declare const identityLinkCreateResponseSchema: z.ZodType<IdentityLinkCreateResponse>;
49
+ /** `GET /identity/link/:linkId` — what both devices poll. */
50
+ export interface IdentityLinkState {
51
+ status: IdentityLinkStatus;
52
+ /** The account being linked: the proof's subject and actor. */
53
+ userId: string;
54
+ username: string | null;
55
+ /** The key Commons signed with, once it has. */
56
+ publicKey: string | null;
57
+ audience: string;
58
+ expiresAt: number;
59
+ }
60
+ export declare const identityLinkStateSchema: z.ZodType<IdentityLinkState>;
61
+ /** `POST /identity/link/:linkId/proof` — from Commons, no bearer. */
62
+ export declare const identityLinkProofRequestSchema: z.ZodObject<{
63
+ publicKey: z.ZodString;
64
+ proof: z.ZodObject<{
65
+ v: z.ZodLiteral<2>;
66
+ challenge: z.ZodString;
67
+ expiresAt: z.ZodNumber;
68
+ signature: z.ZodString;
69
+ }, "strip", z.ZodTypeAny, {
70
+ expiresAt: number;
71
+ signature: string;
72
+ v: 2;
73
+ challenge: string;
74
+ }, {
75
+ expiresAt: number;
76
+ signature: string;
77
+ v: 2;
78
+ challenge: string;
79
+ }>;
80
+ }, "strict", z.ZodTypeAny, {
81
+ publicKey: string;
82
+ proof: {
83
+ expiresAt: number;
84
+ signature: string;
85
+ v: 2;
86
+ challenge: string;
87
+ };
88
+ }, {
89
+ publicKey: string;
90
+ proof: {
91
+ expiresAt: number;
92
+ signature: string;
93
+ v: 2;
94
+ challenge: string;
95
+ };
96
+ }>;
97
+ export type IdentityLinkProofRequest = z.infer<typeof identityLinkProofRequestSchema>;
98
+ /**
99
+ * `POST /identity/link/:linkId/complete` — the account's own confirmation: a
100
+ * code just sent to its email for this link (`reauth`, plus its authenticator
101
+ * code when it has one).
102
+ */
103
+ export declare const identityLinkCompleteRequestSchema: z.ZodObject<{
104
+ reauth: z.ZodObject<{
105
+ emailCode: z.ZodObject<{
106
+ verificationId: z.ZodString;
107
+ code: z.ZodString;
108
+ }, "strict", z.ZodTypeAny, {
109
+ code: string;
110
+ verificationId: string;
111
+ }, {
112
+ code: string;
113
+ verificationId: string;
114
+ }>;
115
+ totpCode: z.ZodOptional<z.ZodString>;
116
+ }, "strict", z.ZodTypeAny, {
117
+ emailCode: {
118
+ code: string;
119
+ verificationId: string;
120
+ };
121
+ totpCode?: string | undefined;
122
+ }, {
123
+ emailCode: {
124
+ code: string;
125
+ verificationId: string;
126
+ };
127
+ totpCode?: string | undefined;
128
+ }>;
129
+ }, "strict", z.ZodTypeAny, {
130
+ reauth: {
131
+ emailCode: {
132
+ code: string;
133
+ verificationId: string;
134
+ };
135
+ totpCode?: string | undefined;
136
+ };
137
+ }, {
138
+ reauth: {
139
+ emailCode: {
140
+ code: string;
141
+ verificationId: string;
142
+ };
143
+ totpCode?: string | undefined;
144
+ };
145
+ }>;
146
+ export type IdentityLinkCompleteRequest = z.infer<typeof identityLinkCompleteRequestSchema>;
@@ -27,26 +27,12 @@ export declare const IDENTITY_PROOF_CHALLENGE_TTL_MS: number;
27
27
  * action and spent only by a proof for that action.
28
28
  */
29
29
  export declare const IDENTITY_PROOF_ACTIONS: {
30
- /** A keyless account's FIRST root, stored with its web envelope. */
31
- readonly establish: "web_envelope_establish";
32
- /** Replace the web envelope (add or remove a wrap, re-seal). */
33
- readonly put: "web_envelope_put";
34
- /** Record that the recovery material is written down. */
35
- readonly phraseConfirmed: "web_envelope_phrase_confirmed";
36
- /** Record that the recovery material re-derived the root. */
37
- readonly recoveryVerified: "web_envelope_recovery_verified";
38
- /** Remove the web holder. */
39
- readonly delete: "web_envelope_delete";
40
- /** Link a keyless account's first root without a web envelope (`POST /auth/link`). */
30
+ /**
31
+ * Link Commons' root to an account that has none (`POST /auth/link`).
32
+ * The account becomes self-custodied and its email is deleted
33
+ * (ADR 0029 D3).
34
+ */
41
35
  readonly link: "link_identity";
42
- /** Create a personal account together with its root (passkey sign-up). */
43
- readonly enroll: "enroll_identity";
44
- /** Prove the root to start signed-out recovery. */
45
- readonly recoverStart: "recover_account_start";
46
- /** Bind the new passkey and envelope when completing signed-out recovery. */
47
- readonly recoverComplete: "recover_account_complete";
48
- /** Seal the root for the Commons device that joined a move (payload: move id + sealed bytes). */
49
- readonly moveSeal: "identity_move_seal";
50
36
  };
51
37
  export type IdentityProofAction = (typeof IDENTITY_PROOF_ACTIONS)[keyof typeof IDENTITY_PROOF_ACTIONS];
52
38
  export declare const IDENTITY_PROOF_ACTION_VALUES: [IdentityProofAction, ...IdentityProofAction[]];
@@ -64,7 +50,7 @@ export interface IdentityProofClaims {
64
50
  rootPublicKey: string;
65
51
  /** SHA-256 hex of `canonicalJson(payload)`, or `null` when the operation has no payload. */
66
52
  payloadDigest: string | null;
67
- /** The envelope revision the operation expects to replace, or `null`. */
53
+ /** The revision the operation expects to replace, or `null` when it replaces nothing. */
68
54
  expectedRevision: number | null;
69
55
  audience: string;
70
56
  /** The one-use server challenge. */
@@ -103,11 +89,11 @@ export declare const identityProofSchema: z.ZodObject<{
103
89
  export type IdentityProof = z.infer<typeof identityProofSchema>;
104
90
  /** `POST /identity/proof-challenge` */
105
91
  export declare const identityProofChallengeRequestSchema: z.ZodObject<{
106
- action: z.ZodEnum<[IdentityProofAction, ...IdentityProofAction[]]>;
92
+ action: z.ZodEnum<["link_identity", ..."link_identity"[]]>;
107
93
  }, "strip", z.ZodTypeAny, {
108
- action: IdentityProofAction;
94
+ action: "link_identity";
109
95
  }, {
110
- action: IdentityProofAction;
96
+ action: "link_identity";
111
97
  }>;
112
98
  export type IdentityProofChallengeRequest = z.infer<typeof identityProofChallengeRequestSchema>;
113
99
  export interface IdentityProofChallengeResponse {
@@ -123,34 +109,21 @@ export declare const identityProofChallengeResponseSchema: z.ZodType<IdentityPro
123
109
  */
124
110
  export declare const IDENTITY_ERROR_CODES: {
125
111
  readonly proofInvalid: "IDENTITY_PROOF_INVALID";
126
- readonly revisionConflict: "IDENTITY_ENVELOPE_REVISION_CONFLICT";
127
112
  readonly rootAlreadyLinked: "IDENTITY_ROOT_ALREADY_LINKED";
128
113
  readonly rootLinkedElsewhere: "IDENTITY_ROOT_LINKED_ELSEWHERE";
129
- readonly noRoot: "IDENTITY_NO_ROOT";
130
114
  readonly freshFactorRequired: "IDENTITY_FRESH_FACTOR_REQUIRED";
131
- readonly lastWebHolder: "IDENTITY_LAST_WEB_HOLDER";
132
- readonly enrollmentRequired: "IDENTITY_ENROLLMENT_REQUIRED";
133
- readonly enrollmentInvalid: "IDENTITY_ENROLLMENT_INVALID";
134
115
  readonly notPersonal: "IDENTITY_NOT_PERSONAL_ACCOUNT";
135
- readonly recoveryFailed: "IDENTITY_RECOVERY_FAILED";
136
116
  };
137
117
  export type IdentityErrorCode = (typeof IDENTITY_ERROR_CODES)[keyof typeof IDENTITY_ERROR_CODES];
138
118
  /**
139
- * `GET /identity/root-status` — non-sensitive readiness metadata any first-party
140
- * surface (Accounts, the account menu) may read with a bearer to show a reminder,
141
- * without the ciphertext and without opening anything (ADR 0024 D5).
119
+ * `GET /identity/root-status` — how the signed-in account is kept (ADR 0029 D3),
120
+ * for Accounts and the account menu to recommend linking Commons. Read with a
121
+ * bearer from any first-party origin.
142
122
  */
143
123
  export interface IdentityRootStatus {
144
- /** Whether the account has a root at all. */
124
+ /** Whether Commons' root is linked: the account is self-custodied. */
145
125
  rootLinked: boolean;
146
- /** Passkeys whose wraps can open the web holder, and how many have proven it. `null`: no web holder. */
147
- webHolder: {
148
- passkeys: number;
149
- verifiedPasskeys: number;
150
- } | null;
151
- /** Whether the root has recovery words (a raw-key root does not). `null` when unknown (no web holder). */
152
- hasPhrase: boolean | null;
153
- phraseConfirmedAt: string | null;
154
- recoveryVerifiedAt: string | null;
126
+ /** The email of an account without a key; `null` once Commons is linked. */
127
+ recoveryEmail: string | null;
155
128
  }
156
129
  export declare const identityRootStatusSchema: z.ZodType<IdentityRootStatus>;