@tempoxyz/auth 0.0.0-pr-6.sha-17bb901 → 0.0.0-pr-6.sha-fe206c7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,217 @@
1
+ # @tempoxyz/auth
2
+
3
+ Sign users in to Tempo through Tempo Auth, and get back their tokens for Tempo APIs and a [viem](https://viem.sh) access key account on their Tempo Account.
4
+
5
+ Every sign-in returns the same `Authorization`. Send transactions with its `account` through viem, and call Tempo APIs with its `tokens.accessToken`.
6
+
7
+ ```ts
8
+ import { Actions, Auth } from '@tempoxyz/auth'
9
+ import { Addresses, createClient, Expiry, Scopes } from 'viem/tempo'
10
+ import { tempoTestnet } from 'viem/tempo/chains'
11
+
12
+ const auth = Auth.create({
13
+ clientId: 'client_…',
14
+ url: 'https://auth.tempo.xyz',
15
+ })
16
+ const client = createClient({
17
+ chain: tempoTestnet,
18
+ feeToken: Addresses.pathUsd,
19
+ })
20
+
21
+ const authorization = await Actions.authorize(auth, {
22
+ accessKey: {
23
+ chainId: tempoTestnet.id,
24
+ expiry: Expiry.days(1),
25
+ limits: [{ limit: 10_000_000n, token: Addresses.pathUsd }],
26
+ scopes: [Scopes.tip20(Addresses.pathUsd).transfer()],
27
+ },
28
+ })
29
+
30
+ await client.token.transferSync({
31
+ account: authorization!.account,
32
+ amount: 10_000n,
33
+ to: '0x…',
34
+ token: Addresses.pathUsd,
35
+ })
36
+ ```
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pnpm add @tempoxyz/auth viem
42
+ ```
43
+
44
+ viem is a peer dependency.
45
+
46
+ ## Before you start
47
+
48
+ - **Register the app.** Create a WorkOS Connect app in the Tempo Issuer environment, and register every redirect URI it uses. Its `client_id` is the `clientId` you pass to `Auth.create`. An app with a backend also gets a client secret.
49
+ - **Allow the app's origin.** Apps that call Tempo Auth from their own page need their origin in Tempo Auth's `INTERNAL_ORIGINS`.
50
+
51
+ The CLI device grant needs neither. It signs in as Tempo Auth's own `tempo-cli` app.
52
+
53
+ ## Access keys
54
+
55
+ Each sign-in creates a key for the app and asks Tempo Auth to authorize it on the user's Tempo Account. The `accessKey` option says what the key may do, with viem's `Expiry` and `Scopes` helpers:
56
+
57
+ | Field | Meaning |
58
+ | --------- | ----------------------------------------------------------------------------- |
59
+ | `chainId` | The chain the key acts on, such as `tempoTestnet.id` |
60
+ | `expiry` | When the key expires, in Unix seconds, such as `Expiry.days(1)` |
61
+ | `limits` | One-time spending limits per token, such as `[{ limit: 10_000_000n, token }]` |
62
+ | `scopes` | The calls the key may make, such as `[Scopes.tip20(token).transfer()]` |
63
+
64
+ Flows without a Tempo Auth page (`authorize`, `verifyCode`) must stay within Tempo Auth's ceiling, about $1,000 per token. Flows with a page (`redirect`, `createAuthorizationUrl`, `authorizeDevice`) show the limits to the user, who approves them, so they may go beyond it.
65
+
66
+ The returned `account` attaches the key's authorization to its first transaction, which registers the key on chain.
67
+
68
+ ## Flows
69
+
70
+ ### Your own sign-in UI
71
+
72
+ For a browser app that renders its own email form. The page calls Tempo Auth with the user's Tempo Auth session, so it works across every app on the same site.
73
+
74
+ ```ts
75
+ // A user already signed in to Tempo Auth, from any app, needs no form.
76
+ const authorization = await Actions.authorize(auth, { accessKey })
77
+
78
+ // Otherwise, sign in with an emailed code.
79
+ await Actions.sendCode(auth, { email })
80
+ const result = await Actions.verifyCode(auth, { accessKey, code, email })
81
+
82
+ // An address that requires SSO continues at its connection, and returns to this page.
83
+ if ('step' in result) Actions.signIn(auth, { url: result.step.url })
84
+ ```
85
+
86
+ To offer OAuth providers such as Google, list the ones Tempo Auth enables and send the browser to one. It returns to the current page signed in, where `authorize` finishes.
87
+
88
+ ```ts
89
+ const providers = await Actions.getProviders(auth) // [{ id: 'google', name: 'Google' }]
90
+ Actions.signIn(auth, { provider: 'google' })
91
+ ```
92
+
93
+ ### Tempo Auth's pages
94
+
95
+ For a browser app without its own sign-in UI. The browser leaves for Tempo Auth's sign-in page, which shows the key's limits, and returns with a code.
96
+
97
+ ```ts
98
+ await Actions.redirect(auth, { accessKey })
99
+
100
+ // On the page the browser returns to, and on every load:
101
+ const authorization = await Actions.handleRedirect(auth)
102
+ ```
103
+
104
+ `redirect` keeps its state in session storage until the browser returns. Pass `storage` to `Auth.create` to keep it elsewhere.
105
+
106
+ ### A backend
107
+
108
+ For an app whose server holds a client secret. The server builds the URL, keeps the pending state until the callback, and redeems the code itself.
109
+
110
+ ```ts
111
+ const auth = Auth.create({ clientId, clientSecret, url })
112
+
113
+ // GET /auth/start
114
+ const { pending, url } = await Actions.createAuthorizationUrl(auth, {
115
+ accessKey,
116
+ redirectUri: 'https://app.example/auth/callback',
117
+ })
118
+ // Keep `pending` in an encrypted, HttpOnly cookie, then redirect to `url`.
119
+
120
+ // GET /auth/callback
121
+ const authorization = await Actions.handleCallback(auth, {
122
+ pending,
123
+ url: request.url,
124
+ })
125
+ ```
126
+
127
+ ### A CLI
128
+
129
+ For a command-line tool. The user approves a code in any browser, and the CLI waits.
130
+
131
+ ```ts
132
+ const device = await Actions.authorizeDevice(auth, {
133
+ accessKey,
134
+ device: os.hostname(),
135
+ })
136
+ console.log(
137
+ `Open ${device.verificationUriComplete} and confirm ${device.userCode}.`,
138
+ )
139
+ const authorization = await device.wait()
140
+ ```
141
+
142
+ ## Keep a sign-in
143
+
144
+ `Authorization.key` is the app's key as JSON: the Tempo Account, the signed authorization, and the private key. Store it with the tokens, as securely as a session, and rebuild the account later with `Key.toAccount`.
145
+
146
+ ```ts
147
+ import { Actions, Key } from '@tempoxyz/auth'
148
+
149
+ if (Key.expired(saved.key)) signInAgain()
150
+
151
+ const account = Key.toAccount(saved.key)
152
+ const tokens = await Actions.refresh(auth, {
153
+ refreshToken: saved.tokens.refreshToken,
154
+ })
155
+ ```
156
+
157
+ Refresh tokens rotate, so store the new `tokens` after each refresh.
158
+
159
+ ## Errors
160
+
161
+ A request Tempo Auth refuses throws `Auth.RequestError`, with its `status` and `message`.
162
+
163
+ ```ts
164
+ try {
165
+ await Actions.verifyCode(auth, { accessKey, code, email })
166
+ } catch (error) {
167
+ if (error instanceof Auth.RequestError && error.status === 401)
168
+ showWrongCode()
169
+ }
170
+ ```
171
+
172
+ ## Reference
173
+
174
+ ### `Auth.create(options)`
175
+
176
+ | Option | Meaning |
177
+ | -------------- | ----------------------------------------------------------------------------------------------- |
178
+ | `clientId` | The app's Connect client ID. Required |
179
+ | `url` | Where Tempo Auth runs, such as `https://auth.tempo.xyz`. Required |
180
+ | `clientSecret` | The app's client secret. Only a backend holds one |
181
+ | `redirectUri` | Where Tempo Auth redirects back to. Defaults to the page's origin |
182
+ | `scope` | Scopes beyond `openid` and `email`, which every instance includes. Defaults to `offline_access` |
183
+ | `storage` | Where `redirect` keeps its state. Defaults to session storage |
184
+ | `fetch` | Calls Tempo Auth. Defaults to the global `fetch` |
185
+
186
+ ### `Actions`
187
+
188
+ | Action | Returns |
189
+ | ---------------------------------------------------------- | -------------------------------------------------------------------------- |
190
+ | `authorize(auth, { accessKey })` | An `Authorization` for a user already signed in, or `undefined` |
191
+ | `authorizeDevice(auth, { accessKey, device })` | The code to show and a `wait()` that resolves with an `Authorization` |
192
+ | `createAuthorizationUrl(auth, { accessKey, redirectUri })` | The `url` to send the browser to, and the `pending` state to keep |
193
+ | `getProviders(auth)` | The OAuth providers Tempo Auth offers, as `{ id, name }` |
194
+ | `handleCallback(auth, { pending, url })` | The `Authorization` for a redirect's callback |
195
+ | `handleRedirect(auth)` | The `Authorization` when the page is a redirect's callback, or `undefined` |
196
+ | `redirect(auth, { accessKey })` | Nothing. The browser leaves for Tempo Auth's pages |
197
+ | `refresh(auth, { refreshToken })` | New `Tokens` |
198
+ | `sendCode(auth, { email })` | Nothing. Tempo Auth emails a code |
199
+ | `signIn(auth, { provider } \| { url })` | Nothing. The browser leaves for the provider or SSO connection |
200
+ | `signOut(auth)` | Nothing. The user's Tempo Auth session ends |
201
+ | `verifyCode(auth, { accessKey, code, email })` | An `Authorization`, or the `step` the address requires first |
202
+
203
+ ### `Authorization`
204
+
205
+ | Field | Meaning |
206
+ | --------- | ------------------------------------------------------------------------------- |
207
+ | `account` | The app's key as a viem access key account, acting for the user's Tempo Account |
208
+ | `key` | The same key as JSON, for `Key.toAccount` |
209
+ | `tokens` | `accessToken`, `expiresAt`, and the optional `idToken` and `refreshToken` |
210
+ | `user` | The user's `email` and `id`, the `sub` of every token |
211
+
212
+ ### `Key`
213
+
214
+ | Function | Returns |
215
+ | ---------------- | ------------------------------------------- |
216
+ | `toAccount(key)` | The key as a viem access key account |
217
+ | `expired(key)` | Whether the key's authorization has expired |
package/dist/Actions.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type AccessKey, type Auth, type Authorization, type Pending, type Provider, type Step, type Tokens } from './Auth.ts';
2
2
  /**
3
- * Authorizes the app for a person already signed in to Tempo Auth, from any app. Returns
3
+ * Authorizes the app for a user already signed in to Tempo Auth, from any app. Returns
4
4
  * `undefined` without a session, so the app shows sign-in. The key must fit Tempo Auth's headless
5
5
  * ceiling.
6
6
  *
@@ -20,7 +20,7 @@ export declare namespace authorize {
20
20
  type ReturnType = Authorization | undefined;
21
21
  }
22
22
  /**
23
- * Starts the device grant for a CLI: the person approves `userCode` at `verificationUri` from any
23
+ * Starts the device grant for a CLI: the user approves `userCode` at `verificationUri` from any
24
24
  * browser, and `wait` resolves once they do.
25
25
  *
26
26
  * @example
@@ -41,18 +41,18 @@ export declare namespace authorizeDevice {
41
41
  type ReturnType = {
42
42
  /** Seconds until the code expires. */
43
43
  expiresIn: number;
44
- /** The code the person confirms in the browser. */
44
+ /** The code the user confirms in the browser. */
45
45
  userCode: string;
46
- /** Where the person enters the code. */
46
+ /** Where the user enters the code. */
47
47
  verificationUri: string;
48
- /** Where the person approves the code, with it already filled in. */
48
+ /** Where the user approves the code, with it already filled in. */
49
49
  verificationUriComplete: string;
50
- /** Resolves with the authorization once the person approves. */
50
+ /** Resolves with the authorization once the user approves. */
51
51
  wait: () => Promise<Authorization>;
52
52
  };
53
53
  }
54
54
  /**
55
- * Returns the URL that signs the person in on Tempo Auth's pages and redirects back with a code
55
+ * Returns the URL that signs the user in on Tempo Auth's pages and redirects back with a code
56
56
  * and the key authorization, and the {@link Pending} state to keep until then, such as in a
57
57
  * sealed cookie. {@link handleCallback} finishes it.
58
58
  *
@@ -69,7 +69,7 @@ export declare namespace authorizeDevice {
69
69
  export declare function createAuthorizationUrl(auth: Auth, options: createAuthorizationUrl.Options): Promise<createAuthorizationUrl.ReturnType>;
70
70
  export declare namespace createAuthorizationUrl {
71
71
  type Options = {
72
- /** What the app's key may do. Tempo Auth's pages show it, and the person approves it. */
72
+ /** What the app's key may do. Tempo Auth's pages show it, and the user approves it. */
73
73
  accessKey: AccessKey;
74
74
  /** Where Tempo Auth redirects back to. Defaults to the instance's `redirectUri`. */
75
75
  redirectUri?: string | undefined;
@@ -147,7 +147,7 @@ export declare namespace handleRedirect {
147
147
  export declare function redirect(auth: Auth, options: redirect.Options): Promise<void>;
148
148
  export declare namespace redirect {
149
149
  type Options = {
150
- /** What the app's key may do. Tempo Auth's pages show it, and the person approves it. */
150
+ /** What the app's key may do. Tempo Auth's pages show it, and the user approves it. */
151
151
  accessKey: AccessKey;
152
152
  };
153
153
  }
@@ -169,17 +169,17 @@ export declare namespace refresh {
169
169
  };
170
170
  }
171
171
  /**
172
- * Emails the person a sign-in code. The response is the same for every address.
172
+ * Emails the user a sign-in code. The response is the same for every address.
173
173
  *
174
174
  * @example
175
175
  * ```ts
176
- * await Actions.sendCode(auth, { email: 'person@example.com' })
176
+ * await Actions.sendCode(auth, { email: 'user@example.com' })
177
177
  * ```
178
178
  */
179
179
  export declare function sendCode(auth: Auth, options: sendCode.Options): Promise<void>;
180
180
  export declare namespace sendCode {
181
181
  type Options = {
182
- /** The person's email address. */
182
+ /** The user's email address. */
183
183
  email: string;
184
184
  };
185
185
  }
@@ -206,7 +206,7 @@ export declare namespace signIn {
206
206
  };
207
207
  }
208
208
  /**
209
- * Ends the person's Tempo Auth session, which signs them out of every app that relies on it.
209
+ * Ends the user's Tempo Auth session, which signs them out of every app that relies on it.
210
210
  *
211
211
  * @example
212
212
  * ```ts
@@ -215,7 +215,7 @@ export declare namespace signIn {
215
215
  */
216
216
  export declare function signOut(auth: Auth): Promise<void>;
217
217
  /**
218
- * Signs the person in with the code they were emailed, and authorizes the app in the same call.
218
+ * Signs the user in with the code they were emailed, and authorizes the app in the same call.
219
219
  * Returns a {@link Step} when the address requires SSO, which {@link signIn} continues.
220
220
  *
221
221
  * @example
@@ -229,7 +229,7 @@ export declare namespace verifyCode {
229
229
  type Options = {
230
230
  /** What the app's key may do. It must fit the headless ceiling. */
231
231
  accessKey: AccessKey;
232
- /** The 6-digit code the person was emailed. */
232
+ /** The 6-digit code the user was emailed. */
233
233
  code: string;
234
234
  /** The address the code was sent to. */
235
235
  email: string;
package/dist/Actions.js CHANGED
@@ -8,7 +8,7 @@ import { RequestError, } from './Auth.js';
8
8
  import * as Key from './Key.js';
9
9
  import { request } from './internal/request.js';
10
10
  /**
11
- * Authorizes the app for a person already signed in to Tempo Auth, from any app. Returns
11
+ * Authorizes the app for a user already signed in to Tempo Auth, from any app. Returns
12
12
  * `undefined` without a session, so the app shows sign-in. The key must fit Tempo Auth's headless
13
13
  * ceiling.
14
14
  *
@@ -34,7 +34,7 @@ export async function authorize(auth, options) {
34
34
  return authorization(await json(response), { privateKey });
35
35
  }
36
36
  /**
37
- * Starts the device grant for a CLI: the person approves `userCode` at `verificationUri` from any
37
+ * Starts the device grant for a CLI: the user approves `userCode` at `verificationUri` from any
38
38
  * browser, and `wait` resolves once they do.
39
39
  *
40
40
  * @example
@@ -53,7 +53,7 @@ export async function authorizeDevice(auth, options) {
53
53
  .keyAuthorization,
54
54
  },
55
55
  }));
56
- /** Polls until the person approves or denies the code, or it expires. */
56
+ /** Polls until the user approves or denies the code, or it expires. */
57
57
  async function wait() {
58
58
  let interval = code.interval;
59
59
  const deadline = Date.now() + code.expires_in * 1000;
@@ -89,7 +89,7 @@ export async function authorizeDevice(auth, options) {
89
89
  };
90
90
  }
91
91
  /**
92
- * Returns the URL that signs the person in on Tempo Auth's pages and redirects back with a code
92
+ * Returns the URL that signs the user in on Tempo Auth's pages and redirects back with a code
93
93
  * and the key authorization, and the {@link Pending} state to keep until then, such as in a
94
94
  * sealed cookie. {@link handleCallback} finishes it.
95
95
  *
@@ -233,11 +233,11 @@ export function refresh(auth, options) {
233
233
  });
234
234
  }
235
235
  /**
236
- * Emails the person a sign-in code. The response is the same for every address.
236
+ * Emails the user a sign-in code. The response is the same for every address.
237
237
  *
238
238
  * @example
239
239
  * ```ts
240
- * await Actions.sendCode(auth, { email: 'person@example.com' })
240
+ * await Actions.sendCode(auth, { email: 'user@example.com' })
241
241
  * ```
242
242
  */
243
243
  export async function sendCode(auth, options) {
@@ -260,7 +260,7 @@ export function signIn(auth, options) {
260
260
  window.location.assign(url);
261
261
  }
262
262
  /**
263
- * Ends the person's Tempo Auth session, which signs them out of every app that relies on it.
263
+ * Ends the user's Tempo Auth session, which signs them out of every app that relies on it.
264
264
  *
265
265
  * @example
266
266
  * ```ts
@@ -272,7 +272,7 @@ export async function signOut(auth) {
272
272
  await json(await post(auth, '/api/auth/logout', { json: {} }));
273
273
  }
274
274
  /**
275
- * Signs the person in with the code they were emailed, and authorizes the app in the same call.
275
+ * Signs the user in with the code they were emailed, and authorizes the app in the same call.
276
276
  * Returns a {@link Step} when the address requires SSO, which {@link signIn} continues.
277
277
  *
278
278
  * @example
package/dist/Auth.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A Tempo Auth instance, and the types its actions take and return. Create one with
3
3
  * {@link create}, then pass it to each action in `Actions`. Every sign-in returns an
4
- * {@link Authorization}: the person's tokens, and a viem access key account.
4
+ * {@link Authorization}: the user's tokens, and a viem access key account.
5
5
  *
6
6
  * @example
7
7
  * ```ts
@@ -51,13 +51,13 @@ export type AccessKey = Pick<KeyAuthorization.KeyAuthorization, 'limits' | 'scop
51
51
  };
52
52
  /** What every sign-in returns. */
53
53
  export type Authorization = {
54
- /** The app's access key, as a viem account acting for the person's Tempo Account. */
54
+ /** The app's access key, as a viem account acting for the user's Tempo Account. */
55
55
  account: ReturnType<typeof Key.toAccount>;
56
56
  /** The same key as JSON, for apps that store it and rebuild it with `Key.toAccount`. */
57
57
  key: Key.Key;
58
58
  /** The app's tokens for Tempo APIs. */
59
59
  tokens: Tokens;
60
- /** The signed-in person, from the ID token. */
60
+ /** The signed-in user, from the ID token. */
61
61
  user: User;
62
62
  };
63
63
  /** The app's tokens for Tempo APIs. */
@@ -66,16 +66,16 @@ export type Tokens = {
66
66
  accessToken: string;
67
67
  /** When the access token expires, in milliseconds. */
68
68
  expiresAt: number;
69
- /** The OIDC ID token, which names the person. A refresh may omit it. */
69
+ /** The OIDC ID token, which names the user. A refresh may omit it. */
70
70
  idToken?: string | undefined;
71
71
  /** Exchanges for new tokens through `Actions.refresh`. It rotates on every refresh. */
72
72
  refreshToken?: string | undefined;
73
73
  };
74
- /** The signed-in person. */
74
+ /** The signed-in user. */
75
75
  export type User = {
76
- /** The person's verified email address. */
76
+ /** The user's verified email address. */
77
77
  email: string;
78
- /** The person's ID, the `sub` of every token. */
78
+ /** The user's ID, the `sub` of every token. */
79
79
  id: string;
80
80
  };
81
81
  /** An OAuth provider's short name, as `Actions.getProviders` lists them. */
package/dist/Auth.js CHANGED
@@ -17,7 +17,7 @@ export class RequestError extends Error {
17
17
  * ```
18
18
  */
19
19
  export function create(options) {
20
- // Every authorization reads the person from the ID token, which needs `openid` and `email`.
20
+ // Every authorization reads the user from the ID token, which needs `openid` and `email`.
21
21
  const scopes = (options.scope ?? 'offline_access').split(' ').filter(Boolean);
22
22
  return {
23
23
  ...options,
package/dist/Key.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * An app's access key on a person's Tempo Account, as the app stores it: the key, the account it
2
+ * An app's access key on a user's Tempo Account, as the app stores it: the key, the account it
3
3
  * acts for, and the authorization Tempo Auth signed for it.
4
4
  * @module
5
5
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tempoxyz/auth",
3
- "version": "0.0.0-pr-6.sha-17bb901",
4
- "description": "Signs people in to Tempo through Tempo Auth, and returns viem access key accounts on their Tempo Accounts.",
3
+ "version": "0.0.0-pr-6.sha-fe206c7",
4
+ "description": "Signs users in to Tempo through Tempo Auth, and returns viem access key accounts on their Tempo Accounts.",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {