@tempoxyz/id 0.0.1 → 0.0.2

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 (120) hide show
  1. package/dist/app/server/.vite/manifest.json +65 -65
  2. package/dist/app/server/assets/Grant-D_tun0mF.js.map +1 -1
  3. package/dist/app/server/assets/authorize-DXYo-K2n-B-6vqhvc.js +56 -0
  4. package/dist/app/server/assets/authorize-DXYo-K2n-B-6vqhvc.js.map +1 -0
  5. package/dist/app/server/assets/core-FSS9-r35.js +56 -0
  6. package/dist/app/server/assets/core-FSS9-r35.js.map +1 -0
  7. package/dist/app/server/assets/default-u_dwuiYb-Bby14ucv.js +2 -0
  8. package/dist/app/server/assets/{default-u_dwuiYb-DOrMMcg9.js.map → default-u_dwuiYb-Bby14ucv.js.map} +1 -1
  9. package/dist/app/server/assets/{dist-CoH0hD1s.js → dist-B9KiBJYw.js} +2 -2
  10. package/dist/app/server/assets/{dist-CoH0hD1s.js.map → dist-B9KiBJYw.js.map} +1 -1
  11. package/dist/app/server/assets/{dist-DcICI3fd.js → dist-CU1NXSiz.js} +2 -2
  12. package/dist/app/server/assets/{dist-DcICI3fd.js.map → dist-CU1NXSiz.js.map} +1 -1
  13. package/dist/app/server/assets/dist-CetH4JSY.js +1 -0
  14. package/dist/app/server/assets/{effect-QlVUlMFu-mXPkuQTb.js → effect-QlVUlMFu-Cm-eTtaO.js} +2 -2
  15. package/dist/app/server/assets/{effect-QlVUlMFu-mXPkuQTb.js.map → effect-QlVUlMFu-Cm-eTtaO.js.map} +1 -1
  16. package/dist/app/server/assets/factory-g3i_Ffi5.js +5 -0
  17. package/dist/app/server/assets/{factory-dvoOlKer.js.map → factory-g3i_Ffi5.js.map} +1 -1
  18. package/dist/app/server/assets/get-id-field-BpOAoqqI.js +6 -0
  19. package/dist/app/server/assets/get-id-field-BpOAoqqI.js.map +1 -0
  20. package/dist/app/server/assets/{index-CLddUTqr-CC33CMvS.js → index-CLddUTqr-CmgkbWi9.js} +2 -2
  21. package/dist/app/server/assets/{index-CLddUTqr-CC33CMvS.js.map → index-CLddUTqr-CmgkbWi9.js.map} +1 -1
  22. package/dist/app/server/assets/{index-DZEfthgZ-Ec2MMQ_e.js → index-DZEfthgZ-BnPmps9O.js} +2 -2
  23. package/dist/app/server/assets/{index-DZEfthgZ-Ec2MMQ_e.js.map → index-DZEfthgZ-BnPmps9O.js.map} +1 -1
  24. package/dist/app/server/assets/kysely-adapter-B8VoPArC.js +1 -0
  25. package/dist/app/server/assets/{schema-check-oLqvHR8y.js → schema-check-Bgwl1v0_.js} +2 -2
  26. package/dist/app/server/assets/{schema-check-oLqvHR8y.js.map → schema-check-Bgwl1v0_.js.map} +1 -1
  27. package/dist/app/server/assets/{sury-CWZTCd75-CDaOS2oM.js → sury-CWZTCd75-Bd_PzqCV.js} +2 -2
  28. package/dist/app/server/assets/{sury-CWZTCd75-CDaOS2oM.js.map → sury-CWZTCd75-Bd_PzqCV.js.map} +1 -1
  29. package/dist/app/server/assets/{valibot--1zFm7rT-CIKoH3MS.js → valibot--1zFm7rT-Dd2aEhWG.js} +2 -2
  30. package/dist/app/server/assets/{valibot--1zFm7rT-CIKoH3MS.js.map → valibot--1zFm7rT-Dd2aEhWG.js.map} +1 -1
  31. package/dist/app/server/assets/{valibot-D_HTw1Gn-BdPyLkXt.js → valibot-D_HTw1Gn-C1l3va6F.js} +2 -2
  32. package/dist/app/server/assets/{valibot-D_HTw1Gn-BdPyLkXt.js.map → valibot-D_HTw1Gn-C1l3va6F.js.map} +1 -1
  33. package/dist/app/server/assets/{zod-Bwrt9trS-BezI5Gi1.js → zod-Bwrt9trS-Dry2w0in.js} +2 -2
  34. package/dist/app/server/assets/{zod-Bwrt9trS-BezI5Gi1.js.map → zod-Bwrt9trS-Dry2w0in.js.map} +1 -1
  35. package/dist/app/server/assets/{zod-DSgpEGAE-DZ7M_Q33.js → zod-DSgpEGAE-BZMP86UA.js} +2 -2
  36. package/dist/app/server/assets/{zod-DSgpEGAE-DZ7M_Q33.js.map → zod-DSgpEGAE-BZMP86UA.js.map} +1 -1
  37. package/dist/app/server/index.js +35 -35
  38. package/dist/app/server/index.js.map +1 -1
  39. package/dist/client/Auth.d.ts +1 -1
  40. package/dist/client/Auth.d.ts.map +1 -1
  41. package/dist/client/Auth.js.map +1 -1
  42. package/dist/client/Decorator.d.ts +3 -1
  43. package/dist/client/Decorator.d.ts.map +1 -1
  44. package/dist/client/Decorator.js +1 -0
  45. package/dist/client/Decorator.js.map +1 -1
  46. package/dist/client/actions/passkey.d.ts +63 -2
  47. package/dist/client/actions/passkey.d.ts.map +1 -1
  48. package/dist/client/actions/passkey.js +121 -3
  49. package/dist/client/actions/passkey.js.map +1 -1
  50. package/dist/internal/Apps.d.ts +14 -0
  51. package/dist/internal/Apps.d.ts.map +1 -1
  52. package/dist/internal/Apps.js +14 -2
  53. package/dist/internal/Apps.js.map +1 -1
  54. package/dist/internal/Auth.d.ts +7 -0
  55. package/dist/internal/Auth.d.ts.map +1 -1
  56. package/dist/internal/Auth.js +2 -1
  57. package/dist/internal/Auth.js.map +1 -1
  58. package/dist/internal/Authorize.d.ts.map +1 -1
  59. package/dist/internal/Authorize.js +13 -13
  60. package/dist/internal/Authorize.js.map +1 -1
  61. package/dist/internal/ErrorResponse.d.ts +5 -0
  62. package/dist/internal/ErrorResponse.d.ts.map +1 -1
  63. package/dist/internal/ErrorResponse.js +8 -0
  64. package/dist/internal/ErrorResponse.js.map +1 -1
  65. package/dist/internal/KeyAuthorization.d.ts.map +1 -1
  66. package/dist/internal/KeyAuthorization.js +28 -28
  67. package/dist/internal/KeyAuthorization.js.map +1 -1
  68. package/dist/internal/Owner.d.ts +16 -0
  69. package/dist/internal/Owner.d.ts.map +1 -1
  70. package/dist/internal/Owner.js.map +1 -1
  71. package/dist/internal/Passkey.d.ts +66 -4
  72. package/dist/internal/Passkey.d.ts.map +1 -1
  73. package/dist/internal/Passkey.js +66 -30
  74. package/dist/internal/Passkey.js.map +1 -1
  75. package/dist/internal/Privy.d.ts +27 -0
  76. package/dist/internal/Privy.d.ts.map +1 -1
  77. package/dist/internal/Privy.js +82 -0
  78. package/dist/internal/Privy.js.map +1 -1
  79. package/dist/internal/TempoAccount.d.ts +57 -0
  80. package/dist/internal/TempoAccount.d.ts.map +1 -1
  81. package/dist/internal/TempoAccount.js +118 -5
  82. package/dist/internal/TempoAccount.js.map +1 -1
  83. package/dist/internal/owners/Privy.d.ts.map +1 -1
  84. package/dist/internal/owners/Privy.js +11 -2
  85. package/dist/internal/owners/Privy.js.map +1 -1
  86. package/package.json +1 -1
  87. package/src/api/account.ts +140 -103
  88. package/src/api/authenticate.ts +48 -51
  89. package/src/api/authorize.ts +35 -36
  90. package/src/api/device.ts +20 -23
  91. package/src/api/health.ts +1 -2
  92. package/src/api/lib/AccountStep.ts +75 -0
  93. package/src/api/lib/Auth.ts +6 -0
  94. package/src/api/lib/Cors.ts +3 -0
  95. package/src/api/lib/KeySigner.ts +1 -1
  96. package/src/api/oauth.ts +23 -22
  97. package/src/client/Auth.ts +1 -0
  98. package/src/client/Decorator.ts +6 -1
  99. package/src/client/actions/passkey.ts +197 -3
  100. package/src/internal/Apps.ts +26 -2
  101. package/src/internal/Auth.ts +4 -0
  102. package/src/internal/Authorize.ts +13 -15
  103. package/src/internal/ErrorResponse.ts +10 -0
  104. package/src/internal/KeyAuthorization.ts +28 -31
  105. package/src/internal/OpenApi.ts +35 -0
  106. package/src/internal/Owner.ts +16 -0
  107. package/src/internal/Passkey.ts +99 -34
  108. package/src/internal/Privy.ts +128 -0
  109. package/src/internal/TempoAccount.ts +221 -7
  110. package/src/internal/owners/Privy.ts +19 -6
  111. package/dist/app/server/assets/authorize-DXYo-K2n-rmQQ42x1.js +0 -56
  112. package/dist/app/server/assets/authorize-DXYo-K2n-rmQQ42x1.js.map +0 -1
  113. package/dist/app/server/assets/core-BBrZXfrB.js +0 -56
  114. package/dist/app/server/assets/core-BBrZXfrB.js.map +0 -1
  115. package/dist/app/server/assets/default-u_dwuiYb-DOrMMcg9.js +0 -2
  116. package/dist/app/server/assets/dist-CR6FW-yS.js +0 -1
  117. package/dist/app/server/assets/factory-dvoOlKer.js +0 -5
  118. package/dist/app/server/assets/get-id-field-OwLpfjj2.js +0 -6
  119. package/dist/app/server/assets/get-id-field-OwLpfjj2.js.map +0 -1
  120. package/dist/app/server/assets/kysely-adapter-B8tQwTcu.js +0 -1
@@ -45,17 +45,17 @@ namespace schema {
45
45
  .union([
46
46
  z.literal(true).meta({
47
47
  description:
48
- "Requests the passkey step for an account that a passkey owns, in place of `webauthn_required`. The passkey's relying party must cover the page's origin.",
48
+ "Returns a passkey step for a passkey-owned account, in place of `webauthn_required`. The passkey's relying party must cover the page's origin.",
49
49
  }),
50
50
  z.object({ assertion: Passkey.schema.assertion }).meta({
51
51
  description:
52
- "The passkey's assertion over the options of the passkey step. The repeated request signs the key with it.",
52
+ "The passkey's assertion over the passkey step's challenge, which signs the key.",
53
53
  }),
54
54
  ])
55
55
  .optional()
56
56
  .meta({
57
57
  description:
58
- "Whether the app's page signs the key with the passkey that owns the account. Absent, a passkey-owned account refuses the key with `webauthn_required`.",
58
+ "Whether the page can sign the key with the account's passkey. Without it, a passkey-owned account returns `webauthn_required`.",
59
59
  }),
60
60
  }),
61
61
  )
@@ -69,7 +69,7 @@ namespace schema {
69
69
  export const json = z.object({
70
70
  account: TempoAccount.schema.tempoAccount.shape.address.meta({
71
71
  description:
72
- "The address of the account that the user selects, one of the user's accounts in the app's realms.",
72
+ "The address of the selected account, one of the user's accounts in the app's realms.",
73
73
  examples: ['0x8C3b5d0e2A4f6B1c9D7e3F5a0B2c4D6e8F1a3B5c'],
74
74
  }),
75
75
  oauth_query: z
@@ -78,7 +78,7 @@ namespace schema {
78
78
  .max(8192)
79
79
  .meta({
80
80
  description:
81
- "The account step's query string, which Better Auth signs when it sends the browser there. At most 8,192 characters.",
81
+ "The signed query string of the account page, which names the app's pending authorization request.",
82
82
  examples: [
83
83
  'client_id=Qk3vT9xZbLmN2pRw8YcHjF6sDaE4uGtK&response_type=code&exp=1760000000&sig=Gx3kB0',
84
84
  ],
@@ -88,7 +88,7 @@ namespace schema {
88
88
  .optional()
89
89
  .meta({
90
90
  description:
91
- "The passkey's assertion over the options of the passkey step, which the selection of a passkey-owned account returns. The repeated selection signs the key with it.",
91
+ "The passkey's assertion over the passkey step's challenge, which signs the key.",
92
92
  }),
93
93
  })
94
94
 
@@ -97,7 +97,7 @@ namespace schema {
97
97
  .object({
98
98
  redirect: z.string().meta({
99
99
  description:
100
- "Where the browser continues: the approval page's path on Tempo ID, or the app's `redirect_uri` with an authorization code and `state`.",
100
+ "The URL to send the browser to next: the approval page, or the app's `redirect_uri` with a code.",
101
101
  examples: [
102
102
  'https://app.example.com/callback?code=Vb7mQ2xLkP9sTzR4&state=af0ifjsldkj',
103
103
  ],
@@ -115,12 +115,12 @@ namespace schema {
115
115
  export const json = z.object({
116
116
  accept: z.boolean().meta({
117
117
  description:
118
- 'Whether the user approves the request. `false` denies it, and the app receives `access_denied`.',
118
+ 'Whether the user approves the request. `false` returns `access_denied` to the app.',
119
119
  examples: [true],
120
120
  }),
121
121
  account: TempoAccount.schema.tempoAccount.shape.address.optional().meta({
122
122
  description:
123
- "The Tempo Account that the approval page showed for the request's key authorization, one of the user's accounts in the app's realms. Ignored when the key names its account, and absent for a request without a key.",
123
+ 'The Tempo Account that signs the key, as the approval page showed it. Ignored if the key names its account.',
124
124
  examples: ['0x8C3b5d0e2A4f6B1c9D7e3F5a0B2c4D6e8F1a3B5c'],
125
125
  }),
126
126
  oauth_query: z
@@ -129,7 +129,7 @@ namespace schema {
129
129
  .max(8192)
130
130
  .meta({
131
131
  description:
132
- "The approval page's query string, which Better Auth signs when it sends the browser there. At most 8,192 characters.",
132
+ "The signed query string of the approval page, which names the app's pending authorization request.",
133
133
  examples: [
134
134
  'client_id=Qk3vT9xZbLmN2pRw8YcHjF6sDaE4uGtK&response_type=code&exp=1760000000&sig=Gx3kB0',
135
135
  ],
@@ -139,7 +139,7 @@ namespace schema {
139
139
  .optional()
140
140
  .meta({
141
141
  description:
142
- "The passkey's assertion over the options of the passkey step, which a passkey-owned account's approval returns. The repeated approval signs the key with it.",
142
+ "The passkey's assertion over the passkey step's challenge, which signs the key.",
143
143
  }),
144
144
  session_id: z
145
145
  .string()
@@ -147,7 +147,7 @@ namespace schema {
147
147
  .max(256)
148
148
  .meta({
149
149
  description:
150
- 'The ID of the session that the approval page showed. The decision applies only while that session is the signed-in one.',
150
+ 'The session ID that the approval page showed. The decision fails if another session signed in since.',
151
151
  examples: ['8nVq3kZ0bT5wXy2mLp7sR4dA'],
152
152
  }),
153
153
  skip_upgrade: z
@@ -155,7 +155,7 @@ namespace schema {
155
155
  .optional()
156
156
  .meta({
157
157
  description:
158
- "Whether the approval skips the pending upgrade of a wallet.tempo passkey's wallet on the key's chain, `false` by default. The passkey then signs the key as its own account, and the key stops working on that chain once the wallet upgrades, unless the chain registered it first.",
158
+ "Whether to sign the key before a pending upgrade of the wallet.tempo passkey's account. The key then stops working after the upgrade, unless the upgrade registers it.",
159
159
  examples: [true],
160
160
  }),
161
161
  })
@@ -167,7 +167,7 @@ namespace schema {
167
167
  .object({
168
168
  redirect: z.url().meta({
169
169
  description:
170
- "The app's `redirect_uri`, with an authorization code and `state`, or with `error=access_denied` when the user denied the request.",
170
+ "The app's `redirect_uri`, with a code, or with `error=access_denied` for a denial.",
171
171
  examples: [
172
172
  'https://app.example.com/callback?code=Vb7mQ2xLkP9sTzR4&state=af0ifjsldkj',
173
173
  ],
@@ -186,7 +186,7 @@ namespace schema {
186
186
  .max(8192)
187
187
  .meta({
188
188
  description:
189
- "The approval page's query string, which Better Auth signs when it sends the browser there. At most 8,192 characters.",
189
+ "The signed query string of the approval page, which names the app's pending authorization request.",
190
190
  examples: [
191
191
  'client_id=Qk3vT9xZbLmN2pRw8YcHjF6sDaE4uGtK&response_type=code&exp=1760000000&sig=Gx3kB0',
192
192
  ],
@@ -198,15 +198,14 @@ namespace schema {
198
198
  .object({
199
199
  redirect: z.url().meta({
200
200
  description:
201
- "The app's `redirect_uri`, with the refusal of the request's key as `error` and `error_description`, `state`, and `iss`.",
201
+ "The app's `redirect_uri`, with the key's refusal as `error` and `error_description`.",
202
202
  examples: [
203
203
  'https://app.example.com/callback?error=invalid_request&state=af0ifjsldkj',
204
204
  ],
205
205
  }),
206
206
  })
207
207
  .meta({
208
- description:
209
- 'A key that Tempo ID refuses, which returns the browser to the app.',
208
+ description: 'A refused key, which returns the browser to the app.',
210
209
  }),
211
210
  z
212
211
  .object({
@@ -225,16 +224,16 @@ namespace schema {
225
224
  .optional()
226
225
  .meta({
227
226
  description:
228
- "The Tempo Account that the approval applies to: the account that the key names, else the app's selected account, else the user's oldest. Present only when the user holds more than one account in the app's realms.",
227
+ "The Tempo Account that the approval applies to. Present only if the user has more than one account in the app's realms.",
229
228
  }),
230
229
  app: z.string().meta({
231
230
  description:
232
- "The app's display name, or the host of its redirect URI without one.",
231
+ "The app's display name, or the host of its redirect URI if it has none.",
233
232
  examples: ['Tempo Cash'],
234
233
  }),
235
234
  destination: z.string().meta({
236
235
  description:
237
- "The host of the app's redirect URI, or its scheme for a native app. For a third-party app, the host that serves its metadata document.",
236
+ "The host of the app's redirect URI, or its scheme for a native app. For a third-party app, the host of its metadata document.",
238
237
  examples: ['cash.tempo.xyz'],
239
238
  }),
240
239
  email: z.email().meta({
@@ -255,7 +254,7 @@ namespace schema {
255
254
  }),
256
255
  name: Chain.schema.optional().meta({
257
256
  description:
258
- 'The name that `GET /v1/chains` lists for the chain. Absent for a chain that no name lists.',
257
+ "The chain's name in `GET /v1/chains`. Absent for an unlisted chain.",
259
258
  examples: ['testnet'],
260
259
  }),
261
260
  })
@@ -265,7 +264,7 @@ namespace schema {
265
264
  .optional()
266
265
  .meta({
267
266
  description:
268
- "The message that the key's witness binds, such as a sign-in challenge from the app. Absent when the request carries none.",
267
+ "The message that the key's witness binds, such as a sign-in challenge from the app.",
269
268
  examples: [
270
269
  'Sign in to app.example.com\nNonce: 7f3a9c2e5b1d4f60',
271
270
  ],
@@ -275,7 +274,7 @@ namespace schema {
275
274
  .optional()
276
275
  .meta({
277
276
  description:
278
- 'What the key that the request carries may do, and the account that approval signs for. Absent for a request without a key.',
277
+ "The requested key's permissions, and the account that signs it. Absent without a key.",
279
278
  }),
280
279
  logo: z
281
280
  .string()
@@ -283,24 +282,24 @@ namespace schema {
283
282
  .optional()
284
283
  .meta({
285
284
  description:
286
- "The app's logo as a `data:` URL, which Tempo ID fetched from the origin of its client ID. Present only for an app with an origin client ID whose manifest or page names a logo.",
285
+ "The app's logo, as a `data:` URL. Present only for a third-party app whose site names a logo.",
287
286
  examples: ['data:image/png;base64,iVBORw0KGgo='],
288
287
  }),
289
288
  session: z.string().meta({
290
289
  description:
291
- 'The ID of the signed-in session, which `POST /v1/authorize/consent` names.',
290
+ 'The ID of the signed-in session, to send to `POST /v1/authorize/consent`.',
292
291
  examples: ['8nVq3kZ0bT5wXy2mLp7sR4dA'],
293
292
  }),
294
293
  verified: z.boolean().meta({
295
294
  description:
296
- 'Whether the app is a registered app, whose name the page marks as verified. A third-party app names itself in its metadata document.',
295
+ 'Whether the app is a registered app, which the page marks as verified.',
297
296
  examples: [true],
298
297
  }),
299
298
  })
300
299
  .nullable()
301
300
  .meta({
302
301
  description:
303
- 'The request that the approval page shows, or `null` for a forged or expired query, or a request without a known app.',
302
+ 'The request, or `null` for an invalid query or an unknown app.',
304
303
  }),
305
304
  })
306
305
  .meta({ description: 'The request that the approval page shows.' }),
@@ -328,7 +327,7 @@ namespace schema {
328
327
  }),
329
328
  balance: Balance.schema.balance.nullable().meta({
330
329
  description:
331
- "The account's holdings of USD tokens on the chain of the request's key, or of Tempo mainnet without one. `null` when the read fails or takes longer than 3 seconds.",
330
+ "The account's USD balance on the key's chain, or on Tempo mainnet without a key. `null` if the balance cannot be read.",
332
331
  }),
333
332
  owner:
334
333
  TempoAccount.schema.tempoAccount.shape.config.shape.owners.unwrap()
@@ -356,7 +355,7 @@ namespace schema {
356
355
  .nullable()
357
356
  .meta({
358
357
  description:
359
- 'The account step, or `null` for a forged or expired query, or a request without a known app.',
358
+ 'The account step, or `null` for an invalid query or an unknown app.',
360
359
  }),
361
360
  })
362
361
  }
@@ -535,7 +534,7 @@ export const authorize = new Hono<Env>()
535
534
  '/v1/authorize',
536
535
  OpenApi.describeRoute({
537
536
  description:
538
- "Authorizes the app for the signed-in user without a redirect, from a first-party app's page on the redirect URI's origin. It returns a code with `code_challenge`, or tokens to a public app without one, and signs any key within the headless ceiling. With `passkey: true`, a passkey-owned account returns the passkey step, and the repeated request with `passkey: { assertion }` and the step's `expiry` signs the key.",
537
+ "Headless authorization: authorizes the app from a first-party app's page without a redirect, and returns a code or tokens with any signed key. With `passkey: true`, a passkey-owned account first returns a passkey step, and the repeat with its assertion signs the key.",
539
538
  responses: OpenApi.responses({
540
539
  errors: [
541
540
  ...Authorize.check.errors,
@@ -614,7 +613,7 @@ export const authorize = new Hono<Env>()
614
613
  '/v1/authorize/account',
615
614
  OpenApi.describeRoute({
616
615
  description:
617
- "Selects the account that the app of `oauth_query` uses, and returns the next redirect of the authorization. The selection holds for the app's later authorizations in the session, and `GET /v1/account` lists it first.",
616
+ "Redirect authorization, account page: selects the Tempo Account that the app uses, and returns the next redirect. The selection holds for the app's later authorizations in the session.",
618
617
  responses: OpenApi.responses({
619
618
  errors: [
620
619
  ...BodyLimit.errors,
@@ -754,7 +753,7 @@ export const authorize = new Hono<Env>()
754
753
  '/v1/authorize/consent',
755
754
  OpenApi.describeRoute({
756
755
  description:
757
- "Records the signed-in user's decision on the request in `oauth_query`, stores an approval's consent, and returns the app's redirect. An approval of a key authorization signs the key. If the key names no account and the user holds none, the approval first creates an account.",
756
+ "Redirect authorization, approval page: records the user's decision, and returns the redirect to the app. An approval stores the consent and signs any key.",
758
757
  responses: OpenApi.responses({
759
758
  errors: [
760
759
  ...Authorize.checkKey.errors,
@@ -891,7 +890,7 @@ export const authorize = new Hono<Env>()
891
890
  '/v1/authorize/request',
892
891
  OpenApi.describeRoute({
893
892
  description:
894
- "Reads the authorization request in `oauth_query` as the approval page shows it: the app, the destination, the account, and any key. For a key that Tempo ID refuses, it returns the app's redirect with the refusal.",
893
+ 'Redirect authorization, approval page: returns the app, the Tempo Account, and any key of the request in `oauth_query`. For a refused key, it returns the redirect to the app with the refusal.',
895
894
  responses: OpenApi.responses({
896
895
  errors: [
897
896
  ...OpenApi.validate.errors,
@@ -927,7 +926,7 @@ export const authorize = new Hono<Env>()
927
926
  '/i/authorize/request',
928
927
  OpenApi.describeRoute({
929
928
  description:
930
- "Reads the app's authorization request that `oauth_query` carries, as `GET /v1/authorize/request` does, without the account that the approval applies to, the account that approval signs for, or why approval cannot sign the key. The read holds no account, so it does not count against the user's `ACCOUNT_LIMIT`. Internal: only Tempo ID's approval page sends the request, so the public document leaves it out.",
929
+ "Returns the request in `oauth_query`, as `GET /v1/authorize/request` does, without its accounts. Internal to Tempo ID's approval page.",
931
930
  // Internal routes stay out of `/openapi.json` and `/docs`.
932
931
  hide: true,
933
932
  responses: OpenApi.responses({
@@ -959,7 +958,7 @@ export const authorize = new Hono<Env>()
959
958
  '/v1/authorize/accounts',
960
959
  OpenApi.describeRoute({
961
960
  description:
962
- "Reads the account step of the authorization request in `oauth_query`: the app, and the user's accounts in its realms, oldest first. Each account carries its balance on the chain of the request's key, or of Tempo mainnet without a key.",
961
+ "Redirect authorization, account page: returns the app of the request in `oauth_query`, and the user's Tempo Accounts in its realms with their balances.",
963
962
  responses: OpenApi.responses({
964
963
  errors: [
965
964
  ...AccountStep.accounts.errors,
package/src/api/device.ts CHANGED
@@ -48,41 +48,39 @@ namespace schema {
48
48
  })
49
49
  .optional()
50
50
  .meta({
51
- description:
52
- 'The app that requested the code. Absent when the app has no display name.',
51
+ description: 'The app that requested the code.',
53
52
  }),
54
53
  challenge: z
55
54
  .string()
56
55
  .optional()
57
56
  .meta({
58
57
  description:
59
- "The message whose EIP-191 hash is the key's witness, which approval signs. Absent when the CLI sent none.",
58
+ "The message that the key's witness binds, as an EIP-191 message.",
60
59
  examples: ['Sign in to app.example.com\nNonce: 7f3a9c2e5b1d4f60'],
61
60
  }),
62
61
  created_at: z.number().meta({
63
- description: 'When the CLI requested the code, in Unix seconds.',
62
+ description: 'When the device requested the code, in Unix seconds.',
64
63
  examples: [1792075800],
65
64
  }),
66
65
  device: z
67
66
  .string()
68
67
  .optional()
69
68
  .meta({
70
- description:
71
- 'The name that the CLI reported, such as its hostname. Absent for a code from `POST /device/code`.',
69
+ description: "The device's name, such as its hostname.",
72
70
  examples: ['ada-macbook'],
73
71
  }),
74
72
  ip: z
75
73
  .string()
76
74
  .optional()
77
75
  .meta({
78
- description: "The CLI's IP address. Absent when it is unknown.",
76
+ description: "The device's IP address.",
79
77
  examples: ['203.0.113.7'],
80
78
  }),
81
79
  key_authorization: KeyAuthorization.schema.description
82
80
  .extend({
83
81
  account: KeyAuthorization.schema.description.shape.account.meta({
84
82
  description:
85
- 'The Tempo Account that approval signs for: the one the request names, else the one the user selected for the app, else the oldest. Absent when the request names none and the accounts cannot be read, or the user holds none.',
83
+ 'The Tempo Account that signs the key. Absent if the user has none, or the accounts cannot be read.',
86
84
  examples: ['0x9a073914db0d149375ac2b7fbb72c68c0780fb5f'],
87
85
  }),
88
86
  chain: z
@@ -96,40 +94,40 @@ namespace schema {
96
94
  }),
97
95
  name: Chain.schema.optional().meta({
98
96
  description:
99
- 'The name that `GET /v1/chains` lists for the chain. Absent for a chain that no name lists.',
97
+ "The chain's name in `GET /v1/chains`. Absent for an unlisted chain.",
100
98
  examples: ['testnet'],
101
99
  }),
102
100
  })
103
101
  .optional()
104
102
  .meta({
105
103
  description:
106
- 'The chain that the key acts on. Absent once the user decides.',
104
+ 'The chain that the key acts on. Absent after the user decides.',
107
105
  }),
108
106
  refusal: KeySigner.schema.refusal.optional(),
109
107
  })
110
108
  .optional()
111
109
  .meta({
112
110
  description:
113
- 'What the key that the CLI requested may do. Absent when the CLI requested no key.',
111
+ "The requested key's permissions. Absent without a key.",
114
112
  }),
115
113
  location: z
116
114
  .string()
117
115
  .optional()
118
116
  .meta({
119
117
  description:
120
- "The CLI's city and country, from its IP address. Absent when they are unknown.",
118
+ "The device's city and country, from its IP address.",
121
119
  examples: ['Berlin, DE'],
122
120
  }),
123
121
  status: z.enum(['approved', 'denied', 'pending']).meta({
124
122
  description:
125
- 'Whether the user approved or denied the code. `pending` until the user decides.',
123
+ 'Whether the user approved or denied the code, or `pending`.',
126
124
  examples: ['pending'],
127
125
  }),
128
126
  user_code: z
129
127
  .string()
130
128
  .meta({ description: 'The user code.', examples: ['WDJB-MJHT'] }),
131
129
  })
132
- .meta({ description: 'The request that the CLI made with the code.' }),
130
+ .meta({ description: "The device's request." }),
133
131
  })
134
132
  }
135
133
 
@@ -137,7 +135,7 @@ namespace schema {
137
135
  export const json = z.object({
138
136
  account: TempoAccount.schema.tempoAccount.shape.address.optional().meta({
139
137
  description:
140
- "The Tempo Account that the device page showed for the request's key authorization, one of the user's accounts in the app's realms. Ignored when the key names its account, and absent for a request without a key.",
138
+ 'The Tempo Account that signs the key, as the device page showed it. Ignored if the key names its account.',
141
139
  examples: ['0x8C3b5d0e2A4f6B1c9D7e3F5a0B2c4D6e8F1a3B5c'],
142
140
  }),
143
141
  code: z
@@ -149,7 +147,7 @@ namespace schema {
149
147
  .optional()
150
148
  .meta({
151
149
  description:
152
- "The passkey's assertion over the options of the passkey step, which a passkey-owned account's approval returns. The repeated approval signs the key with it.",
150
+ "The passkey's assertion over the passkey step's challenge, which signs the key.",
153
151
  }),
154
152
  })
155
153
 
@@ -160,7 +158,7 @@ namespace schema {
160
158
  .object({
161
159
  status: z.literal('approved').meta({
162
160
  description:
163
- 'The user approved the code. The CLI receives its tokens, and any signed key, on its next poll.',
161
+ "The device's next poll returns its tokens and any signed key.",
164
162
  }),
165
163
  })
166
164
  .meta({ description: 'A stored approval.' }),
@@ -173,8 +171,7 @@ namespace schema {
173
171
 
174
172
  export const response = z.object({
175
173
  status: z.literal('denied').meta({
176
- description:
177
- 'The user denied the code. The next poll returns `access_denied`.',
174
+ description: "The device's next poll returns `access_denied`.",
178
175
  }),
179
176
  })
180
177
  }
@@ -344,7 +341,7 @@ export const device = new Hono<Env>()
344
341
  '/v1/device/requests/:code',
345
342
  OpenApi.describeRoute({
346
343
  description:
347
- 'Reads the request of a device code for the device page, by its user code. The first signed-in user to read a pending code binds it, so no other user can decide it.',
344
+ 'Device grant, step 2 of 4: returns the request of a user code, as the device page shows it. The first signed-in user to read a pending code binds it.',
348
345
  responses: OpenApi.responses({
349
346
  errors: [
350
347
  ...OpenApi.validate.errors,
@@ -354,7 +351,7 @@ export const device = new Hono<Env>()
354
351
  ErrorResponse.CrossSiteRequestError,
355
352
  ],
356
353
  success: {
357
- description: 'What the CLI requested.',
354
+ description: "The device's request.",
358
355
  schema: schema.requests.response,
359
356
  },
360
357
  }),
@@ -437,7 +434,7 @@ export const device = new Hono<Env>()
437
434
  '/v1/device/approve',
438
435
  OpenApi.describeRoute({
439
436
  description:
440
- "Approves a device code for the signed-in user, and the CLI's next poll returns its tokens and any signed key. If the key names no account and the user holds none, the approval first creates an account. Only Tempo ID's device page sends the request.",
437
+ "Device grant, step 3 of 4: approves the device code, so the device's next poll of `/oauth2/token` returns tokens and any signed key.",
441
438
  responses: OpenApi.responses({
442
439
  errors: [
443
440
  ...BodyLimit.errors,
@@ -485,7 +482,7 @@ export const device = new Hono<Env>()
485
482
  '/v1/device/deny',
486
483
  OpenApi.describeRoute({
487
484
  description:
488
- "Denies a device code for the signed-in user, and the CLI's next poll returns `access_denied`. Only Tempo ID's device page sends the request.",
485
+ "Device grant, step 3 of 4: denies the device code, so the device's next poll of `/oauth2/token` returns `access_denied`.",
489
486
  responses: OpenApi.responses({
490
487
  errors: [
491
488
  ...BodyLimit.errors,
package/src/api/health.ts CHANGED
@@ -54,8 +54,7 @@ export const health = new Hono<
54
54
  .get(
55
55
  '/health',
56
56
  OpenApi.describeRoute({
57
- description:
58
- 'Responds while the Worker is up, with the version that served the request.',
57
+ description: 'Returns the version of the Worker that serves the request.',
59
58
  responses: OpenApi.responses({
60
59
  success: {
61
60
  description: 'The Worker is up.',
@@ -71,6 +71,81 @@ export namespace accounts {
71
71
  export const errors = TempoAccount.held.errors
72
72
  }
73
73
 
74
+ /**
75
+ * Returns every address of the Tempo Accounts that the user holds in the realms of the registered app
76
+ * with `clientId`, as `TempoAccount.addresses` reads them, for an access token's `tempo_accounts` claim.
77
+ * It creates no account and never throws for an upstream kind that fails or misses its deadline,
78
+ * such as Privy during an outage. It logs the failure, and leaves out that kind's addresses.
79
+ */
80
+ export async function addresses(c: Context<Env>, options: addresses.Options) {
81
+ // Each Privy read spends the quota that every user shares, so the user's grants count against
82
+ // `ACCOUNT_LIMIT`, apart from the user's account reads. Only registered apps, which the seeds
83
+ // list, receive the claim, and each counts on its own, so no other app's loop of refreshes strips
84
+ // its tokens. Past the limit, the grant leaves out Privy's addresses, and reads Privy no more.
85
+ const [admitted, reached] = await Promise.all([
86
+ RateLimit.count(c, {
87
+ binding: 'ACCOUNT_LIMIT',
88
+ key: `${options.userId}:${options.clientId}`,
89
+ }),
90
+ Timing.measure(c, realms(c, options), 'AccountStep.realms'),
91
+ ])
92
+ if (!admitted)
93
+ Log.warn(c, {
94
+ fields: { clientId: options.clientId },
95
+ message:
96
+ 'An access token left out the Privy addresses of a user past the token read limit.',
97
+ })
98
+
99
+ const result = await Timing.measure(
100
+ c,
101
+ TempoAccount.addresses(c, {
102
+ db,
103
+ env,
104
+ maxAge: addresses.maxAge,
105
+ realms: reached,
106
+ timeout: addresses.timeout,
107
+ upstream: admitted,
108
+ userId: options.userId,
109
+ waitUntil: (promise) => c.executionCtx.waitUntil(promise),
110
+ warn: (entry) => Log.warn(c, entry),
111
+ }),
112
+ 'TempoAccount.addresses',
113
+ )
114
+ for (const error of result.errors)
115
+ Log.warn(c, {
116
+ error,
117
+ fields: { clientId: options.clientId },
118
+ message:
119
+ 'An access token left out the addresses of an owner kind that failed to list.',
120
+ })
121
+ return result.addresses
122
+ }
123
+
124
+ export declare namespace addresses {
125
+ type Options = {
126
+ /** The app's client ID. */
127
+ clientId: string
128
+ /** The user's ID. */
129
+ userId: string
130
+ }
131
+ }
132
+
133
+ export namespace addresses {
134
+ /**
135
+ * How old, in seconds, the user's Privy wallets that a grant reads from the data center's cache
136
+ * may be. A refresh within this time reads Privy no more, and a wallet that Privy creates
137
+ * elsewhere appears within it.
138
+ */
139
+ export const maxAge = 60
140
+
141
+ /**
142
+ * How long each owner kind that reads an upstream service may list, in milliseconds, so a slow
143
+ * Privy delays no token by more. Privy's own client waits up to 8 seconds for each of two
144
+ * attempts.
145
+ */
146
+ export const timeout = 750
147
+ }
148
+
74
149
  /**
75
150
  * Returns whether the first account of a user with `email`, in the realms of the app with
76
151
  * `clientId`, waits for the upgrade of a wallet.tempo passkey of the email, as {@link create} reads
@@ -40,6 +40,12 @@ export const middleware = createMiddleware<Env>(async (c, next) => {
40
40
  export function create(c: Context<Env>): core_Auth.Instance {
41
41
  return betterAuth(
42
42
  core_Auth.options({
43
+ accounts: (options) =>
44
+ Timing.measure(
45
+ c,
46
+ AccountStep.addresses(c, options),
47
+ 'AccountStep.addresses',
48
+ ),
43
49
  // A request that already chose the account, such as `POST /v1/authorize`, needs no account step.
44
50
  // A key names its chain, where each account is listed at its address there, so the step reads
45
51
  // the accounts that a selection names.
@@ -34,6 +34,9 @@ const maxAge = 7200
34
34
  */
35
35
  const paths = new Set([
36
36
  '/v1/account',
37
+ '/v1/account/passkey',
38
+ '/v1/account/passkey/options',
39
+ '/v1/account/passkey/verify',
37
40
  '/v1/account/upgrade',
38
41
  '/v1/auth/code',
39
42
  '/v1/auth/code/verify',
@@ -44,7 +44,7 @@ export namespace schema {
44
44
  ])
45
45
  .meta({
46
46
  description:
47
- "Why approval cannot sign the key: the key names another user's account (`foreign_account`), the user holds no account and none can be created (`no_account`), a passkey that this page cannot use owns the account (`passkey`), the accounts could not be read (`unavailable`), or the account must upgrade first (`upgrade`). Absent when approval can sign it.",
47
+ "Why the approval cannot sign the key: another user's account (`foreign_account`), no account (`no_account`), an unusable passkey (`passkey`), unreadable accounts (`unavailable`), or a pending upgrade (`upgrade`).",
48
48
  examples: ['passkey'],
49
49
  })
50
50
  }