@tempoxyz/auth 0.0.0-pr-74.sha-aa4f4e2 → 0.0.0-pr-87.sha-7bb5600

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.d.ts CHANGED
@@ -32,9 +32,14 @@ export declare namespace authorize {
32
32
  type Options<chainId extends number = number> = {
33
33
  /**
34
34
  * The key to authorize, as a Viem account. Its `access` names the Tempo Account, which
35
- * defaults to the user's first. Defaults to a new key.
35
+ * defaults to the one the user selected at sign-in, or their oldest. Defaults to a new key.
36
36
  */
37
37
  accessKey?: Account.Account | undefined;
38
+ /**
39
+ * The address of the user's Tempo Account the key acts for, such as the one the user chose from
40
+ * a sign-in's account step. Takes the place of `accessKey`'s `access`.
41
+ */
42
+ account?: `0x${string}` | undefined;
38
43
  /**
39
44
  * A message the owner's signature binds, such as a sign-in challenge the app's backend issued.
40
45
  * Tempo Auth binds its EIP-191 hash as the key authorization's `witness`. Only an app with the
package/dist/Actions.js CHANGED
@@ -30,6 +30,7 @@ export async function authorize(auth, options = {}) {
30
30
  const response = await Api.post(auth, '/v1/authorize', {
31
31
  json: request(auth, {
32
32
  accessKey: key.accessKey,
33
+ account: options.account,
33
34
  challenge: options.challenge,
34
35
  permissions: options.permissions,
35
36
  }),
package/dist/Step.d.ts CHANGED
@@ -2,8 +2,20 @@
2
2
  * A step a sign-in needs before it finishes, such as the SSO connection an address requires.
3
3
  * @module
4
4
  */
5
- /** A step a sign-in needs before it finishes, such as the SSO connection an address requires. */
5
+ import type { TempoAccount } from './TempoAccount.ts';
6
+ /**
7
+ * A step a sign-in needs before it finishes: the SSO connection an address requires, or the choice
8
+ * of account for a user with more than one Tempo Account.
9
+ */
6
10
  export type Step = {
11
+ /** The user's Tempo Accounts: the one the browser selected before first, then oldest first. */
12
+ accounts: readonly TempoAccount[];
13
+ /**
14
+ * The kind of step. The user is signed in, and chooses an account, whose `address`
15
+ * `Actions.authorize` takes as `account`.
16
+ */
17
+ type: 'account';
18
+ } | {
7
19
  /** The kind of step. */
8
20
  type: 'sso';
9
21
  /** Where `Actions.provider.signIn` sends the browser to take the step. */
@@ -18,7 +18,7 @@ export declare namespace authorize {
18
18
  type Options<chainId extends number = number> = {
19
19
  /**
20
20
  * The key to authorize, as a Viem account. Its `access` names the Tempo Account, which
21
- * defaults to the user's first. Defaults to a new key.
21
+ * defaults to the one the user selected at sign-in, or their oldest. Defaults to a new key.
22
22
  */
23
23
  accessKey?: Account.Account | undefined;
24
24
  /**
@@ -24,12 +24,15 @@ export declare namespace sendCode {
24
24
  }
25
25
  /**
26
26
  * Signs the user in with the code they were emailed, and authorizes the app in the same call.
27
- * Returns a {@link Step} when the address requires SSO, which `Actions.provider.signIn` continues.
27
+ * Returns a {@link Step} when the address requires SSO, which `Actions.provider.signIn` continues,
28
+ * or when the user has more than one Tempo Account, whose chosen `address` `Actions.authorize` takes
29
+ * as `account`.
28
30
  *
29
31
  * @example
30
32
  * ```ts
31
33
  * const result = await Actions.email.signIn(auth, { code, email })
32
- * if ('step' in result) Actions.provider.signIn(auth, { url: result.step.url })
34
+ * if ('step' in result && result.step.type === 'sso')
35
+ * Actions.provider.signIn(auth, { url: result.step.url })
33
36
  * ```
34
37
  */
35
38
  export declare function signIn<const chainId extends number = number>(auth: Auth, options: signIn.Options<chainId>): Promise<signIn.ReturnType<chainId>>;
@@ -37,7 +40,7 @@ export declare namespace signIn {
37
40
  type Options<chainId extends number = number> = {
38
41
  /**
39
42
  * The key to authorize, as a Viem account. Its `access` names the Tempo Account, which
40
- * defaults to the user's first. Defaults to a new key.
43
+ * defaults to the one the user selected at sign-in, or their oldest. Defaults to a new key.
41
44
  */
42
45
  accessKey?: Account.Account | undefined;
43
46
  /**
@@ -60,7 +63,7 @@ export declare namespace signIn {
60
63
  /** What the key may do. It must fit the headless ceiling, which fills what it leaves out. */
61
64
  permissions?: Permissions<chainId> | undefined;
62
65
  };
63
- /** The authorization, or the step the address requires first. */
66
+ /** The authorization, or the step the address or the user's accounts require first. */
64
67
  type ReturnType<chainId extends number = number> = Authorization<chainId> | {
65
68
  step: Step;
66
69
  };
@@ -15,12 +15,15 @@ export async function sendCode(auth, options) {
15
15
  }
16
16
  /**
17
17
  * Signs the user in with the code they were emailed, and authorizes the app in the same call.
18
- * Returns a {@link Step} when the address requires SSO, which `Actions.provider.signIn` continues.
18
+ * Returns a {@link Step} when the address requires SSO, which `Actions.provider.signIn` continues,
19
+ * or when the user has more than one Tempo Account, whose chosen `address` `Actions.authorize` takes
20
+ * as `account`.
19
21
  *
20
22
  * @example
21
23
  * ```ts
22
24
  * const result = await Actions.email.signIn(auth, { code, email })
23
- * if ('step' in result) Actions.provider.signIn(auth, { url: result.step.url })
25
+ * if ('step' in result && result.step.type === 'sso')
26
+ * Actions.provider.signIn(auth, { url: result.step.url })
24
27
  * ```
25
28
  */
26
29
  export async function signIn(auth, options) {
@@ -46,9 +49,17 @@ export async function signIn(auth, options) {
46
49
  // rather than discard the authorization.
47
50
  if (kept && link)
48
51
  await Link.release(auth, { link }).catch(() => undefined);
49
- if ('step' in body)
52
+ if ('step' in body) {
53
+ if (body.step.type === 'account')
54
+ return {
55
+ step: {
56
+ accounts: body.step.accounts.map(Api.tempoAccount),
57
+ type: 'account',
58
+ },
59
+ };
50
60
  return {
51
61
  step: { ...body.step, url: new URL(body.step.url, auth.url).href },
52
62
  };
63
+ }
53
64
  return Api.authorization(body.authorization, key);
54
65
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Signs the user in with a passkey from the app's own sign-in form, and signs the app's key with the
3
- * same assertion. The browser offers every passkey it holds for Tempo's domain, so the app needs no
4
- * email first.
3
+ * same assertion. By default the browser offers every passkey it holds for Tempo's domain, so the
4
+ * app needs no email first. A non-empty `credentialId` narrows that to the passkeys it names.
5
5
  * @module
6
6
  */
7
7
  import { Authentication } from 'ox/webauthn';
@@ -9,6 +9,7 @@ import type { Account } from 'viem/tempo';
9
9
  import type { Auth } from '../Auth.ts';
10
10
  import type { Authorization } from '../Authorization.ts';
11
11
  import type { Permissions } from '../Permissions.ts';
12
+ import type { Step } from '../Step.ts';
12
13
  /**
13
14
  * Signs the user in with a passkey, and authorizes the app in the same call. On a page, the
14
15
  * passkey's one assertion also signs the app's key when that passkey owns the Tempo Account, since
@@ -33,7 +34,7 @@ export declare namespace signIn {
33
34
  type Options<chainId extends number = number> = {
34
35
  /**
35
36
  * The key to authorize, as a Viem account. Its `access` names the Tempo Account, which
36
- * defaults to the user's first. Defaults to a new key.
37
+ * defaults to the one the user selected at sign-in, or their oldest. Defaults to a new key.
37
38
  */
38
39
  accessKey?: Account.Account | undefined;
39
40
  /**
@@ -42,6 +43,13 @@ export declare namespace signIn {
42
43
  * `tempo-auth:sign` scope may send one.
43
44
  */
44
45
  challenge?: string | undefined;
46
+ /**
47
+ * The credential ID, or up to 16 IDs, the browser offers. Each is unpadded base64url, as
48
+ * `PublicKeyCredential.id` reports it, of 1 to 1,023 bytes. A padded or otherwise non-canonical
49
+ * encoding throws a `RequestError` with reason `invalid_field`. Defaults to every passkey the
50
+ * browser holds for Tempo's domain, as an empty list does.
51
+ */
52
+ credentialId?: readonly string[] | string | undefined;
45
53
  /**
46
54
  * Runs the WebAuthn ceremony in place of `navigator.credentials.get`, such as a native passkey
47
55
  * module. Its assertion must report a web origin on the relying party's domain that the app
@@ -50,6 +58,8 @@ export declare namespace signIn {
50
58
  * `Actions.redirect.createUrl` in the system browser, where the user signs in again.
51
59
  */
52
60
  getFn?: Authentication.sign.Options['getFn'] | undefined;
61
+ /** How the browser mediates the ceremony, such as `conditional` for passkey autofill. Defaults to the browser's own mediation. */
62
+ mediation?: 'conditional' | 'optional' | 'required' | 'silent' | undefined;
53
63
  /** What the key may do. It must fit the headless ceiling, which fills what it leaves out. */
54
64
  permissions?: Permissions<chainId> | undefined;
55
65
  };
@@ -65,8 +75,14 @@ export declare namespace signIn {
65
75
  /** The kind of step. */
66
76
  type: 'email';
67
77
  };
68
- /** The authorization, or the email step. */
78
+ /**
79
+ * The authorization, the email step, or the account step of a user with more than one Tempo
80
+ * Account. A passkey that owns one of the user's accounts selects it, so only a passkey that owns
81
+ * none returns the account step.
82
+ */
69
83
  type ReturnType<chainId extends number = number> = Authorization<chainId> | {
70
- step: EmailStep;
84
+ step: EmailStep | Extract<Step, {
85
+ type: 'account';
86
+ }>;
71
87
  };
72
88
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Signs the user in with a passkey from the app's own sign-in form, and signs the app's key with the
3
- * same assertion. The browser offers every passkey it holds for Tempo's domain, so the app needs no
4
- * email first.
3
+ * same assertion. By default the browser offers every passkey it holds for Tempo's domain, so the
4
+ * app needs no email first. A non-empty `credentialId` narrows that to the passkeys it names.
5
5
  * @module
6
6
  */
7
7
  import { Authentication } from 'ox/webauthn';
@@ -29,7 +29,7 @@ import { request } from '../internal/request.js';
29
29
  * ```
30
30
  */
31
31
  export async function signIn(auth, options = {}) {
32
- const { challenge, getFn, permissions } = options;
32
+ const { challenge, credentialId, getFn, mediation, permissions } = options;
33
33
  const key = await AccessKey.resolve(options.accessKey);
34
34
  const authorize = request(auth, {
35
35
  accessKey: key.accessKey,
@@ -40,7 +40,11 @@ export async function signIn(auth, options = {}) {
40
40
  // sends no `Origin` to bind one-tap to, so it sends the request with the assertion instead.
41
41
  const native = !/^https?:/i.test(authorize.redirect_uri);
42
42
  const requested = await Api.json(await Api.post(auth, '/v1/auth/passkey/options', {
43
- json: native ? {} : { authorize },
43
+ json: {
44
+ ...(native ? {} : { authorize }),
45
+ ...(credentialId === undefined ? {} : { credentialId }),
46
+ ...(mediation === undefined ? {} : { mediation }),
47
+ },
44
48
  }));
45
49
  const deserialized = Authentication.deserializeOptions(requested.options);
46
50
  const assertion = Authentication.serializeResponse(await Authentication.sign(
@@ -50,6 +54,13 @@ export async function signIn(auth, options = {}) {
50
54
  json: native ? { assertion, authorize } : { assertion },
51
55
  }));
52
56
  if ('step' in body) {
57
+ if (body.step.type === 'account')
58
+ return {
59
+ step: {
60
+ accounts: body.step.accounts.map(Api.tempoAccount),
61
+ type: 'account',
62
+ },
63
+ };
53
64
  // The emailed-code sign-in that sends the link returns this key's authorization, so a key the
54
65
  // library generated waits for it in `storage`.
55
66
  if (body.step.signed && !options.accessKey)
@@ -42,7 +42,7 @@ export declare namespace createUrl {
42
42
  type Options = {
43
43
  /**
44
44
  * The key to authorize, as a Viem account. Its `access` names the Tempo Account, which
45
- * defaults to the user's first. Defaults to a new key, whose material `pending` keeps.
45
+ * defaults to the one the user selected at sign-in, or their oldest. Defaults to a new key, whose material `pending` keeps.
46
46
  */
47
47
  accessKey?: Account.Account | undefined;
48
48
  /**
@@ -22,7 +22,8 @@ export declare namespace create {
22
22
  type ReturnType = TempoAccount;
23
23
  }
24
24
  /**
25
- * Lists the signed-in user's Tempo Accounts, their default first.
25
+ * Lists the signed-in user's Tempo Accounts, their default first: the one they selected at sign-in,
26
+ * or else their oldest.
26
27
  *
27
28
  * @example
28
29
  * ```ts
@@ -12,10 +12,11 @@ import * as Api from '../internal/api.js';
12
12
  * ```
13
13
  */
14
14
  export async function create(auth) {
15
- return toTempoAccount(await Api.json(await Api.post(auth, '/v1/account/tempo', { json: {} })));
15
+ return Api.tempoAccount(await Api.json(await Api.post(auth, '/v1/account/tempo', { json: {} })));
16
16
  }
17
17
  /**
18
- * Lists the signed-in user's Tempo Accounts, their default first.
18
+ * Lists the signed-in user's Tempo Accounts, their default first: the one they selected at sign-in,
19
+ * or else their oldest.
19
20
  *
20
21
  * @example
21
22
  * ```ts
@@ -26,12 +27,5 @@ export async function list(auth) {
26
27
  const { tempo } = await Api.json(await (auth.fetch ?? fetch)(new URL('/v1/account', auth.url), {
27
28
  credentials: 'include',
28
29
  }));
29
- return tempo.map(toTempoAccount);
30
- }
31
- /** Reads the account's address and the type of its owner, which the API lists first. */
32
- function toTempoAccount(account) {
33
- return {
34
- address: account.address,
35
- owner: account.config.owners[0].type,
36
- };
30
+ return tempo.map(Api.tempoAccount);
37
31
  }
@@ -3,6 +3,7 @@ import type { Account } from 'viem/tempo';
3
3
  import { type Auth, RequestError } from '../Auth.ts';
4
4
  import type { Authorization } from '../Authorization.ts';
5
5
  import * as Key from '../Key.ts';
6
+ import type { TempoAccount } from '../TempoAccount.ts';
6
7
  import type { Tokens } from '../Tokens.ts';
7
8
  /**
8
9
  * Builds an authorization from what Tempo Auth returned and the sign-in's key: the material the
@@ -13,6 +14,14 @@ export declare function authorization<chainId extends number = number>(body: Hea
13
14
  accessKey?: Account.Account | undefined;
14
15
  material: Key.Material;
15
16
  }): Authorization<chainId>;
17
+ /**
18
+ * A sign-in's account step as the API returns it: the accounts of a user with more than one, who
19
+ * selects one before a key is authorized.
20
+ */
21
+ export type AccountStep = {
22
+ accounts: readonly TempoAccountResponse[];
23
+ type: 'account';
24
+ };
16
25
  /** What the headless API returns for an authorization. */
17
26
  export type Headless = {
18
27
  account: Key.Key['account'];
@@ -43,6 +52,19 @@ export declare function post(auth: Auth, path: string, options: {
43
52
  * OAuth form posts to WorkOS.
44
53
  */
45
54
  export declare function token(auth: Auth, body: Record<string, string>): Promise<TokenResponse>;
55
+ /** Reads the account's address and the type of its owner, which the API lists first. */
56
+ export declare function tempoAccount(account: TempoAccountResponse): TempoAccount;
57
+ /** A Tempo Account as the API returns it, with the fields the package reads. */
58
+ export type TempoAccountResponse = {
59
+ address: `0x${string}`;
60
+ config: {
61
+ owners: readonly [{
62
+ type: string;
63
+ }, ...{
64
+ type: string;
65
+ }[]];
66
+ };
67
+ };
46
68
  /** What Tempo Auth's token endpoint returns. Redeeming a redirect's code adds its key authorization. */
47
69
  export type TokenResponse = {
48
70
  access_token: string;
@@ -71,6 +71,13 @@ export async function token(auth, body) {
71
71
  method: 'POST',
72
72
  }));
73
73
  }
74
+ /** Reads the account's address and the type of its owner, which the API lists first. */
75
+ export function tempoAccount(account) {
76
+ return {
77
+ address: account.address,
78
+ owner: account.config.owners[0].type,
79
+ };
80
+ }
74
81
  /** Reads a token response into `Tokens`. */
75
82
  export function tokens(response) {
76
83
  return {
@@ -7,6 +7,8 @@ import type { Permissions } from '../Permissions.ts';
7
7
  */
8
8
  export declare function request(auth: Auth, options: {
9
9
  accessKey: Account.Account;
10
+ /** The user's Tempo Account the key acts for, in place of the one `accessKey` names. */
11
+ account?: `0x${string}` | undefined;
10
12
  /** A message the key's witness binds. */
11
13
  challenge?: string | undefined;
12
14
  permissions?: Permissions | undefined;
@@ -10,11 +10,13 @@ import { Hex } from 'ox';
10
10
  */
11
11
  export function request(auth, options) {
12
12
  const { accessKey, challenge, permissions = {} } = options;
13
+ const account = options.account ??
14
+ (accessKey.accessKeyAddress ? accessKey.address : undefined);
13
15
  return {
14
16
  ...(challenge === undefined ? {} : { challenge }),
15
17
  client_id: auth.clientId,
16
18
  key_authorization: {
17
- ...(accessKey.accessKeyAddress ? { account: accessKey.address } : {}),
19
+ ...(account ? { account } : {}),
18
20
  address: accessKey.accessKeyAddress ?? accessKey.address,
19
21
  chainId: Hex.fromNumber(permissions.chainId ?? auth.chainId),
20
22
  expiry: permissions.expiry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tempoxyz/auth",
3
- "version": "0.0.0-pr-74.sha-aa4f4e2",
3
+ "version": "0.0.0-pr-87.sha-7bb5600",
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": {