@tempoxyz/auth 0.0.0-pr-6.sha-17bb901
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 +241 -0
- package/dist/Actions.js +372 -0
- package/dist/Auth.d.ts +128 -0
- package/dist/Auth.js +26 -0
- package/dist/Key.d.ts +39 -0
- package/dist/Key.js +39 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/internal/request.d.ts +29 -0
- package/dist/internal/request.js +28 -0
- package/package.json +32 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { type AccessKey, type Auth, type Authorization, type Pending, type Provider, type Step, type Tokens } from './Auth.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Authorizes the app for a person already signed in to Tempo Auth, from any app. Returns
|
|
4
|
+
* `undefined` without a session, so the app shows sign-in. The key must fit Tempo Auth's headless
|
|
5
|
+
* ceiling.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const authorization = await Actions.authorize(auth, { accessKey })
|
|
10
|
+
* if (!authorization) showSignIn()
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function authorize(auth: Auth, options: authorize.Options): Promise<authorize.ReturnType>;
|
|
14
|
+
export declare namespace authorize {
|
|
15
|
+
type Options = {
|
|
16
|
+
/** What the app's key may do. It must fit the headless ceiling. */
|
|
17
|
+
accessKey: AccessKey;
|
|
18
|
+
};
|
|
19
|
+
/** The authorization, or `undefined` without a session. */
|
|
20
|
+
type ReturnType = Authorization | undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Starts the device grant for a CLI: the person approves `userCode` at `verificationUri` from any
|
|
24
|
+
* browser, and `wait` resolves once they do.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const device = await Actions.authorizeDevice(auth, { accessKey, device: Os.hostname() })
|
|
29
|
+
* console.log(`Open ${device.verificationUriComplete}`)
|
|
30
|
+
* const authorization = await device.wait()
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function authorizeDevice(auth: Auth, options: authorizeDevice.Options): Promise<authorizeDevice.ReturnType>;
|
|
34
|
+
export declare namespace authorizeDevice {
|
|
35
|
+
type Options = {
|
|
36
|
+
/** What the CLI's key may do. The approval page shows it beside the device. */
|
|
37
|
+
accessKey: AccessKey;
|
|
38
|
+
/** The name the approval page shows, such as the machine's hostname. */
|
|
39
|
+
device: string;
|
|
40
|
+
};
|
|
41
|
+
type ReturnType = {
|
|
42
|
+
/** Seconds until the code expires. */
|
|
43
|
+
expiresIn: number;
|
|
44
|
+
/** The code the person confirms in the browser. */
|
|
45
|
+
userCode: string;
|
|
46
|
+
/** Where the person enters the code. */
|
|
47
|
+
verificationUri: string;
|
|
48
|
+
/** Where the person approves the code, with it already filled in. */
|
|
49
|
+
verificationUriComplete: string;
|
|
50
|
+
/** Resolves with the authorization once the person approves. */
|
|
51
|
+
wait: () => Promise<Authorization>;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Returns the URL that signs the person in on Tempo Auth's pages and redirects back with a code
|
|
56
|
+
* and the key authorization, and the {@link Pending} state to keep until then, such as in a
|
|
57
|
+
* sealed cookie. {@link handleCallback} finishes it.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const { pending, url } = await Actions.createAuthorizationUrl(auth, {
|
|
62
|
+
* accessKey,
|
|
63
|
+
* redirectUri: 'https://app.example/auth/callback',
|
|
64
|
+
* })
|
|
65
|
+
* await seal(c, 'oauth', pending)
|
|
66
|
+
* return c.redirect(url)
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function createAuthorizationUrl(auth: Auth, options: createAuthorizationUrl.Options): Promise<createAuthorizationUrl.ReturnType>;
|
|
70
|
+
export declare namespace createAuthorizationUrl {
|
|
71
|
+
type Options = {
|
|
72
|
+
/** What the app's key may do. Tempo Auth's pages show it, and the person approves it. */
|
|
73
|
+
accessKey: AccessKey;
|
|
74
|
+
/** Where Tempo Auth redirects back to. Defaults to the instance's `redirectUri`. */
|
|
75
|
+
redirectUri?: string | undefined;
|
|
76
|
+
};
|
|
77
|
+
type ReturnType = {
|
|
78
|
+
/** What to keep until the callback, such as in a sealed cookie. */
|
|
79
|
+
pending: Pending;
|
|
80
|
+
/** Where to send the browser. */
|
|
81
|
+
url: string;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Lists the OAuth providers Tempo Auth offers, for an app that shows its own sign-in buttons.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const providers = await Actions.getProviders(auth)
|
|
90
|
+
* // [{ id: 'google', name: 'Google' }]
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare function getProviders(auth: Auth): Promise<getProviders.ReturnType>;
|
|
94
|
+
export declare namespace getProviders {
|
|
95
|
+
type ReturnType = readonly {
|
|
96
|
+
/** The provider's short name, which `signIn` takes. */
|
|
97
|
+
id: Provider;
|
|
98
|
+
/** The name to show, such as `Google`. */
|
|
99
|
+
name: string;
|
|
100
|
+
}[];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Finishes a sign-in redirect: checks its state, redeems the code with the PKCE verifier (and the
|
|
104
|
+
* client secret, for a backend), and reads the key authorization from the callback URL.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* const authorization = await Actions.handleCallback(auth, {
|
|
109
|
+
* pending: await unseal(c, 'oauth'),
|
|
110
|
+
* url: c.req.url,
|
|
111
|
+
* })
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export declare function handleCallback(auth: Auth, options: handleCallback.Options): Promise<Authorization>;
|
|
115
|
+
export declare namespace handleCallback {
|
|
116
|
+
type Options = {
|
|
117
|
+
/** What `createAuthorizationUrl` returned to keep. */
|
|
118
|
+
pending: Pending;
|
|
119
|
+
/** The callback URL Tempo Auth redirected to. */
|
|
120
|
+
url: string | URL;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Finishes a {@link redirect} when the page loads with Tempo Auth's callback, and returns
|
|
125
|
+
* `undefined` otherwise. The code is redeemed once, however often the page calls this.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* const authorization =
|
|
130
|
+
* (await Actions.handleRedirect(auth)) ??
|
|
131
|
+
* (await Actions.authorize(auth, { accessKey }))
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare function handleRedirect(auth: Auth): Promise<handleRedirect.ReturnType>;
|
|
135
|
+
export declare namespace handleRedirect {
|
|
136
|
+
/** The authorization, or `undefined` when the page is not a redirect's callback. */
|
|
137
|
+
type ReturnType = Authorization | undefined;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Sends the browser to Tempo Auth's pages to sign in, keeping the redirect's state in `storage`.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* await Actions.redirect(auth, { accessKey })
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export declare function redirect(auth: Auth, options: redirect.Options): Promise<void>;
|
|
148
|
+
export declare namespace redirect {
|
|
149
|
+
type Options = {
|
|
150
|
+
/** What the app's key may do. Tempo Auth's pages show it, and the person approves it. */
|
|
151
|
+
accessKey: AccessKey;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Exchanges a refresh token for new tokens. Refresh tokens rotate, so store the new one.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* const tokens = await Actions.refresh(auth, {
|
|
160
|
+
* refreshToken: saved.tokens.refreshToken,
|
|
161
|
+
* })
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export declare function refresh(auth: Auth, options: refresh.Options): Promise<Tokens>;
|
|
165
|
+
export declare namespace refresh {
|
|
166
|
+
type Options = {
|
|
167
|
+
/** The latest refresh token. Each refresh rotates it. */
|
|
168
|
+
refreshToken: string;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Emails the person a sign-in code. The response is the same for every address.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* await Actions.sendCode(auth, { email: 'person@example.com' })
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export declare function sendCode(auth: Auth, options: sendCode.Options): Promise<void>;
|
|
180
|
+
export declare namespace sendCode {
|
|
181
|
+
type Options = {
|
|
182
|
+
/** The person's email address. */
|
|
183
|
+
email: string;
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Sends the browser to an OAuth provider, or to the SSO connection a {@link Step} names, and back to
|
|
188
|
+
* `returnTo` signed in to Tempo Auth. The app then calls {@link authorize}.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```ts
|
|
192
|
+
* Actions.signIn(auth, { provider: 'google' })
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
export declare function signIn(auth: Auth, options: signIn.Options): void;
|
|
196
|
+
export declare namespace signIn {
|
|
197
|
+
type Options = ({
|
|
198
|
+
/** The OAuth provider to sign in with, from `getProviders`. */
|
|
199
|
+
provider: Provider;
|
|
200
|
+
} | {
|
|
201
|
+
/** The URL of a `Step`, such as the SSO connection an address requires. */
|
|
202
|
+
url: string;
|
|
203
|
+
}) & {
|
|
204
|
+
/** Where to return. Defaults to the current page. It must be on an internal origin. */
|
|
205
|
+
returnTo?: string | undefined;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Ends the person's Tempo Auth session, which signs them out of every app that relies on it.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* await Actions.signOut(auth)
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
export declare function signOut(auth: Auth): Promise<void>;
|
|
217
|
+
/**
|
|
218
|
+
* Signs the person in with the code they were emailed, and authorizes the app in the same call.
|
|
219
|
+
* Returns a {@link Step} when the address requires SSO, which {@link signIn} continues.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```ts
|
|
223
|
+
* const result = await Actions.verifyCode(auth, { accessKey, code, email })
|
|
224
|
+
* if ('step' in result) Actions.signIn(auth, { url: result.step.url })
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
export declare function verifyCode(auth: Auth, options: verifyCode.Options): Promise<verifyCode.ReturnType>;
|
|
228
|
+
export declare namespace verifyCode {
|
|
229
|
+
type Options = {
|
|
230
|
+
/** What the app's key may do. It must fit the headless ceiling. */
|
|
231
|
+
accessKey: AccessKey;
|
|
232
|
+
/** The 6-digit code the person was emailed. */
|
|
233
|
+
code: string;
|
|
234
|
+
/** The address the code was sent to. */
|
|
235
|
+
email: string;
|
|
236
|
+
};
|
|
237
|
+
/** The authorization, or the step the address requires first. */
|
|
238
|
+
type ReturnType = Authorization | {
|
|
239
|
+
step: Step;
|
|
240
|
+
};
|
|
241
|
+
}
|
package/dist/Actions.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tempo Auth's actions. Each takes an instance from `Auth.create`.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
import { Base64, Bytes, Hash, Hex } from 'ox';
|
|
6
|
+
import { Secp256k1 } from 'viem/tempo';
|
|
7
|
+
import { RequestError, } from './Auth.js';
|
|
8
|
+
import * as Key from './Key.js';
|
|
9
|
+
import { request } from './internal/request.js';
|
|
10
|
+
/**
|
|
11
|
+
* Authorizes the app for a person already signed in to Tempo Auth, from any app. Returns
|
|
12
|
+
* `undefined` without a session, so the app shows sign-in. The key must fit Tempo Auth's headless
|
|
13
|
+
* ceiling.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const authorization = await Actions.authorize(auth, { accessKey })
|
|
18
|
+
* if (!authorization) showSignIn()
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export async function authorize(auth, options) {
|
|
22
|
+
// Checking the session first answers a visitor without one with no failed request.
|
|
23
|
+
const session = await json(await (auth.fetch ?? fetch)(new URL('/api/auth/session', auth.url), {
|
|
24
|
+
credentials: 'include',
|
|
25
|
+
}));
|
|
26
|
+
if (!session.user)
|
|
27
|
+
return undefined;
|
|
28
|
+
const privateKey = Secp256k1.randomPrivateKey();
|
|
29
|
+
const response = await post(auth, '/api/authorize', {
|
|
30
|
+
json: request(auth, { ...options, privateKey }),
|
|
31
|
+
});
|
|
32
|
+
if (response.status === 401)
|
|
33
|
+
return undefined;
|
|
34
|
+
return authorization(await json(response), { privateKey });
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Starts the device grant for a CLI: the person approves `userCode` at `verificationUri` from any
|
|
38
|
+
* browser, and `wait` resolves once they do.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const device = await Actions.authorizeDevice(auth, { accessKey, device: Os.hostname() })
|
|
43
|
+
* console.log(`Open ${device.verificationUriComplete}`)
|
|
44
|
+
* const authorization = await device.wait()
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export async function authorizeDevice(auth, options) {
|
|
48
|
+
const privateKey = Secp256k1.randomPrivateKey();
|
|
49
|
+
const code = await json(await post(auth, '/api/device/authorize', {
|
|
50
|
+
json: {
|
|
51
|
+
device: options.device,
|
|
52
|
+
keyAuthorization: request(auth, { ...options, privateKey })
|
|
53
|
+
.keyAuthorization,
|
|
54
|
+
},
|
|
55
|
+
}));
|
|
56
|
+
/** Polls until the person approves or denies the code, or it expires. */
|
|
57
|
+
async function wait() {
|
|
58
|
+
let interval = code.interval;
|
|
59
|
+
const deadline = Date.now() + code.expires_in * 1000;
|
|
60
|
+
while (Date.now() < deadline) {
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve, interval * 1000));
|
|
62
|
+
const response = await post(auth, '/api/device/token', {
|
|
63
|
+
json: { device_code: code.device_code },
|
|
64
|
+
});
|
|
65
|
+
const body = (await response.json());
|
|
66
|
+
if ('access_token' in body)
|
|
67
|
+
return authorization({
|
|
68
|
+
account: body.account,
|
|
69
|
+
keyAuthorization: body.key_authorization,
|
|
70
|
+
tokens: tokens(body),
|
|
71
|
+
}, { privateKey });
|
|
72
|
+
// RFC 8628 asks a client told to slow down to add 5 seconds to its interval.
|
|
73
|
+
if (body.error === 'slow_down')
|
|
74
|
+
interval += 5;
|
|
75
|
+
else if (body.error !== 'authorization_pending')
|
|
76
|
+
throw new RequestError({
|
|
77
|
+
message: `Sign-in failed: ${body.error}.`,
|
|
78
|
+
status: response.status,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
throw new RequestError({ message: 'The code expired.', status: 400 });
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
expiresIn: code.expires_in,
|
|
85
|
+
userCode: code.user_code,
|
|
86
|
+
verificationUri: code.verification_uri,
|
|
87
|
+
verificationUriComplete: code.verification_uri_complete,
|
|
88
|
+
wait,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Returns the URL that signs the person in on Tempo Auth's pages and redirects back with a code
|
|
93
|
+
* and the key authorization, and the {@link Pending} state to keep until then, such as in a
|
|
94
|
+
* sealed cookie. {@link handleCallback} finishes it.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const { pending, url } = await Actions.createAuthorizationUrl(auth, {
|
|
99
|
+
* accessKey,
|
|
100
|
+
* redirectUri: 'https://app.example/auth/callback',
|
|
101
|
+
* })
|
|
102
|
+
* await seal(c, 'oauth', pending)
|
|
103
|
+
* return c.redirect(url)
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export async function createAuthorizationUrl(auth, options) {
|
|
107
|
+
const redirectUri = options.redirectUri ?? auth.redirectUri ?? `${window.location.origin}/`;
|
|
108
|
+
const pending = {
|
|
109
|
+
privateKey: Secp256k1.randomPrivateKey(),
|
|
110
|
+
redirectUri,
|
|
111
|
+
state: crypto.randomUUID(),
|
|
112
|
+
verifier: Base64.fromBytes(Bytes.random(32), base64url),
|
|
113
|
+
};
|
|
114
|
+
const { client_id, keyAuthorization, scope } = request(auth, {
|
|
115
|
+
accessKey: options.accessKey,
|
|
116
|
+
privateKey: pending.privateKey,
|
|
117
|
+
});
|
|
118
|
+
const url = new URL('/authorize', auth.url);
|
|
119
|
+
url.search = new URLSearchParams({
|
|
120
|
+
client_id,
|
|
121
|
+
code_challenge: Base64.fromBytes(Hash.sha256(Bytes.fromString(pending.verifier)), base64url),
|
|
122
|
+
key_authorization: Base64.fromString(JSON.stringify(keyAuthorization), base64url),
|
|
123
|
+
redirect_uri: redirectUri,
|
|
124
|
+
scope,
|
|
125
|
+
state: pending.state,
|
|
126
|
+
}).toString();
|
|
127
|
+
return { pending, url: url.href };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Lists the OAuth providers Tempo Auth offers, for an app that shows its own sign-in buttons.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* const providers = await Actions.getProviders(auth)
|
|
135
|
+
* // [{ id: 'google', name: 'Google' }]
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export async function getProviders(auth) {
|
|
139
|
+
const { providers } = await json(await (auth.fetch ?? fetch)(new URL('/api/auth/providers', auth.url)));
|
|
140
|
+
return providers;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Finishes a sign-in redirect: checks its state, redeems the code with the PKCE verifier (and the
|
|
144
|
+
* client secret, for a backend), and reads the key authorization from the callback URL.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* const authorization = await Actions.handleCallback(auth, {
|
|
149
|
+
* pending: await unseal(c, 'oauth'),
|
|
150
|
+
* url: c.req.url,
|
|
151
|
+
* })
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export async function handleCallback(auth, options) {
|
|
155
|
+
const { pending } = options;
|
|
156
|
+
const params = new URL(options.url).searchParams;
|
|
157
|
+
const code = params.get('code');
|
|
158
|
+
const account = params.get('account');
|
|
159
|
+
const keyAuthorization = params.get('key_authorization');
|
|
160
|
+
if (!code ||
|
|
161
|
+
!account ||
|
|
162
|
+
!keyAuthorization ||
|
|
163
|
+
params.get('state') !== pending.state)
|
|
164
|
+
throw new RequestError({
|
|
165
|
+
message: 'Sign-in failed. Start again.',
|
|
166
|
+
status: 400,
|
|
167
|
+
});
|
|
168
|
+
return authorization({
|
|
169
|
+
account: account,
|
|
170
|
+
keyAuthorization: JSON.parse(Base64.toString(keyAuthorization)),
|
|
171
|
+
tokens: await token(auth, {
|
|
172
|
+
code,
|
|
173
|
+
code_verifier: pending.verifier,
|
|
174
|
+
grant_type: 'authorization_code',
|
|
175
|
+
redirect_uri: pending.redirectUri,
|
|
176
|
+
}),
|
|
177
|
+
}, pending);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Finishes a {@link redirect} when the page loads with Tempo Auth's callback, and returns
|
|
181
|
+
* `undefined` otherwise. The code is redeemed once, however often the page calls this.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* const authorization =
|
|
186
|
+
* (await Actions.handleRedirect(auth)) ??
|
|
187
|
+
* (await Actions.authorize(auth, { accessKey }))
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
export function handleRedirect(auth) {
|
|
191
|
+
redirected ??= (async () => {
|
|
192
|
+
const stored = storage(auth).getItem(storageKey);
|
|
193
|
+
storage(auth).removeItem(storageKey);
|
|
194
|
+
const url = new URL(window.location.href);
|
|
195
|
+
if (!stored || !url.searchParams.has('code'))
|
|
196
|
+
return undefined;
|
|
197
|
+
// The app keeps its own query and fragment, without the callback's parameters.
|
|
198
|
+
const clean = new URL(url);
|
|
199
|
+
for (const name of ['account', 'code', 'key_authorization', 'state'])
|
|
200
|
+
clean.searchParams.delete(name);
|
|
201
|
+
window.history.replaceState(null, '', clean.pathname + clean.search + clean.hash);
|
|
202
|
+
return handleCallback(auth, { pending: JSON.parse(stored), url });
|
|
203
|
+
})();
|
|
204
|
+
return redirected;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Sends the browser to Tempo Auth's pages to sign in, keeping the redirect's state in `storage`.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* await Actions.redirect(auth, { accessKey })
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export async function redirect(auth, options) {
|
|
215
|
+
const { pending, url } = await createAuthorizationUrl(auth, options);
|
|
216
|
+
storage(auth).setItem(storageKey, JSON.stringify(pending));
|
|
217
|
+
window.location.assign(url);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Exchanges a refresh token for new tokens. Refresh tokens rotate, so store the new one.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* const tokens = await Actions.refresh(auth, {
|
|
225
|
+
* refreshToken: saved.tokens.refreshToken,
|
|
226
|
+
* })
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
export function refresh(auth, options) {
|
|
230
|
+
return token(auth, {
|
|
231
|
+
grant_type: 'refresh_token',
|
|
232
|
+
refresh_token: options.refreshToken,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Emails the person a sign-in code. The response is the same for every address.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* await Actions.sendCode(auth, { email: 'person@example.com' })
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
export async function sendCode(auth, options) {
|
|
244
|
+
await json(await post(auth, '/api/auth/code', { json: options }));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Sends the browser to an OAuth provider, or to the SSO connection a {@link Step} names, and back to
|
|
248
|
+
* `returnTo` signed in to Tempo Auth. The app then calls {@link authorize}.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* Actions.signIn(auth, { provider: 'google' })
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
export function signIn(auth, options) {
|
|
256
|
+
const url = 'url' in options
|
|
257
|
+
? new URL(options.url)
|
|
258
|
+
: new URL(`/api/auth/oauth/${options.provider}`, auth.url);
|
|
259
|
+
url.searchParams.set('return_to', options.returnTo ?? window.location.href);
|
|
260
|
+
window.location.assign(url);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Ends the person's Tempo Auth session, which signs them out of every app that relies on it.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* await Actions.signOut(auth)
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
export async function signOut(auth) {
|
|
271
|
+
// A JSON body keeps the request out of Tempo Auth's CSRF check for cross-site form posts.
|
|
272
|
+
await json(await post(auth, '/api/auth/logout', { json: {} }));
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Signs the person in with the code they were emailed, and authorizes the app in the same call.
|
|
276
|
+
* Returns a {@link Step} when the address requires SSO, which {@link signIn} continues.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```ts
|
|
280
|
+
* const result = await Actions.verifyCode(auth, { accessKey, code, email })
|
|
281
|
+
* if ('step' in result) Actions.signIn(auth, { url: result.step.url })
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
export async function verifyCode(auth, options) {
|
|
285
|
+
const { accessKey, code, email } = options;
|
|
286
|
+
const privateKey = Secp256k1.randomPrivateKey();
|
|
287
|
+
const body = await json(await post(auth, '/api/auth/code/verify', {
|
|
288
|
+
json: {
|
|
289
|
+
authorize: request(auth, { accessKey, privateKey }),
|
|
290
|
+
code,
|
|
291
|
+
email,
|
|
292
|
+
},
|
|
293
|
+
}));
|
|
294
|
+
if ('step' in body)
|
|
295
|
+
return {
|
|
296
|
+
step: { ...body.step, url: new URL(body.step.url, auth.url).href },
|
|
297
|
+
};
|
|
298
|
+
return authorization(body.authorization, { privateKey });
|
|
299
|
+
}
|
|
300
|
+
/** Unpadded URL-safe Base64, as PKCE and `key_authorization` use. */
|
|
301
|
+
const base64url = { pad: false, url: true };
|
|
302
|
+
/** The redirect this page load returns from, so its code is redeemed once. */
|
|
303
|
+
let redirected;
|
|
304
|
+
/** Where a redirect keeps its state in `storage`. */
|
|
305
|
+
const storageKey = 'tempo-auth:redirect';
|
|
306
|
+
/** Builds an authorization from what Tempo Auth returned and the key the app generated. */
|
|
307
|
+
function authorization(body, options) {
|
|
308
|
+
const key = {
|
|
309
|
+
account: body.account,
|
|
310
|
+
keyAuthorization: body.keyAuthorization,
|
|
311
|
+
privateKey: options.privateKey,
|
|
312
|
+
};
|
|
313
|
+
const tokens_ = 'accessToken' in body.tokens ? body.tokens : tokens(body.tokens);
|
|
314
|
+
const claims = JSON.parse(Base64.toString(tokens_.idToken.split('.')[1]));
|
|
315
|
+
return {
|
|
316
|
+
account: Key.toAccount(key),
|
|
317
|
+
key,
|
|
318
|
+
tokens: tokens_,
|
|
319
|
+
user: { email: claims.email, id: claims.sub },
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/** Reads a JSON response, and throws a {@link RequestError} for a refused request. */
|
|
323
|
+
async function json(response) {
|
|
324
|
+
// Some refusals, such as the CSRF check's, answer plain text.
|
|
325
|
+
const body = (await response.json().catch(() => ({})));
|
|
326
|
+
if (!response.ok)
|
|
327
|
+
throw new RequestError({
|
|
328
|
+
message: body.error ?? 'Something went wrong.',
|
|
329
|
+
status: response.status,
|
|
330
|
+
});
|
|
331
|
+
return body;
|
|
332
|
+
}
|
|
333
|
+
/** Posts JSON to Tempo Auth's API with the session cookie. */
|
|
334
|
+
function post(auth, path, options) {
|
|
335
|
+
return (auth.fetch ?? fetch)(new URL(path, auth.url), {
|
|
336
|
+
...(options.json === undefined
|
|
337
|
+
? {}
|
|
338
|
+
: {
|
|
339
|
+
body: JSON.stringify(options.json),
|
|
340
|
+
headers: { 'content-type': 'application/json' },
|
|
341
|
+
}),
|
|
342
|
+
credentials: 'include',
|
|
343
|
+
method: 'POST',
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
/** Where a redirect keeps its state: the instance's `storage`, or the page's session storage. */
|
|
347
|
+
function storage(auth) {
|
|
348
|
+
return auth.storage ?? sessionStorage;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Calls Tempo Auth's OAuth token endpoint, adding the client and any secret. It forwards standard
|
|
352
|
+
* OAuth form posts to WorkOS.
|
|
353
|
+
*/
|
|
354
|
+
async function token(auth, body) {
|
|
355
|
+
return tokens(await json(await (auth.fetch ?? fetch)(new URL('/oauth2/token', auth.url), {
|
|
356
|
+
body: new URLSearchParams({
|
|
357
|
+
...body,
|
|
358
|
+
client_id: auth.clientId,
|
|
359
|
+
...(auth.clientSecret ? { client_secret: auth.clientSecret } : {}),
|
|
360
|
+
}),
|
|
361
|
+
method: 'POST',
|
|
362
|
+
})));
|
|
363
|
+
}
|
|
364
|
+
/** Reads a token response into `Tokens`. */
|
|
365
|
+
function tokens(response) {
|
|
366
|
+
return {
|
|
367
|
+
accessToken: response.access_token,
|
|
368
|
+
expiresAt: Date.now() + response.expires_in * 1000,
|
|
369
|
+
idToken: response.id_token,
|
|
370
|
+
refreshToken: response.refresh_token,
|
|
371
|
+
};
|
|
372
|
+
}
|
package/dist/Auth.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Tempo Auth instance, and the types its actions take and return. Create one with
|
|
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.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { Actions, Auth } from '@tempoxyz/auth'
|
|
9
|
+
* import { Addresses, Expiry, Scopes } from 'viem/tempo'
|
|
10
|
+
* import { tempoTestnet } from 'viem/tempo/chains'
|
|
11
|
+
*
|
|
12
|
+
* const auth = Auth.create({ clientId: 'client_…', url: 'https://auth.tempo.xyz' })
|
|
13
|
+
*
|
|
14
|
+
* const authorization = await Actions.authorize(auth, {
|
|
15
|
+
* accessKey: {
|
|
16
|
+
* chainId: tempoTestnet.id,
|
|
17
|
+
* expiry: Expiry.days(1),
|
|
18
|
+
* limits: [{ limit: 10_000_000n, token: Addresses.pathUsd }],
|
|
19
|
+
* scopes: [Scopes.tip20(Addresses.pathUsd).transfer()],
|
|
20
|
+
* },
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import type { Hex } from 'ox';
|
|
26
|
+
import type { KeyAuthorization } from 'ox/tempo';
|
|
27
|
+
import type * as Key from './Key.ts';
|
|
28
|
+
/** A Tempo Auth instance, which every action takes. */
|
|
29
|
+
export type Auth = {
|
|
30
|
+
/** The app's Connect client ID. */
|
|
31
|
+
clientId: string;
|
|
32
|
+
/** The app's client secret. Only a backend holds one. */
|
|
33
|
+
clientSecret?: string | undefined;
|
|
34
|
+
/** Calls Tempo Auth. Defaults to the global `fetch`. */
|
|
35
|
+
fetch?: typeof globalThis.fetch | undefined;
|
|
36
|
+
/** Where Tempo Auth redirects back to. Defaults to the page's origin in a browser. */
|
|
37
|
+
redirectUri?: string | undefined;
|
|
38
|
+
/** The scopes the app's tokens carry. */
|
|
39
|
+
scope: string;
|
|
40
|
+
/** Where a redirect sign-in keeps its state until Tempo Auth redirects back. */
|
|
41
|
+
storage?: Pick<Storage, 'getItem' | 'removeItem' | 'setItem'> | undefined;
|
|
42
|
+
/** Where Tempo Auth runs, such as `https://auth.tempo.xyz`. */
|
|
43
|
+
url: string;
|
|
44
|
+
};
|
|
45
|
+
/** What an app's access key may do, as viem's `accessKey.signAuthorization` takes it. */
|
|
46
|
+
export type AccessKey = Pick<KeyAuthorization.KeyAuthorization, 'limits' | 'scopes'> & {
|
|
47
|
+
/** The chain the key acts on, such as `tempoTestnet.id`. */
|
|
48
|
+
chainId: number;
|
|
49
|
+
/** Unix seconds. viem's `Expiry` builds one, such as `Expiry.days(1)`. */
|
|
50
|
+
expiry: number;
|
|
51
|
+
};
|
|
52
|
+
/** What every sign-in returns. */
|
|
53
|
+
export type Authorization = {
|
|
54
|
+
/** The app's access key, as a viem account acting for the person's Tempo Account. */
|
|
55
|
+
account: ReturnType<typeof Key.toAccount>;
|
|
56
|
+
/** The same key as JSON, for apps that store it and rebuild it with `Key.toAccount`. */
|
|
57
|
+
key: Key.Key;
|
|
58
|
+
/** The app's tokens for Tempo APIs. */
|
|
59
|
+
tokens: Tokens;
|
|
60
|
+
/** The signed-in person, from the ID token. */
|
|
61
|
+
user: User;
|
|
62
|
+
};
|
|
63
|
+
/** The app's tokens for Tempo APIs. */
|
|
64
|
+
export type Tokens = {
|
|
65
|
+
/** The Bearer token Tempo APIs accept. */
|
|
66
|
+
accessToken: string;
|
|
67
|
+
/** When the access token expires, in milliseconds. */
|
|
68
|
+
expiresAt: number;
|
|
69
|
+
/** The OIDC ID token, which names the person. A refresh may omit it. */
|
|
70
|
+
idToken?: string | undefined;
|
|
71
|
+
/** Exchanges for new tokens through `Actions.refresh`. It rotates on every refresh. */
|
|
72
|
+
refreshToken?: string | undefined;
|
|
73
|
+
};
|
|
74
|
+
/** The signed-in person. */
|
|
75
|
+
export type User = {
|
|
76
|
+
/** The person's verified email address. */
|
|
77
|
+
email: string;
|
|
78
|
+
/** The person's ID, the `sub` of every token. */
|
|
79
|
+
id: string;
|
|
80
|
+
};
|
|
81
|
+
/** An OAuth provider's short name, as `Actions.getProviders` lists them. */
|
|
82
|
+
export type Provider = 'apple' | 'bitbucket' | 'github' | 'gitlab' | 'google' | 'intuit' | 'linkedin' | 'microsoft' | 'salesforce' | 'slack' | 'xero';
|
|
83
|
+
/** A step a sign-in needs before it finishes, such as the SSO connection an address requires. */
|
|
84
|
+
export type Step = {
|
|
85
|
+
/** The kind of step. */
|
|
86
|
+
type: 'sso';
|
|
87
|
+
/** Where `Actions.signIn` sends the browser to take the step. */
|
|
88
|
+
url: string;
|
|
89
|
+
};
|
|
90
|
+
/** A sign-in redirect in progress: what the app keeps until Tempo Auth redirects back. */
|
|
91
|
+
export type Pending = {
|
|
92
|
+
/** The app's new access key, which the callback authorizes. */
|
|
93
|
+
privateKey: Hex.Hex;
|
|
94
|
+
/** Where Tempo Auth redirects back to, which the code exchange repeats. */
|
|
95
|
+
redirectUri: string;
|
|
96
|
+
/** The value the callback must echo, so a forged callback is refused. */
|
|
97
|
+
state: string;
|
|
98
|
+
/** The PKCE verifier that redeems the code. */
|
|
99
|
+
verifier: string;
|
|
100
|
+
};
|
|
101
|
+
/** A request Tempo Auth refused, with its status and message. */
|
|
102
|
+
export declare class RequestError extends Error {
|
|
103
|
+
readonly name = "Auth.RequestError";
|
|
104
|
+
/** The HTTP status Tempo Auth answered. */
|
|
105
|
+
readonly status: number;
|
|
106
|
+
constructor(options: {
|
|
107
|
+
message: string;
|
|
108
|
+
status: number;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Creates a Tempo Auth instance.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const auth = Auth.create({ clientId: 'client_…', url: 'https://auth.tempo.xyz' })
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare function create(options: create.Options): Auth;
|
|
120
|
+
export declare namespace create {
|
|
121
|
+
type Options = Omit<Auth, 'scope'> & {
|
|
122
|
+
/**
|
|
123
|
+
* Scopes the app's tokens carry beyond `openid` and `email`, which every instance includes.
|
|
124
|
+
* Defaults to `offline_access`, for a refresh token.
|
|
125
|
+
*/
|
|
126
|
+
scope?: string | undefined;
|
|
127
|
+
};
|
|
128
|
+
}
|
package/dist/Auth.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** A request Tempo Auth refused, with its status and message. */
|
|
2
|
+
export class RequestError extends Error {
|
|
3
|
+
name = 'Auth.RequestError';
|
|
4
|
+
/** The HTTP status Tempo Auth answered. */
|
|
5
|
+
status;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
super(options.message);
|
|
8
|
+
this.status = options.status;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Creates a Tempo Auth instance.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const auth = Auth.create({ clientId: 'client_…', url: 'https://auth.tempo.xyz' })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function create(options) {
|
|
20
|
+
// Every authorization reads the person from the ID token, which needs `openid` and `email`.
|
|
21
|
+
const scopes = (options.scope ?? 'offline_access').split(' ').filter(Boolean);
|
|
22
|
+
return {
|
|
23
|
+
...options,
|
|
24
|
+
scope: [...new Set(['openid', 'email', ...scopes])].join(' '),
|
|
25
|
+
};
|
|
26
|
+
}
|
package/dist/Key.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An app's access key on a person's Tempo Account, as the app stores it: the key, the account it
|
|
3
|
+
* acts for, and the authorization Tempo Auth signed for it.
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type { Address, Hex } from 'ox';
|
|
7
|
+
import { KeyAuthorization } from 'ox/tempo';
|
|
8
|
+
import { Account } from 'viem/tempo';
|
|
9
|
+
/** A stored access key. It is JSON, so servers and CLIs can keep it in a cookie or a file. */
|
|
10
|
+
export type Key = {
|
|
11
|
+
/** The Tempo Account the key acts for. */
|
|
12
|
+
account: Address.Address;
|
|
13
|
+
/** The signed authorization, in the node's RPC format. */
|
|
14
|
+
keyAuthorization: KeyAuthorization.Rpc;
|
|
15
|
+
/** The access key's private key. Keep it as secret as a session. */
|
|
16
|
+
privateKey: Hex.Hex;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Whether the key's authorization has expired, so a new sign-in must authorize a new key.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* if (Key.expired(saved.key)) await signInAgain()
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function expired(key: Key): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Returns the key as a viem access key account acting for its Tempo Account. viem attaches the
|
|
29
|
+
* authorization to transactions until the chain registers the key.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { Key } from '@tempoxyz/auth'
|
|
34
|
+
*
|
|
35
|
+
* const account = Key.toAccount(key)
|
|
36
|
+
* await client.token.transferSync({ account, amount, to, token })
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function toAccount(key: Key): Account.AccessKeyAccount;
|
package/dist/Key.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { KeyAuthorization } from 'ox/tempo';
|
|
2
|
+
import { Account, KeyAuthorizationManager } from 'viem/tempo';
|
|
3
|
+
/**
|
|
4
|
+
* Whether the key's authorization has expired, so a new sign-in must authorize a new key.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* if (Key.expired(saved.key)) await signInAgain()
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export function expired(key) {
|
|
12
|
+
const { expiry } = KeyAuthorization.fromRpc(key.keyAuthorization);
|
|
13
|
+
return Boolean(expiry) && expiry * 1000 <= Date.now();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns the key as a viem access key account acting for its Tempo Account. viem attaches the
|
|
17
|
+
* authorization to transactions until the chain registers the key.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { Key } from '@tempoxyz/auth'
|
|
22
|
+
*
|
|
23
|
+
* const account = Key.toAccount(key)
|
|
24
|
+
* await client.token.transferSync({ account, amount, to, token })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function toAccount(key) {
|
|
28
|
+
const keyAuthorization = KeyAuthorization.fromRpc(key.keyAuthorization);
|
|
29
|
+
const keyAuthorizationManager = KeyAuthorizationManager.memory();
|
|
30
|
+
keyAuthorizationManager.set({
|
|
31
|
+
accessKey: keyAuthorization.address,
|
|
32
|
+
address: key.account,
|
|
33
|
+
chainId: Number(keyAuthorization.chainId),
|
|
34
|
+
}, keyAuthorization);
|
|
35
|
+
return Account.fromSecp256k1(key.privateKey, {
|
|
36
|
+
access: key.account,
|
|
37
|
+
keyAuthorizationManager,
|
|
38
|
+
});
|
|
39
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the authorization and key authorization requests the actions send, in Tempo Auth's wire
|
|
3
|
+
* format. The root type check holds them to Tempo Auth's routes.
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { Hex } from 'ox';
|
|
7
|
+
import { Account } from 'viem/tempo';
|
|
8
|
+
import type { AccessKey, Auth } from '../Auth.ts';
|
|
9
|
+
/** The authorization request and key authorization request, in Tempo Auth's wire format. */
|
|
10
|
+
export declare function request(auth: Auth, options: {
|
|
11
|
+
accessKey: AccessKey;
|
|
12
|
+
privateKey: Hex.Hex;
|
|
13
|
+
}): {
|
|
14
|
+
client_id: string;
|
|
15
|
+
keyAuthorization: {
|
|
16
|
+
address: `0x${string}`;
|
|
17
|
+
chainId: `0x${string}`;
|
|
18
|
+
expiry: number;
|
|
19
|
+
keyType: 'secp256k1';
|
|
20
|
+
limits: {
|
|
21
|
+
limit: `0x${string}`;
|
|
22
|
+
period?: number;
|
|
23
|
+
token: `0x${string}`;
|
|
24
|
+
}[] | undefined;
|
|
25
|
+
scopes: readonly Account.z_KeyAuthorization.Scope<`0x${string}`>[] | undefined;
|
|
26
|
+
};
|
|
27
|
+
redirect_uri: string;
|
|
28
|
+
scope: string;
|
|
29
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the authorization and key authorization requests the actions send, in Tempo Auth's wire
|
|
3
|
+
* format. The root type check holds them to Tempo Auth's routes.
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { Hex } from 'ox';
|
|
7
|
+
import { Account } from 'viem/tempo';
|
|
8
|
+
/** The authorization request and key authorization request, in Tempo Auth's wire format. */
|
|
9
|
+
export function request(auth, options) {
|
|
10
|
+
const { accessKey, privateKey } = options;
|
|
11
|
+
return {
|
|
12
|
+
client_id: auth.clientId,
|
|
13
|
+
keyAuthorization: {
|
|
14
|
+
address: Account.fromSecp256k1(privateKey).address,
|
|
15
|
+
chainId: Hex.fromNumber(accessKey.chainId),
|
|
16
|
+
expiry: accessKey.expiry,
|
|
17
|
+
keyType: 'secp256k1',
|
|
18
|
+
limits: accessKey.limits?.map(({ limit, period, token }) => ({
|
|
19
|
+
limit: Hex.fromNumber(limit),
|
|
20
|
+
...(period ? { period } : {}),
|
|
21
|
+
token,
|
|
22
|
+
})),
|
|
23
|
+
scopes: accessKey.scopes,
|
|
24
|
+
},
|
|
25
|
+
redirect_uri: auth.redirectUri ?? `${globalThis.location?.origin ?? auth.url}/`,
|
|
26
|
+
scope: auth.scope,
|
|
27
|
+
};
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
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.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"ox": "0.14.56"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"viem": ">=2.57.5-0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "7.0.2",
|
|
26
|
+
"viem": "https://pkg.pr.new/viem@0872b53"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.build.json",
|
|
30
|
+
"check:types": "tsc"
|
|
31
|
+
}
|
|
32
|
+
}
|