@tempoxyz/auth 0.0.0-pr-97.sha-b7eeea0 → 0.0.0-pr-99.sha-709eecc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Actions.js CHANGED
@@ -7,6 +7,7 @@
7
7
  import { Authentication } from 'ox/webauthn';
8
8
  import * as AccessKey from './internal/accessKey.js';
9
9
  import * as Api from './internal/api.js';
10
+ import * as Link from './internal/link.js';
10
11
  import { request } from './internal/request.js';
11
12
  export * as account from './actions/account.js';
12
13
  export * as device from './actions/device.js';
@@ -39,7 +40,12 @@ export async function authorize(auth, options = {}) {
39
40
  }));
40
41
  if (!session.user)
41
42
  return undefined;
42
- const key = await AccessKey.resolve(options.accessKey);
43
+ // A passkey sign-in's account step kept the key its assertion signed, which Tempo Auth
44
+ // authorizes with no second assertion when the passkey's account is chosen.
45
+ const kept = options.accessKey
46
+ ? undefined
47
+ : await Link.get(auth, { link: Link.account });
48
+ const key = kept ?? (await AccessKey.resolve(options.accessKey));
43
49
  const json = request(auth, {
44
50
  accessKey: key.accessKey,
45
51
  account: options.account,
@@ -49,6 +55,9 @@ export async function authorize(auth, options = {}) {
49
55
  const response = await Api.post(auth, '/v1/authorize', { json });
50
56
  if (response.status === 401)
51
57
  return undefined;
58
+ // A failed removal keeps the stale key, which the next account step replaces.
59
+ if (kept)
60
+ await Link.release(auth, { link: Link.account }).catch(() => undefined);
52
61
  const body = await Api.json(response);
53
62
  if (!('step' in body))
54
63
  return Api.authorization(body, key);
package/dist/Auth.d.ts CHANGED
@@ -78,12 +78,19 @@ export declare class RequestError extends Error {
78
78
  }
79
79
  export declare namespace RequestError {
80
80
  type Options = {
81
+ /** The OAuth error code, such as `invalid_authorization_details`. */
81
82
  code: string;
83
+ /** The request field at fault, as a dotted path. */
82
84
  field?: string | undefined;
85
+ /** The fault and its fix, from `error_description`. */
83
86
  message: string;
87
+ /** The values the refusal compared, such as `limit` and `maximum`. Base units are decimal strings. */
84
88
  meta?: Readonly<Record<string, number | string>> | undefined;
89
+ /** A stable name for the refusal, to branch on. Absent from the issuer's own errors. */
85
90
  reason?: Reason | undefined;
91
+ /** The HTTP status of Tempo Auth's response. */
86
92
  status: number;
93
+ /** The page that explains the error, from `error_uri`. */
87
94
  uri?: string | undefined;
88
95
  };
89
96
  /** Each refusal's stable name. `docs/errors.md` explains each one. */
@@ -70,6 +70,7 @@ export declare namespace tempoAuth {
70
70
  type Decorator<chainId extends number> = {
71
71
  /** Tempo Auth's actions, with the extension's instance. */
72
72
  auth: {
73
+ /** Actions for the user's Tempo Account. */
73
74
  account: {
74
75
  /** `Actions.account.get`. */
75
76
  get: () => Promise<Actions.account.get.ReturnType>;
@@ -79,10 +80,12 @@ export declare namespace tempoAuth {
79
80
  * session.
80
81
  */
81
82
  authorize: <const id extends number = chainId>(options?: Actions.authorize.Options<id>) => Promise<Actions.authorize.ReturnType<id>>;
83
+ /** Actions for device authorization grants. */
82
84
  device: {
83
85
  /** `Actions.device.authorize`, whose `wait` sets the client's account. */
84
86
  authorize: <const id extends number = chainId>(options?: Actions.device.authorize.Options<id>) => Promise<Actions.device.authorize.ReturnType<id>>;
85
87
  };
88
+ /** Actions for sign-in with an emailed code. */
86
89
  email: {
87
90
  /** `Actions.email.sendCode`. */
88
91
  sendCode: (options: Actions.email.sendCode.Options) => Promise<void>;
@@ -94,6 +97,7 @@ export declare namespace tempoAuth {
94
97
  };
95
98
  /** The instance the members call `Actions` with. */
96
99
  instance: Auth.Auth;
100
+ /** Actions for the issuer's OAuth endpoints. */
97
101
  oauth: {
98
102
  /** `Actions.oauth.introspect`. */
99
103
  introspect: (options: Actions.oauth.introspect.Options) => Promise<Actions.oauth.introspect.ReturnType>;
@@ -102,6 +106,7 @@ export declare namespace tempoAuth {
102
106
  /** `Actions.oauth.userInfo`. */
103
107
  userInfo: (options: Actions.oauth.userInfo.Options) => Promise<User>;
104
108
  };
109
+ /** Actions for sign-in with a passkey. */
105
110
  passkey: {
106
111
  /**
107
112
  * `Actions.passkey.signIn`, which sets the client's account unless it returns a step. A step
@@ -109,16 +114,19 @@ export declare namespace tempoAuth {
109
114
  */
110
115
  signIn: <const id extends number = chainId>(options?: Actions.passkey.signIn.Options<id>) => Promise<Actions.passkey.signIn.ReturnType<id>>;
111
116
  };
117
+ /** Actions for sign-in with an identity provider, such as Google or SSO. */
112
118
  provider: {
113
119
  /** `Actions.provider.list`. */
114
120
  list: () => Promise<Actions.provider.list.ReturnType>;
115
121
  /** `Actions.provider.signIn`. */
116
122
  signIn: (options: Actions.provider.signIn.Options) => void;
117
123
  };
124
+ /** Actions for the realms the app reaches. */
118
125
  realm: {
119
126
  /** `Actions.realm.list`. */
120
127
  list: () => Promise<Actions.realm.list.ReturnType>;
121
128
  };
129
+ /** Actions for sign-in through a redirect to Tempo Auth's pages. */
122
130
  redirect: {
123
131
  /** `Actions.redirect.complete`, which sets the client's account. */
124
132
  complete: (options?: Actions.redirect.complete.Options) => Promise<Actions.redirect.complete.ReturnType>;
@@ -17,7 +17,9 @@ export type TempoAccount = {
17
17
  * `tempo-cash`, with its display name. An app reaches only the accounts of its realms.
18
18
  */
19
19
  realm: {
20
+ /** The realm ID: `default`, or one an app's settings name, such as `tempo-cash`. */
20
21
  id: string;
22
+ /** The realm's display name, such as `Tempo Cash`. */
21
23
  name: string;
22
24
  };
23
25
  };
@@ -65,6 +65,7 @@ export declare namespace signIn {
65
65
  };
66
66
  /** The authorization, or the step the address or the user's accounts require first. */
67
67
  type ReturnType<chainId extends number = number> = Authorization<chainId> | {
68
+ /** The step to complete before the authorization. */
68
69
  step: Step;
69
70
  };
70
71
  }
@@ -84,6 +84,7 @@ export declare namespace signIn {
84
84
  * none returns the account step.
85
85
  */
86
86
  type ReturnType<chainId extends number = number> = Authorization<chainId> | {
87
+ /** The email step, or the account step to complete before the authorization. */
87
88
  step: EmailStep | Extract<Step, {
88
89
  type: 'account';
89
90
  }>;
@@ -57,13 +57,18 @@ export async function signIn(auth, options = {}) {
57
57
  json: native ? { assertion, authorize } : { assertion },
58
58
  }));
59
59
  if ('step' in body) {
60
- if (body.step.type === 'account')
60
+ if (body.step.type === 'account') {
61
+ // Choosing the passkey's account authorizes the key its assertion already signed, once
62
+ // `Actions.authorize` sends that key again.
63
+ if (!native && !options.accessKey)
64
+ await Link.keep(auth, { link: Link.account, material: key.material });
61
65
  return {
62
66
  step: {
63
67
  accounts: body.step.accounts.map(Api.tempoAccount),
64
68
  type: 'account',
65
69
  },
66
70
  };
71
+ }
67
72
  // The emailed-code sign-in that sends the link returns this key's authorization, so a key the
68
73
  // library generated waits for it in `storage`.
69
74
  if (body.step.signed && !options.accessKey)
@@ -19,24 +19,35 @@ export declare function authorization<chainId extends number = number>(body: Hea
19
19
  * one, who selects one before the key is authorized.
20
20
  */
21
21
  export type AccountStep = {
22
+ /** The user's Tempo Accounts, to select one from. */
22
23
  accounts: readonly TempoAccountResponse[];
24
+ /** The kind of step. */
23
25
  type: 'account';
24
26
  };
25
27
  /** What the headless API returns for an authorization. */
26
28
  export type Headless = {
29
+ /** The Tempo Account the key acts for. */
27
30
  account: Key.Key['account'];
31
+ /** The signed key authorization, in the node's RPC format. */
28
32
  key_authorization: KeyAuthorization.Rpc;
33
+ /** The tokens the sign-in issued, as the token endpoint returns them or already read into `Tokens`. */
29
34
  tokens: TokenResponse | Tokens;
30
35
  };
31
36
  /** Reads a JSON response, and throws a {@link RequestError} for a refused request. */
32
37
  export declare function json<value = unknown>(response: Response): Promise<value>;
33
38
  /** An OAuth error response, with Tempo Auth's extension members. */
34
39
  export type ErrorBody = {
40
+ /** The OAuth error code, which `reason` determines. */
35
41
  error: string;
42
+ /** The fault and its fix. Its wording may change. */
36
43
  error_description?: string | undefined;
44
+ /** The errors page, at the refusal's `reason`. */
37
45
  error_uri?: string | undefined;
46
+ /** The request field at fault, as a dotted path. */
38
47
  field?: string | undefined;
48
+ /** The values the refusal compared. Base units are decimal strings. */
39
49
  meta?: Readonly<Record<string, number | string>> | undefined;
50
+ /** A stable name for the refusal. The issuer's own errors carry none. */
40
51
  reason?: RequestError.Reason | undefined;
41
52
  };
42
53
  /** A {@link RequestError} from an OAuth error response, or from a refusal with no body. */
@@ -56,26 +67,38 @@ export declare function token(auth: Auth, body: Record<string, string>): Promise
56
67
  export declare function tempoAccount(account: TempoAccountResponse): TempoAccount;
57
68
  /** A Tempo Account as the API returns it, with the fields the package reads. */
58
69
  export type TempoAccountResponse = {
70
+ /** The account's address, the same on every Tempo chain. */
59
71
  address: `0x${string}`;
72
+ /** The account's configuration, whose owners the API lists in order. */
60
73
  config: {
74
+ /** The account's owners, each with the kind of key that holds it, such as `privy` or `webAuthn`. */
61
75
  owners: readonly [{
62
76
  type: string;
63
77
  }, ...{
64
78
  type: string;
65
79
  }[]];
66
80
  };
81
+ /** The realm the account belongs to, with its display name. */
67
82
  realm: {
83
+ /** The realm ID: `default`, or one an app's settings name, such as `tempo-cash`. */
68
84
  id: string;
85
+ /** The realm's display name, such as `Tempo Cash`. */
69
86
  name: string;
70
87
  };
71
88
  };
72
89
  /** What Tempo Auth's token endpoint returns. Redeeming a redirect's code adds its key authorization. */
73
90
  export type TokenResponse = {
91
+ /** The access token, which authorizes the app's requests to its APIs. */
74
92
  access_token: string;
93
+ /** The Tempo Account the key acts for, present with `key_authorization`. */
75
94
  account?: Key.Key['account'] | undefined;
95
+ /** The access token's lifetime, in seconds. */
76
96
  expires_in: number;
97
+ /** The OIDC ID token, which names the user. A refresh may omit it. */
77
98
  id_token?: string | undefined;
99
+ /** The signed key authorization that the user approved with the code, if any. */
78
100
  key_authorization?: KeyAuthorization.Rpc | undefined;
101
+ /** Exchanges for new tokens through `Actions.refresh`. It rotates on every refresh. */
79
102
  refresh_token?: string | undefined;
80
103
  };
81
104
  /** Reads a token response into `Tokens`. */
@@ -1,6 +1,8 @@
1
1
  import type { Auth } from '../Auth.ts';
2
2
  import type * as Key from '../Key.ts';
3
3
  import * as AccessKey from './accessKey.ts';
4
+ /** The `link` a key waits under after an account step, which names no link of its own. */
5
+ export declare const account = "account";
4
6
  /**
5
7
  * Keeps the key's material for `link`. Keeps nothing outside a browser without `storage`. Only the
6
8
  * link's digest is stored, since the link links the passkey for whoever sends it with a code.
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * Keeps a new key that a one-tap passkey sign-in signed, until the emailed-code sign-in that sends
3
- * its email step's `link` returns the key's authorization.
3
+ * its email step's `link`, or the `Actions.authorize` after its account step, returns the key's
4
+ * authorization.
4
5
  * @module
5
6
  */
6
7
  import { Bytes, Hash } from 'ox';
7
8
  import * as AccessKey from './accessKey.js';
8
9
  import * as Store from './store.js';
9
- /** Where the key waits in `storage`. A later email step replaces it. */
10
+ /** The `link` a key waits under after an account step, which names no link of its own. */
11
+ export const account = 'account';
12
+ /** Where the key waits in `storage`. A later email or account step replaces it. */
10
13
  const storageKey = 'tempo-auth:passkey-link';
11
14
  /**
12
15
  * Keeps the key's material for `link`. Keeps nothing outside a browser without `storage`. Only the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tempoxyz/auth",
3
- "version": "0.0.0-pr-97.sha-b7eeea0",
3
+ "version": "0.0.0-pr-99.sha-709eecc",
4
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": {