@tempoxyz/auth 0.0.0-pr-66.sha-278d6ea → 0.0.0-pr-66.sha-8d0e9ba

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.
@@ -38,7 +38,8 @@ export declare class ChainRequiredError extends Error {
38
38
  /**
39
39
  * Returns a Viem client extension that adds Tempo Auth's actions as `client.auth`. Each member calls
40
40
  * the action of the same name in `Actions` with one instance, whose `chainId` is the client's chain.
41
- * Each sign-in sets the client's account to the authorization's, and `signOut` empties it. Throws
41
+ * Each sign-in sets the client's account to the authorization's, unless another sign-in or a
42
+ * sign-out completed after it began, and `signOut` empties it at once. Throws
42
43
  * {@link ChainRequiredError} for a client without a chain, and {@link AccountRequiredError} for one
43
44
  * whose account is not an `Auth.account()`.
44
45
  *
@@ -63,7 +64,7 @@ export declare namespace tempoAuth {
63
64
  auth: {
64
65
  /**
65
66
  * `Actions.authorize`, which sets the client's account, or empties it when the user has no
66
- * session. A sign-in or sign-out that completes first keeps its account.
67
+ * session.
67
68
  */
68
69
  authorize: <const id extends number = chainId>(options?: Actions.authorize.Options<id>) => Promise<Actions.authorize.ReturnType<id>>;
69
70
  device: {
@@ -114,7 +115,10 @@ export declare namespace tempoAuth {
114
115
  /** The stored key, with or without its `chainId`. */
115
116
  key: Key.from.Value<id>;
116
117
  }, options?: Key.toAccount.Options) => Authorization<id>;
117
- /** `Actions.signOut`, which empties the client's account, even when the request fails. */
118
+ /**
119
+ * `Actions.signOut`, which empties the client's account before the request, even when it
120
+ * fails, and discards any sign-in still pending.
121
+ */
118
122
  signOut: () => Promise<void>;
119
123
  tempoAccount: {
120
124
  /** `Actions.tempoAccount.create`. */
package/dist/Decorator.js CHANGED
@@ -19,7 +19,8 @@ export class ChainRequiredError extends Error {
19
19
  /**
20
20
  * Returns a Viem client extension that adds Tempo Auth's actions as `client.auth`. Each member calls
21
21
  * the action of the same name in `Actions` with one instance, whose `chainId` is the client's chain.
22
- * Each sign-in sets the client's account to the authorization's, and `signOut` empties it. Throws
22
+ * Each sign-in sets the client's account to the authorization's, unless another sign-in or a
23
+ * sign-out completed after it began, and `signOut` empties it at once. Throws
23
24
  * {@link ChainRequiredError} for a client without a chain, and {@link AccountRequiredError} for one
24
25
  * whose account is not an `Auth.account()`.
25
26
  *
@@ -39,41 +40,53 @@ export function tempoAuth(options) {
39
40
  if (!state)
40
41
  throw new AccountRequiredError();
41
42
  const auth = Auth.create({ ...options, chainId: client.chain.id });
42
- // Counts changes to the client's account, so a session check that a sign-in or sign-out
43
- // overtook leaves the newer account in place.
43
+ // Counts changes to the client's account. Each sign-in applies its result only when no other
44
+ // sign-in or sign-out completed after it began, so a stale result never replaces a newer one,
45
+ // and a sign-in still pending at sign-out never signs the client back in.
44
46
  let version = 0;
45
- /** Sets the client's account to the authorization's, and returns the authorization. */
46
- const set = (authorization) => {
47
- if (authorization) {
48
- state.account = authorization.account;
47
+ /**
48
+ * Begins an operation that changes the client's account, and returns the function that applies
49
+ * its result: the account to sign with, or `undefined` to stop signing.
50
+ */
51
+ const begin = () => {
52
+ const started = version;
53
+ return (account) => {
54
+ if (version !== started)
55
+ return;
56
+ state.account = account;
49
57
  version++;
50
- }
51
- return authorization;
58
+ };
52
59
  };
53
60
  return {
54
61
  auth: {
55
62
  async authorize(options = {}) {
56
- const started = version;
63
+ const apply = begin();
57
64
  const authorization = await Actions.authorize(auth, options);
58
- if (version !== started)
59
- return authorization;
60
65
  // Without a session, the client stops signing as the user it last authorized.
61
- if (!authorization)
62
- state.account = undefined;
63
- return set(authorization);
66
+ apply(authorization?.account);
67
+ return authorization;
64
68
  },
65
69
  device: {
66
70
  async authorize(options = {}) {
71
+ const apply = begin();
67
72
  const device = await Actions.device.authorize(auth, options);
68
- return { ...device, wait: async () => set(await device.wait()) };
73
+ return {
74
+ ...device,
75
+ async wait() {
76
+ const authorization = await device.wait();
77
+ apply(authorization.account);
78
+ return authorization;
79
+ },
80
+ };
69
81
  },
70
82
  },
71
83
  email: {
72
84
  sendCode: (options) => Actions.email.sendCode(auth, options),
73
85
  async signIn(options) {
86
+ const apply = begin();
74
87
  const result = await Actions.email.signIn(auth, options);
75
88
  if (!('step' in result))
76
- set(result);
89
+ apply(result.account);
77
90
  return result;
78
91
  },
79
92
  },
@@ -88,30 +101,37 @@ export function tempoAuth(options) {
88
101
  signIn: (options) => Actions.provider.signIn(auth, options),
89
102
  },
90
103
  redirect: {
91
- complete: async (options = {}) => set(await Actions.redirect.complete(auth, options)),
104
+ async complete(options = {}) {
105
+ const apply = begin();
106
+ const authorization = await Actions.redirect.complete(auth, options);
107
+ // `undefined` means the page is not a callback, which says nothing about the session.
108
+ if (authorization)
109
+ apply(authorization.account);
110
+ return authorization;
111
+ },
92
112
  createUrl: (options = {}) => Actions.redirect.createUrl(auth, options),
93
- handleCallback: async (options) => set(await Actions.redirect.handleCallback(auth, options)),
113
+ async handleCallback(options) {
114
+ const apply = begin();
115
+ const authorization = await Actions.redirect.handleCallback(auth, options);
116
+ apply(authorization.account);
117
+ return authorization;
118
+ },
94
119
  start: (options = {}) => Actions.redirect.start(auth, options),
95
120
  },
96
121
  refresh: (options) => Actions.refresh(auth, options),
97
122
  restore: (authorization, options = {}) => {
98
123
  // A sign-in stored before `Key.chainId` existed gains it here.
99
124
  const key = Key.from(authorization.key);
100
- return set({
101
- ...authorization,
102
- account: Key.toAccount(key, options),
103
- key,
104
- });
125
+ const account = Key.toAccount(key, options);
126
+ begin()(account);
127
+ return { ...authorization, account, key };
105
128
  },
106
129
  async signOut() {
107
- // The client stops signing even when the request fails, since the user asked to leave.
108
- try {
109
- await Actions.signOut(auth);
110
- }
111
- finally {
112
- state.account = undefined;
113
- version++;
114
- }
130
+ // The client stops signing at once, even when the request fails, and any sign-in still
131
+ // pending is discarded, since the user asked to leave.
132
+ state.account = undefined;
133
+ version++;
134
+ await Actions.signOut(auth);
115
135
  },
116
136
  tempoAccount: {
117
137
  create: () => Actions.tempoAccount.create(auth),
@@ -53,7 +53,9 @@ export declare namespace introspect {
53
53
  * import { createLocalJWKSet, jwtVerify } from 'jose'
54
54
  *
55
55
  * const keys = createLocalJWKSet(await Actions.oauth.jwks(auth))
56
- * const { payload } = await jwtVerify(accessToken, keys, { issuer })
56
+ * const { payload } = await jwtVerify(accessToken, keys, { audience, issuer })
57
+ * // Every app's token carries the same audience, so an API also allows each app by `client_id`.
58
+ * if (!allowedClientIds.includes(String(payload.client_id))) throw forbidden()
57
59
  * ```
58
60
  */
59
61
  export declare function jwks(auth: Auth): Promise<jwks.ReturnType>;
@@ -43,7 +43,9 @@ export async function introspect(auth, options) {
43
43
  * import { createLocalJWKSet, jwtVerify } from 'jose'
44
44
  *
45
45
  * const keys = createLocalJWKSet(await Actions.oauth.jwks(auth))
46
- * const { payload } = await jwtVerify(accessToken, keys, { issuer })
46
+ * const { payload } = await jwtVerify(accessToken, keys, { audience, issuer })
47
+ * // Every app's token carries the same audience, so an API also allows each app by `client_id`.
48
+ * if (!allowedClientIds.includes(String(payload.client_id))) throw forbidden()
47
49
  * ```
48
50
  */
49
51
  export async function jwks(auth) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tempoxyz/auth",
3
- "version": "0.0.0-pr-66.sha-278d6ea",
3
+ "version": "0.0.0-pr-66.sha-8d0e9ba",
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": {