@oxy.so/contracts 2.2.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 (39) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/accountEmail.js +21 -25
  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 +22 -19
  7. package/dist/cjs/identityProof.js +2 -2
  8. package/dist/cjs/index.js +59 -28
  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 +21 -25
  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 +21 -18
  17. package/dist/esm/identityProof.js +2 -2
  18. package/dist/esm/index.js +11 -11
  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 +20 -28
  23. package/dist/types/accountGraph.d.ts +4 -4
  24. package/dist/types/deviceBoot.d.ts +2 -2
  25. package/dist/types/deviceSession.d.ts +127 -15
  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 +54 -118
  29. package/dist/types/identityProof.d.ts +3 -3
  30. package/dist/types/index.d.ts +8 -8
  31. package/dist/types/inference/entitlement.d.ts +2 -2
  32. package/dist/types/oauth.d.ts +16 -16
  33. package/dist/types/reputation.d.ts +32 -127
  34. package/dist/types/signIn.d.ts +717 -0
  35. package/dist/types/userResponse.d.ts +2 -2
  36. package/package.json +1 -1
  37. package/dist/cjs/webauthn.js +0 -114
  38. package/dist/esm/webauthn.js +0 -111
  39. package/dist/types/webauthn.d.ts +0 -173
@@ -1,22 +1,26 @@
1
1
  /**
2
- * Recovery email contracts (ADR 0029 D3).
2
+ * Email codes and tickets (ADR 0030).
3
3
  *
4
- * A web account is a username, a passkey and a recovery email. The email is
5
- * proven by a 6-digit code sent to it, and the proof is a short-lived one-use
6
- * ticket the next step spends:
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
7
  *
8
- * - `signup`: the ticket lets `POST /webauthn/register/verify` create the
9
- * account with that email;
10
- * - `recovery`: the person names their username or email, the code goes to the
11
- * account's recovery email, and the ticket lets them register a new passkey
12
- * for that account.
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.
13
11
  *
14
- * `start` answers the same whether or not an account exists, so neither purpose
15
- * tells anyone which emails or usernames have an Oxy account. An account with a
16
- * Commons key has no recovery email: it recovers in Commons.
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`).
17
14
  */
18
15
  import { z } from 'zod';
19
- export declare const EMAIL_VERIFICATION_PURPOSES: readonly ["signup", "recovery"];
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"];
20
24
  export type EmailVerificationPurpose = (typeof EMAIL_VERIFICATION_PURPOSES)[number];
21
25
  /** Digits in a code. */
22
26
  export declare const EMAIL_CODE_LENGTH = 6;
@@ -31,7 +35,7 @@ export declare const emailAddressSchema: z.ZodString;
31
35
  /** An opaque one-use ticket (32 random bytes, base64url). */
32
36
  export declare const emailTicketSchema: z.ZodString;
33
37
  /** `POST /auth/email/verify/start` */
34
- export declare const emailVerificationStartRequestSchema: z.ZodDiscriminatedUnion<"purpose", [z.ZodObject<{
38
+ export declare const emailVerificationStartRequestSchema: z.ZodObject<{
35
39
  purpose: z.ZodLiteral<"signup">;
36
40
  email: z.ZodString;
37
41
  }, "strict", z.ZodTypeAny, {
@@ -40,17 +44,7 @@ export declare const emailVerificationStartRequestSchema: z.ZodDiscriminatedUnio
40
44
  }, {
41
45
  purpose: "signup";
42
46
  email: string;
43
- }>, z.ZodObject<{
44
- purpose: z.ZodLiteral<"recovery">;
45
- /** The account's username, or its recovery email. */
46
- identifier: z.ZodString;
47
- }, "strict", z.ZodTypeAny, {
48
- purpose: "recovery";
49
- identifier: string;
50
- }, {
51
- purpose: "recovery";
52
- identifier: string;
53
- }>]>;
47
+ }>;
54
48
  export type EmailVerificationStartRequest = z.infer<typeof emailVerificationStartRequestSchema>;
55
49
  export interface EmailVerificationStartResponse {
56
50
  /** Names this verification in `confirm`. Returned whether or not a code was sent. */
@@ -75,8 +69,6 @@ export interface EmailVerificationConfirmResponse {
75
69
  ticket: string;
76
70
  /** Unix milliseconds after which the ticket is refused. */
77
71
  expiresAt: number;
78
- /** Recovery only: the account the ticket recovers. `null` for sign-up. */
79
- username: string | null;
80
72
  }
81
73
  export declare const emailVerificationConfirmResponseSchema: z.ZodType<EmailVerificationConfirmResponse>;
82
74
  /**
@@ -90,7 +82,7 @@ export declare const EMAIL_VERIFICATION_ERROR_CODES: {
90
82
  readonly tooManyAttempts: "EMAIL_CODE_TOO_MANY_ATTEMPTS";
91
83
  /** The ticket is unknown, expired, spent, or for another email or purpose. */
92
84
  readonly ticketInvalid: "EMAIL_TICKET_INVALID";
93
- /** A sign-up without a confirmed recovery email. */
85
+ /** A sign-up without a confirmed email. */
94
86
  readonly ticketRequired: "EMAIL_TICKET_REQUIRED";
95
87
  /** This server cannot send mail. */
96
88
  readonly unavailable: "EMAIL_UNAVAILABLE";
@@ -315,8 +315,8 @@ export declare const createAccountRequestSchema: z.ZodEffects<z.ZodObject<{
315
315
  */
316
316
  isPrivateAccount: z.ZodOptional<z.ZodBoolean>;
317
317
  }, "strip", z.ZodTypeAny, {
318
- username: string;
319
318
  kind: "bot" | "organization" | "project" | "channel";
319
+ username: string;
320
320
  parentAccountId?: string | undefined;
321
321
  name?: {
322
322
  first?: string | undefined;
@@ -330,8 +330,8 @@ export declare const createAccountRequestSchema: z.ZodEffects<z.ZodObject<{
330
330
  accountCategories?: ("security" | "news" | "politics" | "business" | "startup" | "finance" | "crypto" | "marketplace" | "retail" | "real_estate" | "agency" | "landlord" | "cooperative" | "architecture" | "technology" | "software" | "ai" | "automation" | "science" | "education" | "books" | "health" | "fitness" | "sports" | "gaming" | "music" | "film" | "podcast" | "art" | "photography" | "comedy" | "food" | "travel" | "fashion" | "home_garden" | "diy" | "automotive" | "animals" | "family" | "nonprofit" | "government" | "community" | "activism" | "environment" | "religion" | "other")[] | undefined;
331
331
  isPrivateAccount?: boolean | undefined;
332
332
  }, {
333
- username: string;
334
333
  kind: "bot" | "organization" | "project" | "channel";
334
+ username: string;
335
335
  parentAccountId?: string | undefined;
336
336
  name?: {
337
337
  first?: string | undefined;
@@ -345,8 +345,8 @@ export declare const createAccountRequestSchema: z.ZodEffects<z.ZodObject<{
345
345
  accountCategories?: ("security" | "news" | "politics" | "business" | "startup" | "finance" | "crypto" | "marketplace" | "retail" | "real_estate" | "agency" | "landlord" | "cooperative" | "architecture" | "technology" | "software" | "ai" | "automation" | "science" | "education" | "books" | "health" | "fitness" | "sports" | "gaming" | "music" | "film" | "podcast" | "art" | "photography" | "comedy" | "food" | "travel" | "fashion" | "home_garden" | "diy" | "automotive" | "animals" | "family" | "nonprofit" | "government" | "community" | "activism" | "environment" | "religion" | "other")[] | undefined;
346
346
  isPrivateAccount?: boolean | undefined;
347
347
  }>, {
348
- username: string;
349
348
  kind: "bot" | "organization" | "project" | "channel";
349
+ username: string;
350
350
  parentAccountId?: string | undefined;
351
351
  name?: {
352
352
  first?: string | undefined;
@@ -360,8 +360,8 @@ export declare const createAccountRequestSchema: z.ZodEffects<z.ZodObject<{
360
360
  accountCategories?: ("security" | "news" | "politics" | "business" | "startup" | "finance" | "crypto" | "marketplace" | "retail" | "real_estate" | "agency" | "landlord" | "cooperative" | "architecture" | "technology" | "software" | "ai" | "automation" | "science" | "education" | "books" | "health" | "fitness" | "sports" | "gaming" | "music" | "film" | "podcast" | "art" | "photography" | "comedy" | "food" | "travel" | "fashion" | "home_garden" | "diy" | "automotive" | "animals" | "family" | "nonprofit" | "government" | "community" | "activism" | "environment" | "religion" | "other")[] | undefined;
361
361
  isPrivateAccount?: boolean | undefined;
362
362
  }, {
363
- username: string;
364
363
  kind: "bot" | "organization" | "project" | "channel";
364
+ username: string;
365
365
  parentAccountId?: string | undefined;
366
366
  name?: {
367
367
  first?: string | undefined;
@@ -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
@@ -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;
@@ -409,3 +409,115 @@ export declare const deviceBackgroundTokenResponseSchema: z.ZodObject<{
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>;
@@ -491,7 +491,6 @@ export declare const lookupExternalIdentitiesResponseSchema: z.ZodObject<{
491
491
  }>, "many">;
492
492
  redirectedUserIds: z.ZodArray<z.ZodString, "many">;
493
493
  }, "strip", z.ZodTypeAny, {
494
- identifier: string;
495
494
  userId: string | null;
496
495
  externalIdentities: {
497
496
  canonicalAcct: string;
@@ -502,8 +501,8 @@ export declare const lookupExternalIdentitiesResponseSchema: z.ZodObject<{
502
501
  sourceUserId: string;
503
502
  }[];
504
503
  redirectedUserIds: string[];
505
- }, {
506
504
  identifier: string;
505
+ }, {
507
506
  userId: string | null;
508
507
  externalIdentities: {
509
508
  canonicalAcct: string;
@@ -514,10 +513,10 @@ export declare const lookupExternalIdentitiesResponseSchema: z.ZodObject<{
514
513
  sourceUserId: string;
515
514
  }[];
516
515
  redirectedUserIds: string[];
516
+ identifier: string;
517
517
  }>, "many">;
518
518
  }, "strip", z.ZodTypeAny, {
519
519
  identities: {
520
- identifier: string;
521
520
  userId: string | null;
522
521
  externalIdentities: {
523
522
  canonicalAcct: string;
@@ -528,10 +527,10 @@ export declare const lookupExternalIdentitiesResponseSchema: z.ZodObject<{
528
527
  sourceUserId: string;
529
528
  }[];
530
529
  redirectedUserIds: string[];
530
+ identifier: string;
531
531
  }[];
532
532
  }, {
533
533
  identities: {
534
- identifier: string;
535
534
  userId: string | null;
536
535
  externalIdentities: {
537
536
  canonicalAcct: string;
@@ -542,6 +541,7 @@ export declare const lookupExternalIdentitiesResponseSchema: z.ZodObject<{
542
541
  sourceUserId: string;
543
542
  }[];
544
543
  redirectedUserIds: string[];
544
+ identifier: string;
545
545
  }[];
546
546
  }>;
547
547
  export type ExternalIdentityReference = z.infer<typeof externalIdentityReferenceSchema>;
@@ -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
  /**
@@ -1,22 +1,25 @@
1
1
  /**
2
- * Linking Commons to a passkey account (ADR 0029 D3) — the two-device relay.
2
+ * Linking Commons to an account without a key (ADR 0029 D3) — the two-device
3
+ * relay.
3
4
  *
4
- * A passkey account links a Commons root once, and becomes self-custodied: its
5
- * recovery email is deleted and its phrase in Commons is how it gets back in.
6
- * The authority is the same as `POST /auth/link` (ADR 0024 D8): a root proof
7
- * (`link_identity`) by the key Commons holds, and a fresh assertion by one of
8
- * the account's passkeys over the SAME one-use challenge. Only the transport is
9
- * new, because the two factors live on two devices:
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:
10
12
  *
11
- * 1. auth.oxy.so (signed in) opens a link request → `{ linkId, challenge }`,
12
- * shown as a QR (`oxycommons://link?id=…&c=…`).
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=…`).
13
16
  * 2. Commons scans it, reads the request (the account's id and username),
14
17
  * signs the root proof over the challenge and posts it with its key.
15
18
  * 3. Both screens show the same 6-digit code, derived from the link id and
16
19
  * that key (`deriveIdentityLinkCode` in `@oxy.so/core`); the person checks
17
20
  * they match, so a photographed QR cannot slip another key in.
18
- * 4. auth.oxy.so asserts the passkey over the challenge and completes: the
19
- * account gains the root, loses the email, and Commons signs in with it.
21
+ * 4. The panel completes with the email code: the account gains the root,
22
+ * loses the email, and Commons signs in with it.
20
23
  *
21
24
  * The server stores only the challenge's hash; the challenge travels in the QR.
22
25
  */
@@ -92,119 +95,52 @@ export declare const identityLinkProofRequestSchema: z.ZodObject<{
92
95
  };
93
96
  }>;
94
97
  export type IdentityLinkProofRequest = z.infer<typeof identityLinkProofRequestSchema>;
95
- /** `POST /identity/link/:linkId/options` — WebAuthn request options over the challenge. */
96
- export declare const identityLinkOptionsRequestSchema: z.ZodObject<{
97
- challenge: z.ZodString;
98
- }, "strict", z.ZodTypeAny, {
99
- challenge: string;
100
- }, {
101
- challenge: string;
102
- }>;
103
- export type IdentityLinkOptionsRequest = z.infer<typeof identityLinkOptionsRequestSchema>;
104
- /** `POST /identity/link/:linkId/complete` — the passkey assertion over the challenge. */
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
+ */
105
103
  export declare const identityLinkCompleteRequestSchema: z.ZodObject<{
106
- assertion: z.ZodObject<{
107
- id: z.ZodString;
108
- rawId: z.ZodString;
109
- type: z.ZodLiteral<"public-key">;
110
- response: z.ZodObject<{
111
- clientDataJSON: z.ZodString;
112
- authenticatorData: z.ZodString;
113
- signature: z.ZodString;
114
- userHandle: z.ZodOptional<z.ZodString>;
115
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
116
- clientDataJSON: z.ZodString;
117
- authenticatorData: z.ZodString;
118
- signature: z.ZodString;
119
- userHandle: z.ZodOptional<z.ZodString>;
120
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
121
- clientDataJSON: z.ZodString;
122
- authenticatorData: z.ZodString;
123
- signature: z.ZodString;
124
- userHandle: z.ZodOptional<z.ZodString>;
125
- }, z.ZodTypeAny, "passthrough">>;
126
- clientExtensionResults: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
127
- authenticatorAttachment: z.ZodOptional<z.ZodString>;
128
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
129
- id: z.ZodString;
130
- rawId: z.ZodString;
131
- type: z.ZodLiteral<"public-key">;
132
- response: z.ZodObject<{
133
- clientDataJSON: z.ZodString;
134
- authenticatorData: z.ZodString;
135
- signature: z.ZodString;
136
- userHandle: z.ZodOptional<z.ZodString>;
137
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
138
- clientDataJSON: z.ZodString;
139
- authenticatorData: z.ZodString;
140
- signature: z.ZodString;
141
- userHandle: z.ZodOptional<z.ZodString>;
142
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
143
- clientDataJSON: z.ZodString;
144
- authenticatorData: z.ZodString;
145
- signature: z.ZodString;
146
- userHandle: z.ZodOptional<z.ZodString>;
147
- }, z.ZodTypeAny, "passthrough">>;
148
- clientExtensionResults: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
149
- authenticatorAttachment: z.ZodOptional<z.ZodString>;
150
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
151
- id: z.ZodString;
152
- rawId: z.ZodString;
153
- type: z.ZodLiteral<"public-key">;
154
- response: z.ZodObject<{
155
- clientDataJSON: z.ZodString;
156
- authenticatorData: z.ZodString;
157
- signature: z.ZodString;
158
- userHandle: z.ZodOptional<z.ZodString>;
159
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
160
- clientDataJSON: z.ZodString;
161
- authenticatorData: z.ZodString;
162
- signature: z.ZodString;
163
- userHandle: z.ZodOptional<z.ZodString>;
164
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
165
- clientDataJSON: z.ZodString;
166
- authenticatorData: z.ZodString;
167
- signature: z.ZodString;
168
- userHandle: z.ZodOptional<z.ZodString>;
169
- }, z.ZodTypeAny, "passthrough">>;
170
- clientExtensionResults: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
171
- authenticatorAttachment: z.ZodOptional<z.ZodString>;
172
- }, z.ZodTypeAny, "passthrough">>;
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
+ }>;
173
129
  }, "strict", z.ZodTypeAny, {
174
- assertion: {
175
- type: "public-key";
176
- id: string;
177
- rawId: string;
178
- response: {
179
- signature: string;
180
- clientDataJSON: string;
181
- authenticatorData: string;
182
- userHandle?: string | undefined;
183
- } & {
184
- [k: string]: unknown;
130
+ reauth: {
131
+ emailCode: {
132
+ code: string;
133
+ verificationId: string;
185
134
  };
186
- clientExtensionResults?: Record<string, unknown> | undefined;
187
- authenticatorAttachment?: string | undefined;
188
- } & {
189
- [k: string]: unknown;
135
+ totpCode?: string | undefined;
190
136
  };
191
137
  }, {
192
- assertion: {
193
- type: "public-key";
194
- id: string;
195
- rawId: string;
196
- response: {
197
- signature: string;
198
- clientDataJSON: string;
199
- authenticatorData: string;
200
- userHandle?: string | undefined;
201
- } & {
202
- [k: string]: unknown;
138
+ reauth: {
139
+ emailCode: {
140
+ code: string;
141
+ verificationId: string;
203
142
  };
204
- clientExtensionResults?: Record<string, unknown> | undefined;
205
- authenticatorAttachment?: string | undefined;
206
- } & {
207
- [k: string]: unknown;
143
+ totpCode?: string | undefined;
208
144
  };
209
145
  }>;
210
146
  export type IdentityLinkCompleteRequest = z.infer<typeof identityLinkCompleteRequestSchema>;
@@ -28,8 +28,8 @@ export declare const IDENTITY_PROOF_CHALLENGE_TTL_MS: number;
28
28
  */
29
29
  export declare const IDENTITY_PROOF_ACTIONS: {
30
30
  /**
31
- * Link Commons' root to a passkey account that has none (`POST /auth/link`).
32
- * The account becomes self-custodied and its recovery email is deleted
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
33
  * (ADR 0029 D3).
34
34
  */
35
35
  readonly link: "link_identity";
@@ -123,7 +123,7 @@ export type IdentityErrorCode = (typeof IDENTITY_ERROR_CODES)[keyof typeof IDENT
123
123
  export interface IdentityRootStatus {
124
124
  /** Whether Commons' root is linked: the account is self-custodied. */
125
125
  rootLinked: boolean;
126
- /** The recovery email of a passkey account; `null` once Commons is linked. */
126
+ /** The email of an account without a key; `null` once Commons is linked. */
127
127
  recoveryEmail: string | null;
128
128
  }
129
129
  export declare const identityRootStatusSchema: z.ZodType<IdentityRootStatus>;