@tempoxyz/auth 0.0.0-pr-74.sha-0691fe1 → 0.0.0-pr-63.sha-97afb3d
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 +0 -7
- package/dist/Actions.js +0 -2
- package/dist/Auth.d.ts +3 -4
- package/dist/Decorator.d.ts +1 -11
- package/dist/Decorator.js +24 -55
- package/dist/TempoAccount.d.ts +1 -7
- package/dist/TempoAccount.js +1 -1
- package/dist/actions/device.d.ts +0 -6
- package/dist/actions/device.js +0 -3
- package/dist/actions/email.d.ts +0 -13
- package/dist/actions/email.js +3 -15
- package/dist/actions/redirect.d.ts +0 -12
- package/dist/actions/redirect.js +10 -7
- package/dist/actions/tempoAccount.js +3 -9
- package/dist/internal/accessKey.d.ts +0 -5
- package/dist/internal/accessKey.js +0 -11
- package/dist/internal/request.d.ts +0 -3
- package/dist/internal/request.js +1 -2
- package/package.json +1 -1
- package/dist/actions/passkey.d.ts +0 -70
- package/dist/actions/passkey.js +0 -60
- package/dist/internal/link.d.ts +0 -31
- package/dist/internal/link.js +0 -22
- package/dist/internal/store.d.ts +0 -11
- package/dist/internal/store.js +0 -14
package/dist/Actions.d.ts
CHANGED
|
@@ -12,7 +12,6 @@ import type { Tokens } from './Tokens.ts';
|
|
|
12
12
|
export * as device from './actions/device.ts';
|
|
13
13
|
export * as email from './actions/email.ts';
|
|
14
14
|
export * as oauth from './actions/oauth.ts';
|
|
15
|
-
export * as passkey from './actions/passkey.ts';
|
|
16
15
|
export * as provider from './actions/provider.ts';
|
|
17
16
|
export * as redirect from './actions/redirect.ts';
|
|
18
17
|
export * as tempoAccount from './actions/tempoAccount.ts';
|
|
@@ -35,12 +34,6 @@ export declare namespace authorize {
|
|
|
35
34
|
* defaults to the user's first. Defaults to a new key.
|
|
36
35
|
*/
|
|
37
36
|
accessKey?: Account.Account | undefined;
|
|
38
|
-
/**
|
|
39
|
-
* A message the owner's signature binds, such as a sign-in challenge the app's backend issued.
|
|
40
|
-
* Tempo Auth binds its EIP-191 hash as the key authorization's `witness`. Only an app with the
|
|
41
|
-
* `tempo-auth:sign` scope may send one.
|
|
42
|
-
*/
|
|
43
|
-
challenge?: string | undefined;
|
|
44
37
|
/** What the key may do. It must fit the headless ceiling, which fills what it leaves out. */
|
|
45
38
|
permissions?: Permissions<chainId> | undefined;
|
|
46
39
|
};
|
package/dist/Actions.js
CHANGED
|
@@ -4,7 +4,6 @@ import { request } from './internal/request.js';
|
|
|
4
4
|
export * as device from './actions/device.js';
|
|
5
5
|
export * as email from './actions/email.js';
|
|
6
6
|
export * as oauth from './actions/oauth.js';
|
|
7
|
-
export * as passkey from './actions/passkey.js';
|
|
8
7
|
export * as provider from './actions/provider.js';
|
|
9
8
|
export * as redirect from './actions/redirect.js';
|
|
10
9
|
export * as tempoAccount from './actions/tempoAccount.js';
|
|
@@ -30,7 +29,6 @@ export async function authorize(auth, options = {}) {
|
|
|
30
29
|
const response = await Api.post(auth, '/v1/authorize', {
|
|
31
30
|
json: request(auth, {
|
|
32
31
|
accessKey: key.accessKey,
|
|
33
|
-
challenge: options.challenge,
|
|
34
32
|
permissions: options.permissions,
|
|
35
33
|
}),
|
|
36
34
|
});
|
package/dist/Auth.d.ts
CHANGED
|
@@ -51,9 +51,8 @@ export type Auth = {
|
|
|
51
51
|
/** The scopes the app's tokens carry. */
|
|
52
52
|
scope: string;
|
|
53
53
|
/**
|
|
54
|
-
* Where `Actions.redirect.start` keeps its state until Tempo Auth redirects back
|
|
55
|
-
*
|
|
56
|
-
* are, since a browser key is a `CryptoKey`. Defaults to IndexedDB.
|
|
54
|
+
* Where `Actions.redirect.start` keeps its state until Tempo Auth redirects back. It must keep
|
|
55
|
+
* objects as they are, since a browser key is a `CryptoKey`. Defaults to IndexedDB.
|
|
57
56
|
*/
|
|
58
57
|
storage?: Storage | undefined;
|
|
59
58
|
/** Where Tempo Auth runs, such as `https://auth.tempo.xyz`. */
|
|
@@ -87,7 +86,7 @@ export declare namespace RequestError {
|
|
|
87
86
|
uri?: string | undefined;
|
|
88
87
|
};
|
|
89
88
|
/** Each refusal's stable name. `docs/errors.md` explains each one. */
|
|
90
|
-
type Reason = 'already_decided' | 'approval_pending' | 'body_too_large' | '
|
|
89
|
+
type Reason = 'already_decided' | 'approval_pending' | 'body_too_large' | 'cross_site_request' | 'device_code_expired' | 'duplicate_limit' | 'empty_limits' | 'empty_scopes' | 'expiry_exceeds_maximum' | 'expiry_passed' | 'foreign_account' | 'foreign_origin' | 'invalid_chain' | 'invalid_code' | 'invalid_expiry' | 'invalid_field' | 'invalid_origin' | 'invalid_period' | 'invalid_public_key' | 'limit_exceeds_maximum' | 'malformed_body' | 'missing_code_challenge' | 'missing_key' | 'missing_recipients' | 'polling_too_fast' | 'rate_limited' | 'rejected' | 'request_denied' | 'scope_without_limit' | 'session_required' | 'unexpected' | 'unknown_code' | 'unknown_endpoint' | 'unsupported_call' | 'unsupported_step' | 'unsupported_token' | 'upstream_unavailable' | 'webauthn_rejected';
|
|
91
90
|
}
|
|
92
91
|
/** A client's {@link Account} read before a sign-in set it, or after sign-out emptied it. */
|
|
93
92
|
export declare class SignedOutError extends Error {
|
package/dist/Decorator.d.ts
CHANGED
|
@@ -82,10 +82,7 @@ export declare namespace tempoAuth {
|
|
|
82
82
|
email: {
|
|
83
83
|
/** `Actions.email.sendCode`. */
|
|
84
84
|
sendCode: (options: Actions.email.sendCode.Options) => Promise<void>;
|
|
85
|
-
/**
|
|
86
|
-
* `Actions.email.signIn`, which sets the client's account unless it returns a step. A step
|
|
87
|
-
* that another sign-in or a sign-out overtook throws {@link SupersededError}.
|
|
88
|
-
*/
|
|
85
|
+
/** `Actions.email.signIn`, which sets the client's account unless it returns a step. */
|
|
89
86
|
signIn: <const id extends number = chainId>(options: Actions.email.signIn.Options<id>) => Promise<Actions.email.signIn.ReturnType<id>>;
|
|
90
87
|
};
|
|
91
88
|
/** The instance the members call `Actions` with. */
|
|
@@ -98,13 +95,6 @@ export declare namespace tempoAuth {
|
|
|
98
95
|
/** `Actions.oauth.userInfo`. */
|
|
99
96
|
userInfo: (options: Actions.oauth.userInfo.Options) => Promise<User>;
|
|
100
97
|
};
|
|
101
|
-
passkey: {
|
|
102
|
-
/**
|
|
103
|
-
* `Actions.passkey.signIn`, which sets the client's account unless it returns a step. A step
|
|
104
|
-
* that another sign-in or a sign-out overtook throws {@link SupersededError}.
|
|
105
|
-
*/
|
|
106
|
-
signIn: <const id extends number = chainId>(options?: Actions.passkey.signIn.Options<id>) => Promise<Actions.passkey.signIn.ReturnType<id>>;
|
|
107
|
-
};
|
|
108
98
|
provider: {
|
|
109
99
|
/** `Actions.provider.list`. */
|
|
110
100
|
list: () => Promise<Actions.provider.list.ReturnType>;
|
package/dist/Decorator.js
CHANGED
|
@@ -54,58 +54,42 @@ export function tempoAuth(options) {
|
|
|
54
54
|
// sign-in or sign-out completed after it began, so a stale result never replaces a newer one,
|
|
55
55
|
// and a sign-in still pending at sign-out never signs the client back in.
|
|
56
56
|
let version = 0;
|
|
57
|
-
// The version the latest sign-out produced, while no change has followed it.
|
|
58
|
-
let signedOut;
|
|
59
57
|
/**
|
|
60
|
-
* Begins an operation that changes the client's account
|
|
61
|
-
* account to sign with, or `undefined` to stop signing.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* newer state contradicts.
|
|
58
|
+
* Begins an operation that changes the client's account, and returns the function that applies
|
|
59
|
+
* its result: the account to sign with, or `undefined` to stop signing. Applying an account that
|
|
60
|
+
* a newer change overtook throws {@link SupersededError}, so the caller never holds an
|
|
61
|
+
* authorization the client does not sign with.
|
|
65
62
|
*/
|
|
66
63
|
const begin = () => {
|
|
67
64
|
const started = version;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
async apply(account) {
|
|
77
|
-
if (version !== started) {
|
|
78
|
-
if (account)
|
|
79
|
-
throw await superseded();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
state.account = account;
|
|
83
|
-
version++;
|
|
84
|
-
},
|
|
85
|
-
async check() {
|
|
86
|
-
if (version !== started)
|
|
87
|
-
throw await superseded();
|
|
88
|
-
},
|
|
65
|
+
return (account) => {
|
|
66
|
+
if (version !== started) {
|
|
67
|
+
if (account)
|
|
68
|
+
throw new SupersededError();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
state.account = account;
|
|
72
|
+
version++;
|
|
89
73
|
};
|
|
90
74
|
};
|
|
91
75
|
return {
|
|
92
76
|
auth: {
|
|
93
77
|
async authorize(options = {}) {
|
|
94
|
-
const
|
|
78
|
+
const apply = begin();
|
|
95
79
|
const authorization = await Actions.authorize(auth, options);
|
|
96
80
|
// Without a session, the client stops signing as the user it last authorized.
|
|
97
|
-
|
|
81
|
+
apply(authorization?.account);
|
|
98
82
|
return authorization;
|
|
99
83
|
},
|
|
100
84
|
device: {
|
|
101
85
|
async authorize(options = {}) {
|
|
102
|
-
const
|
|
86
|
+
const apply = begin();
|
|
103
87
|
const device = await Actions.device.authorize(auth, options);
|
|
104
88
|
return {
|
|
105
89
|
...device,
|
|
106
90
|
async wait() {
|
|
107
91
|
const authorization = await device.wait();
|
|
108
|
-
|
|
92
|
+
apply(authorization.account);
|
|
109
93
|
return authorization;
|
|
110
94
|
},
|
|
111
95
|
};
|
|
@@ -114,12 +98,10 @@ export function tempoAuth(options) {
|
|
|
114
98
|
email: {
|
|
115
99
|
sendCode: (options) => Actions.email.sendCode(auth, options),
|
|
116
100
|
async signIn(options) {
|
|
117
|
-
const
|
|
101
|
+
const apply = begin();
|
|
118
102
|
const result = await Actions.email.signIn(auth, options);
|
|
119
|
-
if ('step' in result)
|
|
120
|
-
|
|
121
|
-
else
|
|
122
|
-
await change.apply(result.account);
|
|
103
|
+
if (!('step' in result))
|
|
104
|
+
apply(result.account);
|
|
123
105
|
return result;
|
|
124
106
|
},
|
|
125
107
|
},
|
|
@@ -129,35 +111,24 @@ export function tempoAuth(options) {
|
|
|
129
111
|
jwks: () => Actions.oauth.jwks(auth),
|
|
130
112
|
userInfo: (options) => Actions.oauth.userInfo(auth, options),
|
|
131
113
|
},
|
|
132
|
-
passkey: {
|
|
133
|
-
async signIn(options = {}) {
|
|
134
|
-
const change = begin();
|
|
135
|
-
const result = await Actions.passkey.signIn(auth, options);
|
|
136
|
-
if ('step' in result)
|
|
137
|
-
await change.check();
|
|
138
|
-
else
|
|
139
|
-
await change.apply(result.account);
|
|
140
|
-
return result;
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
114
|
provider: {
|
|
144
115
|
list: () => Actions.provider.list(auth),
|
|
145
116
|
signIn: (options) => Actions.provider.signIn(auth, options),
|
|
146
117
|
},
|
|
147
118
|
redirect: {
|
|
148
119
|
async complete(options = {}) {
|
|
149
|
-
const
|
|
120
|
+
const apply = begin();
|
|
150
121
|
const authorization = await Actions.redirect.complete(auth, options);
|
|
151
122
|
// `undefined` means the page is not a callback, which says nothing about the session.
|
|
152
123
|
if (authorization)
|
|
153
|
-
|
|
124
|
+
apply(authorization.account);
|
|
154
125
|
return authorization;
|
|
155
126
|
},
|
|
156
127
|
createUrl: (options = {}) => Actions.redirect.createUrl(auth, options),
|
|
157
128
|
async handleCallback(options) {
|
|
158
|
-
const
|
|
129
|
+
const apply = begin();
|
|
159
130
|
const authorization = await Actions.redirect.handleCallback(auth, options);
|
|
160
|
-
|
|
131
|
+
apply(authorization.account);
|
|
161
132
|
return authorization;
|
|
162
133
|
},
|
|
163
134
|
start: (options = {}) => Actions.redirect.start(auth, options),
|
|
@@ -167,8 +138,7 @@ export function tempoAuth(options) {
|
|
|
167
138
|
// A sign-in stored before `Key.chainId` existed gains it here.
|
|
168
139
|
const key = Key.from(authorization.key);
|
|
169
140
|
const account = Key.toAccount(key, options);
|
|
170
|
-
|
|
171
|
-
version++;
|
|
141
|
+
begin()(account);
|
|
172
142
|
return { ...authorization, account, key };
|
|
173
143
|
},
|
|
174
144
|
async signOut() {
|
|
@@ -176,7 +146,6 @@ export function tempoAuth(options) {
|
|
|
176
146
|
// pending is discarded, since the user asked to leave.
|
|
177
147
|
state.account = undefined;
|
|
178
148
|
version++;
|
|
179
|
-
signedOut = version;
|
|
180
149
|
await Actions.signOut(auth);
|
|
181
150
|
},
|
|
182
151
|
tempoAccount: {
|
package/dist/TempoAccount.d.ts
CHANGED
|
@@ -6,19 +6,13 @@
|
|
|
6
6
|
export type TempoAccount = {
|
|
7
7
|
/** The account's address, the same on every Tempo chain. */
|
|
8
8
|
address: `0x${string}`;
|
|
9
|
-
/**
|
|
10
|
-
* Who holds the account's owner key: a Privy wallet, which Tempo Auth signs with (`privy`), or a
|
|
11
|
-
* passkey, which signs only on a Tempo Auth page (`webAuthn`). Treat another value as an owner
|
|
12
|
-
* that may refuse a key authorization.
|
|
13
|
-
*/
|
|
14
|
-
owner: 'privy' | 'webAuthn' | (string & {});
|
|
15
9
|
};
|
|
16
10
|
/**
|
|
17
11
|
* Instantiates a {@link TempoAccount}.
|
|
18
12
|
*
|
|
19
13
|
* @example
|
|
20
14
|
* ```ts
|
|
21
|
-
* const tempoAccount = TempoAccount.from({ address: '0x…'
|
|
15
|
+
* const tempoAccount = TempoAccount.from({ address: '0x…' })
|
|
22
16
|
* ```
|
|
23
17
|
*/
|
|
24
18
|
export declare function from(value: TempoAccount): TempoAccount;
|
package/dist/TempoAccount.js
CHANGED
package/dist/actions/device.d.ts
CHANGED
|
@@ -21,12 +21,6 @@ export declare namespace authorize {
|
|
|
21
21
|
* defaults to the user's first. Defaults to a new key.
|
|
22
22
|
*/
|
|
23
23
|
accessKey?: Account.Account | undefined;
|
|
24
|
-
/**
|
|
25
|
-
* A message the owner's signature binds, such as a sign-in challenge the app's backend issued.
|
|
26
|
-
* Tempo Auth binds its EIP-191 hash as the key authorization's `witness`. Only an app with the
|
|
27
|
-
* `tempo-auth:sign` scope may send one.
|
|
28
|
-
*/
|
|
29
|
-
challenge?: string | undefined;
|
|
30
24
|
/**
|
|
31
25
|
* The name the approval page shows. Defaults to the machine's hostname in Node, Bun, and Deno,
|
|
32
26
|
* and to `Unknown device` elsewhere.
|
package/dist/actions/device.js
CHANGED
|
@@ -17,9 +17,6 @@ export async function authorize(auth, options = {}) {
|
|
|
17
17
|
const key = await AccessKey.resolve(options.accessKey);
|
|
18
18
|
const code = await Api.json(await Api.post(auth, '/v1/device/authorize', {
|
|
19
19
|
json: {
|
|
20
|
-
...(options.challenge === undefined
|
|
21
|
-
? {}
|
|
22
|
-
: { challenge: options.challenge }),
|
|
23
20
|
device: options.device ?? hostname(),
|
|
24
21
|
key_authorization: request(auth, {
|
|
25
22
|
accessKey: key.accessKey,
|
package/dist/actions/email.d.ts
CHANGED
|
@@ -40,23 +40,10 @@ export declare namespace signIn {
|
|
|
40
40
|
* defaults to the user's first. Defaults to a new key.
|
|
41
41
|
*/
|
|
42
42
|
accessKey?: Account.Account | undefined;
|
|
43
|
-
/**
|
|
44
|
-
* A message the owner's signature binds, such as a sign-in challenge the app's backend issued.
|
|
45
|
-
* Tempo Auth binds its EIP-191 hash as the key authorization's `witness`. Only an app with the
|
|
46
|
-
* `tempo-auth:sign` scope may send one.
|
|
47
|
-
*/
|
|
48
|
-
challenge?: string | undefined;
|
|
49
43
|
/** The 6-digit code the user was emailed. */
|
|
50
44
|
code: string;
|
|
51
45
|
/** The address the code was sent to. */
|
|
52
46
|
email: string;
|
|
53
|
-
/**
|
|
54
|
-
* The `link` from `Actions.passkey.signIn`'s email step, which links that wallet.tempo passkey
|
|
55
|
-
* to the user. When the step is `signed`, the call returns the key that passkey sign-in
|
|
56
|
-
* authorized with its assertion, given the same `accessKey`, `challenge`, and `permissions`.
|
|
57
|
-
* Another `accessKey` replaces the key that sign-in generated.
|
|
58
|
-
*/
|
|
59
|
-
link?: string | undefined;
|
|
60
47
|
/** What the key may do. It must fit the headless ceiling, which fills what it leaves out. */
|
|
61
48
|
permissions?: Permissions<chainId> | undefined;
|
|
62
49
|
};
|
package/dist/actions/email.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as AccessKey from '../internal/accessKey.js';
|
|
2
2
|
import * as Api from '../internal/api.js';
|
|
3
|
-
import * as Link from '../internal/link.js';
|
|
4
3
|
import { request } from '../internal/request.js';
|
|
5
4
|
/**
|
|
6
5
|
* Emails the user a sign-in code. The response is the same for every address.
|
|
@@ -24,26 +23,15 @@ export async function sendCode(auth, options) {
|
|
|
24
23
|
* ```
|
|
25
24
|
*/
|
|
26
25
|
export async function signIn(auth, options) {
|
|
27
|
-
const {
|
|
28
|
-
|
|
29
|
-
// returns once this request names it again. A key the app supplies replaces it.
|
|
30
|
-
const kept = link && !options.accessKey ? await Link.get(auth, { link }) : undefined;
|
|
31
|
-
const key = kept ?? (await AccessKey.resolve(options.accessKey));
|
|
26
|
+
const { code, email, permissions } = options;
|
|
27
|
+
const key = await AccessKey.resolve(options.accessKey);
|
|
32
28
|
const body = await Api.json(await Api.post(auth, '/v1/auth/code/verify', {
|
|
33
29
|
json: {
|
|
34
|
-
authorize: request(auth, {
|
|
35
|
-
accessKey: key.accessKey,
|
|
36
|
-
challenge,
|
|
37
|
-
permissions,
|
|
38
|
-
}),
|
|
30
|
+
authorize: request(auth, { accessKey: key.accessKey, permissions }),
|
|
39
31
|
code,
|
|
40
32
|
email,
|
|
41
|
-
...(link ? { link } : {}),
|
|
42
33
|
},
|
|
43
34
|
}));
|
|
44
|
-
// A refusal throws above, which keeps the key for a retry with a new code.
|
|
45
|
-
if (kept)
|
|
46
|
-
await Link.release(auth);
|
|
47
35
|
if ('step' in body)
|
|
48
36
|
return {
|
|
49
37
|
step: { ...body.step, url: new URL(body.step.url, auth.url).href },
|
|
@@ -45,12 +45,6 @@ export declare namespace createUrl {
|
|
|
45
45
|
* defaults to the user's first. Defaults to a new key, whose material `pending` keeps.
|
|
46
46
|
*/
|
|
47
47
|
accessKey?: Account.Account | undefined;
|
|
48
|
-
/**
|
|
49
|
-
* A message the owner's signature binds, such as a sign-in challenge the app's backend issued.
|
|
50
|
-
* Tempo Auth binds its EIP-191 hash as the key authorization's `witness`. Only an app with the
|
|
51
|
-
* `tempo-auth:sign` scope may send one.
|
|
52
|
-
*/
|
|
53
|
-
challenge?: string | undefined;
|
|
54
48
|
/** What the key may do. Tempo Auth's pages show it, and the headless ceiling fills what it leaves out. */
|
|
55
49
|
permissions?: Permissions | undefined;
|
|
56
50
|
/** Where Tempo Auth redirects back to. Defaults to the instance's `redirectUri`. */
|
|
@@ -103,12 +97,6 @@ export declare namespace start {
|
|
|
103
97
|
* key, which `storage` keeps until the browser returns.
|
|
104
98
|
*/
|
|
105
99
|
accessKey?: Account.Account | undefined;
|
|
106
|
-
/**
|
|
107
|
-
* A message the owner's signature binds, such as a sign-in challenge the app's backend issued.
|
|
108
|
-
* Tempo Auth binds its EIP-191 hash as the key authorization's `witness`. Only an app with the
|
|
109
|
-
* `tempo-auth:sign` scope may send one.
|
|
110
|
-
*/
|
|
111
|
-
challenge?: string | undefined;
|
|
112
100
|
/** What the key may do. Tempo Auth's pages show it, and the headless ceiling fills what it leaves out. */
|
|
113
101
|
permissions?: Permissions | undefined;
|
|
114
102
|
};
|
package/dist/actions/redirect.js
CHANGED
|
@@ -8,8 +8,8 @@ import { Base64, Bytes, Hash } from 'ox';
|
|
|
8
8
|
import { RequestError } from '../Auth.js';
|
|
9
9
|
import * as AccessKey from '../internal/accessKey.js';
|
|
10
10
|
import * as Api from '../internal/api.js';
|
|
11
|
+
import * as IndexedDb from '../internal/indexedDb.js';
|
|
11
12
|
import { request } from '../internal/request.js';
|
|
12
|
-
import * as Store from '../internal/store.js';
|
|
13
13
|
import * as Pending from '../Pending.js';
|
|
14
14
|
/**
|
|
15
15
|
* Finishes a {@link start} when the page loads with Tempo Auth's callback, and returns `undefined`
|
|
@@ -24,8 +24,8 @@ import * as Pending from '../Pending.js';
|
|
|
24
24
|
*/
|
|
25
25
|
export function complete(auth, options = {}) {
|
|
26
26
|
completed ??= (async () => {
|
|
27
|
-
const stored = (await
|
|
28
|
-
await
|
|
27
|
+
const stored = (await storage(auth).getItem(storageKey));
|
|
28
|
+
await storage(auth).removeItem(storageKey);
|
|
29
29
|
const url = new URL(window.location.href);
|
|
30
30
|
if (!url.searchParams.has('code'))
|
|
31
31
|
return undefined;
|
|
@@ -75,9 +75,6 @@ export async function createUrl(auth, options = {}) {
|
|
|
75
75
|
});
|
|
76
76
|
const url = new URL('/authorize', auth.url);
|
|
77
77
|
url.search = new URLSearchParams({
|
|
78
|
-
...(options.challenge === undefined
|
|
79
|
-
? {}
|
|
80
|
-
: { challenge: options.challenge }),
|
|
81
78
|
client_id,
|
|
82
79
|
code_challenge: Base64.fromBytes(Hash.sha256(Bytes.fromString(pending.verifier)), base64url),
|
|
83
80
|
key_authorization: Base64.fromString(JSON.stringify(key_authorization), base64url),
|
|
@@ -148,12 +145,18 @@ export async function handleCallback(auth, options) {
|
|
|
148
145
|
*/
|
|
149
146
|
export async function start(auth, options = {}) {
|
|
150
147
|
const { pending, url } = await createUrl(auth, options);
|
|
151
|
-
await
|
|
148
|
+
await storage(auth).setItem(storageKey, pending);
|
|
152
149
|
window.location.assign(url);
|
|
153
150
|
}
|
|
154
151
|
/** Unpadded URL-safe Base64, as PKCE and `key_authorization` use. */
|
|
155
152
|
const base64url = { pad: false, url: true };
|
|
156
153
|
/** The redirect this page load returns from, so its code is redeemed once. */
|
|
157
154
|
let completed;
|
|
155
|
+
/** The default `storage`, opened on first use. */
|
|
156
|
+
let indexedDb;
|
|
158
157
|
/** Where a redirect keeps its state in `storage`. */
|
|
159
158
|
const storageKey = 'tempo-auth:redirect';
|
|
159
|
+
/** Where a redirect keeps its state: the instance's `storage`, or IndexedDB. */
|
|
160
|
+
function storage(auth) {
|
|
161
|
+
return auth.storage ?? (indexedDb ??= IndexedDb.create());
|
|
162
|
+
}
|
|
@@ -12,7 +12,8 @@ import * as Api from '../internal/api.js';
|
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
14
|
export async function create(auth) {
|
|
15
|
-
|
|
15
|
+
const { address } = await Api.json(await Api.post(auth, '/v1/account/tempo', { json: {} }));
|
|
16
|
+
return { address };
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Lists the signed-in user's Tempo Accounts, their default first.
|
|
@@ -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(
|
|
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(({ address }) => ({ address }));
|
|
37
31
|
}
|
|
@@ -5,11 +5,6 @@ import type * as Key from '../Key.ts';
|
|
|
5
5
|
* a `CryptoKey` across page loads. Elsewhere a CLI or native app must store the key as a string.
|
|
6
6
|
*/
|
|
7
7
|
export declare function resolve(accessKey: Account.Account | undefined): Promise<resolve.ReturnType>;
|
|
8
|
-
/**
|
|
9
|
-
* Rebuilds a key that {@link resolve} generated from its material. Returns `undefined` for the
|
|
10
|
-
* empty material of a key the app supplied.
|
|
11
|
-
*/
|
|
12
|
-
export declare function restore(material: Key.Material): resolve.ReturnType | undefined;
|
|
13
8
|
export declare namespace resolve {
|
|
14
9
|
type ReturnType = {
|
|
15
10
|
/** The key, as a Viem account that signs with it. */
|
|
@@ -24,14 +24,3 @@ export async function resolve(accessKey) {
|
|
|
24
24
|
material: { privateKey },
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Rebuilds a key that {@link resolve} generated from its material. Returns `undefined` for the
|
|
29
|
-
* empty material of a key the app supplied.
|
|
30
|
-
*/
|
|
31
|
-
export function restore(material) {
|
|
32
|
-
if (material.keyPair)
|
|
33
|
-
return { accessKey: Account.fromWebCryptoP256(material.keyPair), material };
|
|
34
|
-
if (material.privateKey)
|
|
35
|
-
return { accessKey: Account.fromSecp256k1(material.privateKey), material };
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
@@ -7,11 +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
|
-
/** A message the key's witness binds. */
|
|
11
|
-
challenge?: string | undefined;
|
|
12
10
|
permissions?: Permissions | undefined;
|
|
13
11
|
}): {
|
|
14
|
-
challenge?: string;
|
|
15
12
|
client_id: string;
|
|
16
13
|
key_authorization: {
|
|
17
14
|
account?: `0x${string}`;
|
package/dist/internal/request.js
CHANGED
|
@@ -9,9 +9,8 @@ import { Hex } from 'ox';
|
|
|
9
9
|
* key account names its Tempo Account through `access`, and a plain key leaves it to Tempo Auth.
|
|
10
10
|
*/
|
|
11
11
|
export function request(auth, options) {
|
|
12
|
-
const { accessKey,
|
|
12
|
+
const { accessKey, permissions = {} } = options;
|
|
13
13
|
return {
|
|
14
|
-
...(challenge === undefined ? {} : { challenge }),
|
|
15
14
|
client_id: auth.clientId,
|
|
16
15
|
key_authorization: {
|
|
17
16
|
...(accessKey.accessKeyAddress ? { account: accessKey.address } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tempoxyz/auth",
|
|
3
|
-
"version": "0.0.0-pr-
|
|
3
|
+
"version": "0.0.0-pr-63.sha-97afb3d",
|
|
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": {
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
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.
|
|
5
|
-
* @module
|
|
6
|
-
*/
|
|
7
|
-
import { Authentication } from 'ox/webauthn';
|
|
8
|
-
import type { Account } from 'viem/tempo';
|
|
9
|
-
import type { Auth } from '../Auth.ts';
|
|
10
|
-
import type { Authorization } from '../Authorization.ts';
|
|
11
|
-
import type { Permissions } from '../Permissions.ts';
|
|
12
|
-
/**
|
|
13
|
-
* Signs the user in with a passkey, and authorizes the app in the same call. On a page, the
|
|
14
|
-
* passkey's one assertion also signs the app's key when that passkey owns the Tempo Account, since
|
|
15
|
-
* the ceremony's challenge is the key's digest. A native app, which sends no `Origin`, signs in
|
|
16
|
-
* with a random challenge and authorizes as a headless sign-in does. Returns the email step for a
|
|
17
|
-
* wallet.tempo passkey whose account has no email: the app signs the user in with
|
|
18
|
-
* `Actions.email.signIn`, passing the step's `link`, which links the passkey, and the same
|
|
19
|
-
* `accessKey`, `challenge`, and `permissions`. A `signed` step's assertion already signed the key,
|
|
20
|
-
* which that call returns without another tap, so a new key waits in the instance's `storage`.
|
|
21
|
-
* Throws a `RequestError` with reason `webauthn_required` when another passkey owns
|
|
22
|
-
* the account, which signs only on Tempo Auth's pages, where `Actions.redirect.start` authorizes it
|
|
23
|
-
* for the signed-in user.
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```ts
|
|
27
|
-
* const result = await Actions.passkey.signIn(auth)
|
|
28
|
-
* if ('step' in result) showEmailForm()
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export declare function signIn<const chainId extends number = number>(auth: Auth, options?: signIn.Options<chainId>): Promise<signIn.ReturnType<chainId>>;
|
|
32
|
-
export declare namespace signIn {
|
|
33
|
-
type Options<chainId extends number = number> = {
|
|
34
|
-
/**
|
|
35
|
-
* 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
|
-
*/
|
|
38
|
-
accessKey?: Account.Account | undefined;
|
|
39
|
-
/**
|
|
40
|
-
* A message the owner's signature binds, such as a sign-in challenge the app's backend issued.
|
|
41
|
-
* Tempo Auth binds its EIP-191 hash as the key authorization's `witness`. Only an app with the
|
|
42
|
-
* `tempo-auth:sign` scope may send one.
|
|
43
|
-
*/
|
|
44
|
-
challenge?: string | undefined;
|
|
45
|
-
/**
|
|
46
|
-
* Runs the WebAuthn ceremony in place of `navigator.credentials.get`, such as a native passkey
|
|
47
|
-
* module. Its assertion must report a web origin on the relying party's domain that the app
|
|
48
|
-
* registered, so an Android app's facet origin is refused.
|
|
49
|
-
*/
|
|
50
|
-
getFn?: Authentication.sign.Options['getFn'] | undefined;
|
|
51
|
-
/** What the key may do. It must fit the headless ceiling, which fills what it leaves out. */
|
|
52
|
-
permissions?: Permissions<chainId> | undefined;
|
|
53
|
-
};
|
|
54
|
-
/** The step a wallet.tempo passkey without an email needs: an emailed code, which links it. */
|
|
55
|
-
type EmailStep = {
|
|
56
|
-
/** Passed as `link` to `Actions.email.signIn`, which links the passkey to that user. */
|
|
57
|
-
link: string;
|
|
58
|
-
/**
|
|
59
|
-
* Whether the assertion already signed the key, which `Actions.email.signIn` returns with
|
|
60
|
-
* `link`. Otherwise that call authorizes the key itself.
|
|
61
|
-
*/
|
|
62
|
-
signed: boolean;
|
|
63
|
-
/** The kind of step. */
|
|
64
|
-
type: 'email';
|
|
65
|
-
};
|
|
66
|
-
/** The authorization, or the email step. */
|
|
67
|
-
type ReturnType<chainId extends number = number> = Authorization<chainId> | {
|
|
68
|
-
step: EmailStep;
|
|
69
|
-
};
|
|
70
|
-
}
|
package/dist/actions/passkey.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
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.
|
|
5
|
-
* @module
|
|
6
|
-
*/
|
|
7
|
-
import { Authentication } from 'ox/webauthn';
|
|
8
|
-
import * as AccessKey from '../internal/accessKey.js';
|
|
9
|
-
import * as Api from '../internal/api.js';
|
|
10
|
-
import * as Link from '../internal/link.js';
|
|
11
|
-
import { request } from '../internal/request.js';
|
|
12
|
-
/**
|
|
13
|
-
* Signs the user in with a passkey, and authorizes the app in the same call. On a page, the
|
|
14
|
-
* passkey's one assertion also signs the app's key when that passkey owns the Tempo Account, since
|
|
15
|
-
* the ceremony's challenge is the key's digest. A native app, which sends no `Origin`, signs in
|
|
16
|
-
* with a random challenge and authorizes as a headless sign-in does. Returns the email step for a
|
|
17
|
-
* wallet.tempo passkey whose account has no email: the app signs the user in with
|
|
18
|
-
* `Actions.email.signIn`, passing the step's `link`, which links the passkey, and the same
|
|
19
|
-
* `accessKey`, `challenge`, and `permissions`. A `signed` step's assertion already signed the key,
|
|
20
|
-
* which that call returns without another tap, so a new key waits in the instance's `storage`.
|
|
21
|
-
* Throws a `RequestError` with reason `webauthn_required` when another passkey owns
|
|
22
|
-
* the account, which signs only on Tempo Auth's pages, where `Actions.redirect.start` authorizes it
|
|
23
|
-
* for the signed-in user.
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```ts
|
|
27
|
-
* const result = await Actions.passkey.signIn(auth)
|
|
28
|
-
* if ('step' in result) showEmailForm()
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export async function signIn(auth, options = {}) {
|
|
32
|
-
const { challenge, getFn, permissions } = options;
|
|
33
|
-
const key = await AccessKey.resolve(options.accessKey);
|
|
34
|
-
const authorize = request(auth, {
|
|
35
|
-
accessKey: key.accessKey,
|
|
36
|
-
challenge,
|
|
37
|
-
permissions,
|
|
38
|
-
});
|
|
39
|
-
// A page sends the request with the options, so the challenge is its key's digest. A native app
|
|
40
|
-
// sends no `Origin` to bind one-tap to, so it sends the request with the assertion instead.
|
|
41
|
-
const native = !/^https?:/i.test(authorize.redirect_uri);
|
|
42
|
-
const requested = await Api.json(await Api.post(auth, '/v1/auth/passkey/options', {
|
|
43
|
-
json: native ? {} : { authorize },
|
|
44
|
-
}));
|
|
45
|
-
const deserialized = Authentication.deserializeOptions(requested.options);
|
|
46
|
-
const assertion = Authentication.serializeResponse(await Authentication.sign(
|
|
47
|
-
// ox reads `getFn` beside request options at runtime, which its types admit only beside a challenge.
|
|
48
|
-
getFn ? { ...deserialized, getFn } : deserialized));
|
|
49
|
-
const body = await Api.json(await Api.post(auth, '/v1/auth/passkey/verify', {
|
|
50
|
-
json: native ? { assertion, authorize } : { assertion },
|
|
51
|
-
}));
|
|
52
|
-
if ('step' in body) {
|
|
53
|
-
// The emailed-code sign-in that sends the link returns this key's authorization, so a key the
|
|
54
|
-
// library generated waits for it in `storage`.
|
|
55
|
-
if (body.step.signed && !options.accessKey)
|
|
56
|
-
await Link.keep(auth, { link: body.step.link, material: key.material });
|
|
57
|
-
return { step: body.step };
|
|
58
|
-
}
|
|
59
|
-
return Api.authorization(body.authorization, key);
|
|
60
|
-
}
|
package/dist/internal/link.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
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.
|
|
4
|
-
* @module
|
|
5
|
-
*/
|
|
6
|
-
import type { Auth } from '../Auth.ts';
|
|
7
|
-
import type * as Key from '../Key.ts';
|
|
8
|
-
import * as AccessKey from './accessKey.ts';
|
|
9
|
-
/** Keeps the key's material for `link`. Keeps nothing outside a browser without `storage`. */
|
|
10
|
-
export declare function keep(auth: Auth, options: keep.Options): Promise<void>;
|
|
11
|
-
export declare namespace keep {
|
|
12
|
-
type Options = {
|
|
13
|
-
/** The email step's `link`. */
|
|
14
|
-
link: string;
|
|
15
|
-
/** The key `AccessKey.resolve` generated. */
|
|
16
|
-
material: Key.Material;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Returns the key kept for `link`, or `undefined` when none is. Leaves it in place, so a refused
|
|
21
|
-
* code retries with the same key, until {@link release} removes it.
|
|
22
|
-
*/
|
|
23
|
-
export declare function get(auth: Auth, options: get.Options): Promise<AccessKey.resolve.ReturnType | undefined>;
|
|
24
|
-
export declare namespace get {
|
|
25
|
-
type Options = {
|
|
26
|
-
/** The `link` the emailed-code sign-in sends. */
|
|
27
|
-
link: string;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/** Removes the kept key, once the emailed-code sign-in that used it finished. */
|
|
31
|
-
export declare function release(auth: Auth): Promise<void>;
|
package/dist/internal/link.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import * as AccessKey from './accessKey.js';
|
|
2
|
-
import * as Store from './store.js';
|
|
3
|
-
/** Where the key waits in `storage`. A later email step replaces it. */
|
|
4
|
-
const storageKey = 'tempo-auth:passkey-link';
|
|
5
|
-
/** Keeps the key's material for `link`. Keeps nothing outside a browser without `storage`. */
|
|
6
|
-
export async function keep(auth, options) {
|
|
7
|
-
await Store.get(auth)?.setItem(storageKey, options);
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Returns the key kept for `link`, or `undefined` when none is. Leaves it in place, so a refused
|
|
11
|
-
* code retries with the same key, until {@link release} removes it.
|
|
12
|
-
*/
|
|
13
|
-
export async function get(auth, options) {
|
|
14
|
-
const kept = (await Store.get(auth)?.getItem(storageKey));
|
|
15
|
-
if (kept?.link !== options.link)
|
|
16
|
-
return undefined;
|
|
17
|
-
return AccessKey.restore(kept.material);
|
|
18
|
-
}
|
|
19
|
-
/** Removes the kept key, once the emailed-code sign-in that used it finished. */
|
|
20
|
-
export async function release(auth) {
|
|
21
|
-
await Store.get(auth)?.removeItem(storageKey);
|
|
22
|
-
}
|
package/dist/internal/store.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolves where actions keep state across page loads: the instance's `storage`, or IndexedDB.
|
|
3
|
-
* @module
|
|
4
|
-
*/
|
|
5
|
-
import type { Auth } from '../Auth.ts';
|
|
6
|
-
import type { Storage } from '../Storage.ts';
|
|
7
|
-
/**
|
|
8
|
-
* Returns the instance's `storage`, or the page's IndexedDB. Returns `undefined` outside a browser
|
|
9
|
-
* when the instance names no `storage`.
|
|
10
|
-
*/
|
|
11
|
-
export declare function get(auth: Auth): Storage | undefined;
|
package/dist/internal/store.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import * as IndexedDb from './indexedDb.js';
|
|
2
|
-
/** The default `storage`, opened on first use. */
|
|
3
|
-
let indexedDb;
|
|
4
|
-
/**
|
|
5
|
-
* Returns the instance's `storage`, or the page's IndexedDB. Returns `undefined` outside a browser
|
|
6
|
-
* when the instance names no `storage`.
|
|
7
|
-
*/
|
|
8
|
-
export function get(auth) {
|
|
9
|
-
if (auth.storage)
|
|
10
|
-
return auth.storage;
|
|
11
|
-
if (typeof indexedDB === 'undefined')
|
|
12
|
-
return undefined;
|
|
13
|
-
return (indexedDb ??= IndexedDb.create());
|
|
14
|
-
}
|