@happyview/oauth-client 1.0.0 → 1.0.1
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/LICENSE.md +21 -0
- package/README.md +111 -0
- package/dist/index.cjs +126 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -42
- package/dist/index.d.ts +31 -42
- package/dist/index.js +125 -95
- package/dist/index.js.map +1 -1
- package/package.json +11 -3
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Lexicon Community
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @happyview/oauth-client
|
|
2
|
+
|
|
3
|
+
Core OAuth client for authenticating with a [HappyView](https://github.com/gamesgamesgamesgamesgames/happyview) instance.
|
|
4
|
+
|
|
5
|
+
This is a platform-agnostic package. If you're building a browser app, use [`@happyview/oauth-client-browser`](https://www.npmjs.com/package/@happyview/oauth-client-browser) instead. It wraps this package with Web Crypto, localStorage, and a complete OAuth redirect flow.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @happyview/oauth-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
`HappyViewOAuthClient` manages DPoP key provisioning, session registration, and session restoration lifecycle. You provide a `CryptoAdapter` and optional `StorageAdapter` for your platform.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { HappyViewOAuthClient } from "@happyview/oauth-client";
|
|
19
|
+
|
|
20
|
+
const client = new HappyViewOAuthClient({
|
|
21
|
+
instanceUrl: "https://happyview.example.com",
|
|
22
|
+
clientKey: "hvc_your_client_key",
|
|
23
|
+
clientSecret: "hvs_your_secret", // optional, for confidential clients (server-to-server)
|
|
24
|
+
crypto: myCryptoAdapter,
|
|
25
|
+
storage: myStorageAdapter, // optional, defaults to in-memory
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### DPoP Key Provisioning
|
|
30
|
+
|
|
31
|
+
Request a DPoP keypair from the HappyView instance:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const { provisionId, dpopKey, pkceVerifier } = await client.provisionDpopKey();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Session Registration
|
|
38
|
+
|
|
39
|
+
After completing OAuth authorization with the user's PDS, register the session with HappyView:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const session = await client.registerSession({
|
|
43
|
+
provisionId,
|
|
44
|
+
pkceVerifier,
|
|
45
|
+
did: "did:plc:abc123",
|
|
46
|
+
accessToken: tokens.access_token,
|
|
47
|
+
refreshToken: tokens.refresh_token,
|
|
48
|
+
scopes: "atproto",
|
|
49
|
+
pdsUrl: "https://pds.example.com",
|
|
50
|
+
issuer: tokens.iss,
|
|
51
|
+
dpopKey,
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Making Authenticated Requests
|
|
56
|
+
|
|
57
|
+
The returned `HappyViewSession` provides a `fetchHandler` that automatically attaches DPoP proof headers:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const response = await session.fetchHandler("/xrpc/com.example.getStuff", {
|
|
61
|
+
method: "GET",
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Session Restoration
|
|
66
|
+
|
|
67
|
+
Restore a previously stored session:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Restore the last active session
|
|
71
|
+
const session = await client.restore();
|
|
72
|
+
|
|
73
|
+
// Or restore a specific user's session
|
|
74
|
+
const session = await client.restoreSession("did:plc:abc123");
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Logout
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
await client.deleteSession("did:plc:abc123");
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Adapters
|
|
84
|
+
|
|
85
|
+
### CryptoAdapter
|
|
86
|
+
|
|
87
|
+
Implement this interface for your platform's cryptographic primitives:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
interface CryptoAdapter {
|
|
91
|
+
generatePkceVerifier(): Promise<string>;
|
|
92
|
+
computePkceChallenge(verifier: string): Promise<string>;
|
|
93
|
+
signEs256(privateKey: JsonWebKey, payload: Uint8Array): Promise<Uint8Array>;
|
|
94
|
+
sha256(data: Uint8Array): Promise<Uint8Array>;
|
|
95
|
+
getRandomValues(length: number): Uint8Array;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### StorageAdapter
|
|
100
|
+
|
|
101
|
+
Implement this interface to persist sessions across restarts:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
interface StorageAdapter {
|
|
105
|
+
get(key: string): Promise<string | null>;
|
|
106
|
+
set(key: string, value: string): Promise<void>;
|
|
107
|
+
delete(key: string): Promise<void>;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
If no `StorageAdapter` is provided, sessions are stored in memory and will not survive page reloads or process restarts.
|
package/dist/index.cjs
CHANGED
|
@@ -29,8 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
MemoryStorage: () => MemoryStorage,
|
|
30
30
|
ResolutionError: () => ResolutionError,
|
|
31
31
|
TokenExchangeError: () => TokenExchangeError,
|
|
32
|
-
|
|
33
|
-
generateDpopProof: () => generateDpopProof
|
|
32
|
+
importJwk: () => importJwk
|
|
34
33
|
});
|
|
35
34
|
module.exports = __toCommonJS(index_exports);
|
|
36
35
|
|
|
@@ -82,63 +81,46 @@ var ResolutionError = class extends HappyViewError {
|
|
|
82
81
|
}
|
|
83
82
|
};
|
|
84
83
|
|
|
85
|
-
// src/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const { privateKey, method, url, accessToken, nonce } = params;
|
|
95
|
-
const publicJwk = {
|
|
96
|
-
kty: privateKey.kty,
|
|
97
|
-
crv: privateKey.crv,
|
|
98
|
-
x: privateKey.x,
|
|
99
|
-
y: privateKey.y
|
|
100
|
-
};
|
|
101
|
-
const header = {
|
|
102
|
-
alg: "ES256",
|
|
103
|
-
typ: "dpop+jwt",
|
|
104
|
-
jwk: publicJwk
|
|
105
|
-
};
|
|
106
|
-
const jtiBytes = crypto.getRandomValues(16);
|
|
107
|
-
const jti = Array.from(jtiBytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
108
|
-
const payload = {
|
|
109
|
-
htm: method,
|
|
110
|
-
htu: url,
|
|
111
|
-
iat: Math.floor(Date.now() / 1e3),
|
|
112
|
-
jti
|
|
113
|
-
};
|
|
114
|
-
if (accessToken) {
|
|
115
|
-
const tokenBytes = new TextEncoder().encode(accessToken);
|
|
116
|
-
const hashBuf = await crypto.sha256(tokenBytes);
|
|
117
|
-
payload.ath = base64urlEncode(hashBuf);
|
|
118
|
-
}
|
|
119
|
-
if (nonce !== void 0) {
|
|
120
|
-
payload.nonce = nonce;
|
|
121
|
-
}
|
|
122
|
-
const headerB64 = base64urlEncode(
|
|
123
|
-
new TextEncoder().encode(JSON.stringify(header))
|
|
84
|
+
// src/import-jwk.ts
|
|
85
|
+
var import_jwk_webcrypto = require("@atproto/jwk-webcrypto");
|
|
86
|
+
async function importJwk(jwk) {
|
|
87
|
+
const privateKey = await crypto.subtle.importKey(
|
|
88
|
+
"jwk",
|
|
89
|
+
jwk,
|
|
90
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
91
|
+
true,
|
|
92
|
+
["sign"]
|
|
124
93
|
);
|
|
125
|
-
const
|
|
126
|
-
|
|
94
|
+
const { d: _, ...publicJwkFields } = jwk;
|
|
95
|
+
const publicKey = await crypto.subtle.importKey(
|
|
96
|
+
"jwk",
|
|
97
|
+
publicJwkFields,
|
|
98
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
99
|
+
true,
|
|
100
|
+
["verify"]
|
|
127
101
|
);
|
|
128
|
-
|
|
129
|
-
const signature = await crypto.signEs256(privateKey, message);
|
|
130
|
-
const sigB64 = base64urlEncode(signature);
|
|
131
|
-
return `${headerB64}.${payloadB64}.${sigB64}`;
|
|
102
|
+
return import_jwk_webcrypto.WebcryptoKey.fromKeypair({ privateKey, publicKey });
|
|
132
103
|
}
|
|
133
104
|
|
|
134
105
|
// src/session.ts
|
|
106
|
+
function randomHex(byteLength) {
|
|
107
|
+
const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
|
|
108
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
109
|
+
}
|
|
110
|
+
function base64url(buffer) {
|
|
111
|
+
const bytes = new Uint8Array(buffer);
|
|
112
|
+
let binary = "";
|
|
113
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
114
|
+
binary += String.fromCharCode(bytes[i]);
|
|
115
|
+
}
|
|
116
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
117
|
+
}
|
|
135
118
|
var HappyViewSession = class {
|
|
136
119
|
did;
|
|
137
120
|
dpopKey;
|
|
138
121
|
accessToken;
|
|
139
122
|
clientKey;
|
|
140
123
|
instanceUrl;
|
|
141
|
-
crypto;
|
|
142
124
|
_fetch;
|
|
143
125
|
dpopNonce;
|
|
144
126
|
constructor(options) {
|
|
@@ -147,29 +129,61 @@ var HappyViewSession = class {
|
|
|
147
129
|
this.accessToken = options.accessToken;
|
|
148
130
|
this.clientKey = options.clientKey;
|
|
149
131
|
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
150
|
-
this.
|
|
151
|
-
this._fetch = options.fetch ?? globalThis.fetch;
|
|
132
|
+
this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
152
133
|
}
|
|
153
134
|
async fetchHandler(url, init) {
|
|
154
135
|
const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.instanceUrl}${url}`;
|
|
155
136
|
const method = (init.method ?? "GET").toUpperCase();
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
137
|
+
const htu = fullUrl.split("?")[0];
|
|
138
|
+
const sendWithProof = async () => {
|
|
139
|
+
const tokenHash = await crypto.subtle.digest(
|
|
140
|
+
"SHA-256",
|
|
141
|
+
new TextEncoder().encode(this.accessToken)
|
|
142
|
+
);
|
|
143
|
+
const ath = base64url(tokenHash);
|
|
144
|
+
const proof = await this.dpopKey.createJwt(
|
|
145
|
+
{
|
|
146
|
+
alg: "ES256",
|
|
147
|
+
typ: "dpop+jwt",
|
|
148
|
+
jwk: this.dpopKey.publicJwk
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
htm: method,
|
|
152
|
+
htu,
|
|
153
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
154
|
+
jti: randomHex(16),
|
|
155
|
+
ath,
|
|
156
|
+
nonce: this.dpopNonce
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
const headers = new Headers(init.headers);
|
|
160
|
+
headers.set("authorization", `DPoP ${this.accessToken}`);
|
|
161
|
+
headers.set("dpop", proof);
|
|
162
|
+
headers.set("x-client-key", this.clientKey);
|
|
163
|
+
return this._fetch(fullUrl, { ...init, headers });
|
|
164
|
+
};
|
|
165
|
+
let response;
|
|
166
|
+
try {
|
|
167
|
+
response = await sendWithProof();
|
|
168
|
+
} catch (e) {
|
|
169
|
+
const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;
|
|
170
|
+
throw new Error(`fetchHandler failed: ${e.message}
|
|
171
|
+
|
|
172
|
+
DEBUG: ${info}`);
|
|
173
|
+
}
|
|
171
174
|
const nonce = response.headers.get("dpop-nonce");
|
|
172
|
-
if (nonce) {
|
|
175
|
+
if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {
|
|
176
|
+
this.dpopNonce = nonce;
|
|
177
|
+
try {
|
|
178
|
+
response = await sendWithProof();
|
|
179
|
+
} catch (e) {
|
|
180
|
+
throw new Error(`fetchHandler retry failed: ${e.message}`);
|
|
181
|
+
}
|
|
182
|
+
const retryNonce = response.headers.get("dpop-nonce");
|
|
183
|
+
if (retryNonce) {
|
|
184
|
+
this.dpopNonce = retryNonce;
|
|
185
|
+
}
|
|
186
|
+
} else if (nonce) {
|
|
173
187
|
this.dpopNonce = nonce;
|
|
174
188
|
}
|
|
175
189
|
return response;
|
|
@@ -196,7 +210,6 @@ var LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
|
196
210
|
var HappyViewOAuthClient = class {
|
|
197
211
|
instanceUrl;
|
|
198
212
|
clientKey;
|
|
199
|
-
crypto;
|
|
200
213
|
storage;
|
|
201
214
|
clientSecret;
|
|
202
215
|
_fetch;
|
|
@@ -204,9 +217,8 @@ var HappyViewOAuthClient = class {
|
|
|
204
217
|
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
205
218
|
this.clientKey = options.clientKey;
|
|
206
219
|
this.clientSecret = options.clientSecret;
|
|
207
|
-
this.crypto = options.crypto;
|
|
208
220
|
this.storage = options.storage ?? new MemoryStorage();
|
|
209
|
-
this._fetch = options.fetch ??
|
|
221
|
+
this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
210
222
|
}
|
|
211
223
|
get isConfidential() {
|
|
212
224
|
return this.clientSecret !== void 0;
|
|
@@ -222,8 +234,8 @@ var HappyViewOAuthClient = class {
|
|
|
222
234
|
const body = {};
|
|
223
235
|
let pkceVerifier;
|
|
224
236
|
if (!this.isConfidential) {
|
|
225
|
-
pkceVerifier =
|
|
226
|
-
const challenge = await
|
|
237
|
+
pkceVerifier = generatePkceVerifier();
|
|
238
|
+
const challenge = await computePkceChallenge(pkceVerifier);
|
|
227
239
|
body.pkce_challenge = challenge;
|
|
228
240
|
}
|
|
229
241
|
const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {
|
|
@@ -240,9 +252,11 @@ var HappyViewOAuthClient = class {
|
|
|
240
252
|
);
|
|
241
253
|
}
|
|
242
254
|
const data = await resp.json();
|
|
255
|
+
const dpopKey = await importJwk(data.dpop_key);
|
|
243
256
|
return {
|
|
244
257
|
provisionId: data.provision_id,
|
|
245
|
-
dpopKey
|
|
258
|
+
dpopKey,
|
|
259
|
+
rawJwk: data.dpop_key,
|
|
246
260
|
pkceVerifier
|
|
247
261
|
};
|
|
248
262
|
}
|
|
@@ -280,6 +294,7 @@ var HappyViewOAuthClient = class {
|
|
|
280
294
|
);
|
|
281
295
|
}
|
|
282
296
|
const data = await resp.json();
|
|
297
|
+
const dpopKey = await importJwk(params.dpopKey);
|
|
283
298
|
const storedSession = {
|
|
284
299
|
did: data.did,
|
|
285
300
|
dpopKey: params.dpopKey,
|
|
@@ -294,34 +309,28 @@ var HappyViewOAuthClient = class {
|
|
|
294
309
|
await this.storage.set(LAST_ACTIVE_KEY, data.did);
|
|
295
310
|
return new HappyViewSession({
|
|
296
311
|
did: data.did,
|
|
297
|
-
dpopKey
|
|
312
|
+
dpopKey,
|
|
298
313
|
accessToken: params.accessToken,
|
|
299
314
|
clientKey: this.clientKey,
|
|
300
315
|
instanceUrl: this.instanceUrl,
|
|
301
|
-
|
|
316
|
+
fetch: this._fetch
|
|
302
317
|
});
|
|
303
318
|
}
|
|
304
319
|
async deleteSession(did) {
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
const resp = await this._fetch(
|
|
312
|
-
`${this.instanceUrl}/oauth/sessions/${did}`,
|
|
313
|
-
{
|
|
314
|
-
method: "DELETE",
|
|
315
|
-
headers
|
|
316
|
-
}
|
|
317
|
-
);
|
|
318
|
-
if (!resp.ok && resp.status !== 404) {
|
|
319
|
-
const body = await resp.json().catch(() => ({}));
|
|
320
|
-
throw new ApiError(
|
|
321
|
-
`Failed to delete session: ${resp.status} ${body.message ?? resp.statusText}`,
|
|
322
|
-
resp.status,
|
|
323
|
-
body
|
|
320
|
+
const session = await this.restoreSession(did);
|
|
321
|
+
if (session) {
|
|
322
|
+
const resp = await session.fetchHandler(
|
|
323
|
+
`${this.instanceUrl}/oauth/sessions/${did}`,
|
|
324
|
+
{ method: "DELETE" }
|
|
324
325
|
);
|
|
326
|
+
if (!resp.ok && resp.status !== 404) {
|
|
327
|
+
const body = await resp.json().catch(() => ({}));
|
|
328
|
+
throw new ApiError(
|
|
329
|
+
`Failed to delete session: ${resp.status} ${body.message ?? resp.statusText}`,
|
|
330
|
+
resp.status,
|
|
331
|
+
body
|
|
332
|
+
);
|
|
333
|
+
}
|
|
325
334
|
}
|
|
326
335
|
await this.storage.delete(`${STORAGE_PREFIX}${did}`);
|
|
327
336
|
const lastActive = await this.storage.get(LAST_ACTIVE_KEY);
|
|
@@ -333,13 +342,14 @@ var HappyViewOAuthClient = class {
|
|
|
333
342
|
const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);
|
|
334
343
|
if (!stored) return null;
|
|
335
344
|
const data = JSON.parse(stored);
|
|
345
|
+
const dpopKey = await importJwk(data.dpopKey);
|
|
336
346
|
return new HappyViewSession({
|
|
337
347
|
did: data.did,
|
|
338
|
-
dpopKey
|
|
348
|
+
dpopKey,
|
|
339
349
|
accessToken: data.accessToken,
|
|
340
350
|
clientKey: data.clientKey,
|
|
341
351
|
instanceUrl: data.instanceUrl,
|
|
342
|
-
|
|
352
|
+
fetch: this._fetch
|
|
343
353
|
});
|
|
344
354
|
}
|
|
345
355
|
async restore() {
|
|
@@ -348,6 +358,26 @@ var HappyViewOAuthClient = class {
|
|
|
348
358
|
return this.restoreSession(did);
|
|
349
359
|
}
|
|
350
360
|
};
|
|
361
|
+
function generatePkceVerifier() {
|
|
362
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
363
|
+
let binary = "";
|
|
364
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
365
|
+
binary += String.fromCharCode(bytes[i]);
|
|
366
|
+
}
|
|
367
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
368
|
+
}
|
|
369
|
+
async function computePkceChallenge(verifier) {
|
|
370
|
+
const hash = await crypto.subtle.digest(
|
|
371
|
+
"SHA-256",
|
|
372
|
+
new TextEncoder().encode(verifier)
|
|
373
|
+
);
|
|
374
|
+
const bytes = new Uint8Array(hash);
|
|
375
|
+
let binary = "";
|
|
376
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
377
|
+
binary += String.fromCharCode(bytes[i]);
|
|
378
|
+
}
|
|
379
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
380
|
+
}
|
|
351
381
|
// Annotate the CommonJS export names for ESM import in node:
|
|
352
382
|
0 && (module.exports = {
|
|
353
383
|
ApiError,
|
|
@@ -359,7 +389,6 @@ var HappyViewOAuthClient = class {
|
|
|
359
389
|
MemoryStorage,
|
|
360
390
|
ResolutionError,
|
|
361
391
|
TokenExchangeError,
|
|
362
|
-
|
|
363
|
-
generateDpopProof
|
|
392
|
+
importJwk
|
|
364
393
|
});
|
|
365
394
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/dpop-proof.ts","../src/session.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export { HappyViewOAuthClient } from \"./client\";\nexport { base64urlEncode, generateDpopProof } from \"./dpop-proof\";\nexport type { DpopProofParams } from \"./dpop-proof\";\nexport {\n ApiError,\n AuthenticationError,\n HappyViewError,\n InvalidStateError,\n ResolutionError,\n TokenExchangeError,\n} from \"./errors\";\nexport { HappyViewSession } from \"./session\";\nexport type { HappyViewSessionOptions } from \"./session\";\nexport { MemoryStorage } from \"./storage\";\nexport type {\n CryptoAdapter,\n DpopProvision,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n","export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n","import type { CryptoAdapter } from \"./types\";\n\nexport function base64urlEncode(data: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n binary += String.fromCharCode(data[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport interface DpopProofParams {\n privateKey: JsonWebKey;\n method: string;\n url: string;\n accessToken?: string;\n nonce?: string;\n}\n\nexport async function generateDpopProof(\n crypto: CryptoAdapter,\n params: DpopProofParams,\n): Promise<string> {\n const { privateKey, method, url, accessToken, nonce } = params;\n\n const publicJwk = {\n kty: privateKey.kty,\n crv: privateKey.crv,\n x: privateKey.x,\n y: privateKey.y,\n };\n\n const header = {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: publicJwk,\n };\n\n // Generate jti\n const jtiBytes = crypto.getRandomValues(16);\n const jti = Array.from(jtiBytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n\n const payload: Record<string, unknown> = {\n htm: method,\n htu: url,\n iat: Math.floor(Date.now() / 1000),\n jti,\n };\n\n // Compute ath: base64url(SHA-256(access_token)) — only when token is present\n if (accessToken) {\n const tokenBytes = new TextEncoder().encode(accessToken);\n const hashBuf = await crypto.sha256(tokenBytes);\n payload.ath = base64urlEncode(hashBuf);\n }\n\n if (nonce !== undefined) {\n payload.nonce = nonce;\n }\n\n const headerB64 = base64urlEncode(\n new TextEncoder().encode(JSON.stringify(header)),\n );\n const payloadB64 = base64urlEncode(\n new TextEncoder().encode(JSON.stringify(payload)),\n );\n\n const message = new TextEncoder().encode(`${headerB64}.${payloadB64}`);\n const signature = await crypto.signEs256(privateKey, message);\n const sigB64 = base64urlEncode(signature);\n\n return `${headerB64}.${payloadB64}.${sigB64}`;\n}\n","import { generateDpopProof } from \"./dpop-proof\";\nimport type { CryptoAdapter } from \"./types\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: JsonWebKey;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n crypto: CryptoAdapter;\n fetch?: typeof globalThis.fetch;\n}\n\nexport class HappyViewSession {\n readonly did: string;\n\n private readonly dpopKey: JsonWebKey;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly crypto: CryptoAdapter;\n private readonly _fetch: typeof globalThis.fetch;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.crypto = options.crypto;\n this._fetch = options.fetch ?? globalThis.fetch;\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url) ? url : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n\n const proof = await generateDpopProof(this.crypto, {\n privateKey: this.dpopKey,\n method,\n url: fullUrl,\n accessToken: this.accessToken,\n nonce: this.dpopNonce,\n });\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n const response = await this._fetch(fullUrl, {\n ...init,\n headers,\n });\n\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import { ApiError } from \"./errors\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n CryptoAdapter,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\nconst STORAGE_PREFIX = \"happyview:session:\";\nconst LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport class HappyViewOAuthClient {\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly crypto: CryptoAdapter;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n private readonly _fetch: typeof globalThis.fetch;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.crypto = options.crypto;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch = options.fetch ?? globalThis.fetch;\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = await this.crypto.generatePkceVerifier();\n const challenge = await this.crypto.computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n return {\n provisionId: data.provision_id,\n dpopKey: data.dpop_key,\n pkceVerifier,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n return new HappyViewSession({\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n crypto: this.crypto,\n });\n }\n\n async deleteSession(did: string): Promise<void> {\n const headers: Record<string, string> = {\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const resp = await this._fetch(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n {\n method: \"DELETE\",\n headers,\n },\n );\n\n if (!resp.ok && resp.status !== 404) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n return new HappyViewSession({\n did: data.did,\n dpopKey: data.dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n crypto: this.crypto,\n });\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n"],"mappings":"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","names":["body"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/import-jwk.ts","../src/session.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export { HappyViewOAuthClient } from \"./client\";\nexport { importJwk } from \"./import-jwk\";\nexport {\n ApiError,\n AuthenticationError,\n HappyViewError,\n InvalidStateError,\n ResolutionError,\n TokenExchangeError,\n} from \"./errors\";\nexport { HappyViewSession } from \"./session\";\nexport type { HappyViewSessionOptions } from \"./session\";\nexport { MemoryStorage } from \"./storage\";\nexport type {\n DpopProvision,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n","export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n","import { WebcryptoKey } from \"@atproto/jwk-webcrypto\";\n\n/**\n * Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)\n * into a WebcryptoKey that can sign JWTs.\n */\nexport async function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey> {\n const privateKey = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"],\n );\n\n // Derive public key by stripping the private component\n const { d: _, ...publicJwkFields } = jwk;\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n publicJwkFields,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"],\n );\n\n return WebcryptoKey.fromKeypair({ privateKey, publicKey });\n}\n","import type { Key } from \"@atproto/jwk\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: Key;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n fetch?: typeof globalThis.fetch;\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(byteLength));\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class HappyViewSession {\n readonly did: string;\n\n private readonly dpopKey: Key;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly _fetch: typeof globalThis.fetch;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url)\n ? url\n : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const htu = fullUrl.split(\"?\")[0];\n\n const sendWithProof = async (): Promise<Response> => {\n const tokenHash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(this.accessToken),\n );\n const ath = base64url(tokenHash);\n\n const proof = await this.dpopKey.createJwt(\n {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: this.dpopKey.publicJwk!,\n },\n {\n htm: method,\n htu,\n iat: Math.floor(Date.now() / 1000),\n jti: randomHex(16),\n ath,\n nonce: this.dpopNonce,\n },\n );\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n return this._fetch(fullUrl, { ...init, headers });\n };\n\n let response: Response;\n try {\n response = await sendWithProof();\n } catch (e) {\n const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;\n throw new Error(`fetchHandler failed: ${(e as Error).message}\\n\\nDEBUG: ${info}`);\n }\n\n // Retry once if the server requires a DPoP nonce we didn't have\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {\n this.dpopNonce = nonce;\n try {\n response = await sendWithProof();\n } catch (e) {\n throw new Error(`fetchHandler retry failed: ${(e as Error).message}`);\n }\n const retryNonce = response.headers.get(\"dpop-nonce\");\n if (retryNonce) {\n this.dpopNonce = retryNonce;\n }\n } else if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import type { Key } from \"@atproto/jwk\";\nimport { ApiError } from \"./errors\";\nimport { importJwk } from \"./import-jwk\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\nconst STORAGE_PREFIX = \"happyview:session:\";\nconst LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport class HappyViewOAuthClient {\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n protected readonly _fetch: typeof globalThis.fetch;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: Key;\n rawJwk: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = generatePkceVerifier();\n const challenge = await computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n const dpopKey = await importJwk(data.dpop_key);\n return {\n provisionId: data.provision_id,\n dpopKey,\n rawJwk: data.dpop_key,\n pkceVerifier,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n const dpopKey = await importJwk(params.dpopKey);\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n fetch: this._fetch,\n });\n }\n\n async deleteSession(did: string): Promise<void> {\n const session = await this.restoreSession(did);\n if (session) {\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"DELETE\" },\n );\n\n if (!resp.ok && resp.status !== 404) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n }\n\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n const dpopKey = await importJwk(data.dpopKey);\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n fetch: this._fetch,\n });\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n\n// PKCE helpers — inline since they're just a few lines of Web Crypto\nfunction generatePkceVerifier(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nasync function computePkceChallenge(verifier: string): Promise<string> {\n const hash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n const bytes = new Uint8Array(hash);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n"],"mappings":"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","names":["body"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { Key } from '@atproto/jwk';
|
|
2
|
+
import { WebcryptoKey } from '@atproto/jwk-webcrypto';
|
|
3
|
+
|
|
4
|
+
interface HappyViewSessionOptions {
|
|
5
|
+
did: string;
|
|
6
|
+
dpopKey: Key;
|
|
7
|
+
accessToken: string;
|
|
8
|
+
clientKey: string;
|
|
9
|
+
instanceUrl: string;
|
|
10
|
+
fetch?: typeof globalThis.fetch;
|
|
7
11
|
}
|
|
12
|
+
declare class HappyViewSession {
|
|
13
|
+
readonly did: string;
|
|
14
|
+
private readonly dpopKey;
|
|
15
|
+
private readonly accessToken;
|
|
16
|
+
private readonly clientKey;
|
|
17
|
+
private readonly instanceUrl;
|
|
18
|
+
private readonly _fetch;
|
|
19
|
+
private dpopNonce;
|
|
20
|
+
constructor(options: HappyViewSessionOptions);
|
|
21
|
+
fetchHandler(url: string, init: RequestInit): Promise<Response>;
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
interface StorageAdapter {
|
|
9
25
|
get(key: string): Promise<string | null>;
|
|
10
26
|
set(key: string, value: string): Promise<void>;
|
|
@@ -14,7 +30,6 @@ interface HappyViewOAuthClientOptions {
|
|
|
14
30
|
instanceUrl: string;
|
|
15
31
|
clientKey: string;
|
|
16
32
|
clientSecret?: string;
|
|
17
|
-
crypto: CryptoAdapter;
|
|
18
33
|
storage?: StorageAdapter;
|
|
19
34
|
}
|
|
20
35
|
interface DpopProvision {
|
|
@@ -54,42 +69,20 @@ interface RegisterSessionResponse {
|
|
|
54
69
|
did: string;
|
|
55
70
|
}
|
|
56
71
|
|
|
57
|
-
interface HappyViewSessionOptions {
|
|
58
|
-
did: string;
|
|
59
|
-
dpopKey: JsonWebKey;
|
|
60
|
-
accessToken: string;
|
|
61
|
-
clientKey: string;
|
|
62
|
-
instanceUrl: string;
|
|
63
|
-
crypto: CryptoAdapter;
|
|
64
|
-
fetch?: typeof globalThis.fetch;
|
|
65
|
-
}
|
|
66
|
-
declare class HappyViewSession {
|
|
67
|
-
readonly did: string;
|
|
68
|
-
private readonly dpopKey;
|
|
69
|
-
private readonly accessToken;
|
|
70
|
-
private readonly clientKey;
|
|
71
|
-
private readonly instanceUrl;
|
|
72
|
-
private readonly crypto;
|
|
73
|
-
private readonly _fetch;
|
|
74
|
-
private dpopNonce;
|
|
75
|
-
constructor(options: HappyViewSessionOptions);
|
|
76
|
-
fetchHandler(url: string, init: RequestInit): Promise<Response>;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
72
|
declare class HappyViewOAuthClient {
|
|
80
73
|
protected readonly instanceUrl: string;
|
|
81
74
|
protected readonly clientKey: string;
|
|
82
|
-
protected readonly crypto: CryptoAdapter;
|
|
83
75
|
protected readonly storage: StorageAdapter;
|
|
84
76
|
private readonly clientSecret;
|
|
85
|
-
|
|
77
|
+
protected readonly _fetch: typeof globalThis.fetch;
|
|
86
78
|
constructor(options: HappyViewOAuthClientOptions & {
|
|
87
79
|
fetch?: typeof globalThis.fetch;
|
|
88
80
|
});
|
|
89
81
|
get isConfidential(): boolean;
|
|
90
82
|
provisionDpopKey(): Promise<{
|
|
91
83
|
provisionId: string;
|
|
92
|
-
dpopKey:
|
|
84
|
+
dpopKey: Key;
|
|
85
|
+
rawJwk: JsonWebKey;
|
|
93
86
|
pkceVerifier?: string;
|
|
94
87
|
}>;
|
|
95
88
|
registerSession(params: RegisterSessionParams): Promise<HappyViewSession>;
|
|
@@ -98,15 +91,11 @@ declare class HappyViewOAuthClient {
|
|
|
98
91
|
restore(): Promise<HappyViewSession | null>;
|
|
99
92
|
}
|
|
100
93
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
accessToken?: string;
|
|
107
|
-
nonce?: string;
|
|
108
|
-
}
|
|
109
|
-
declare function generateDpopProof(crypto: CryptoAdapter, params: DpopProofParams): Promise<string>;
|
|
94
|
+
/**
|
|
95
|
+
* Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)
|
|
96
|
+
* into a WebcryptoKey that can sign JWTs.
|
|
97
|
+
*/
|
|
98
|
+
declare function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey>;
|
|
110
99
|
|
|
111
100
|
declare class HappyViewError extends Error {
|
|
112
101
|
constructor(message: string);
|
|
@@ -139,4 +128,4 @@ declare class MemoryStorage implements StorageAdapter {
|
|
|
139
128
|
delete(key: string): Promise<void>;
|
|
140
129
|
}
|
|
141
130
|
|
|
142
|
-
export { ApiError, AuthenticationError, type
|
|
131
|
+
export { ApiError, AuthenticationError, type DpopProvision, HappyViewError, HappyViewOAuthClient, type HappyViewOAuthClientOptions, HappyViewSession, type HappyViewSessionOptions, InvalidStateError, MemoryStorage, type ProvisionKeyResponse, type RegisterSessionParams, type RegisterSessionResponse, ResolutionError, type StorageAdapter, type StoredSession, TokenExchangeError, importJwk };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { Key } from '@atproto/jwk';
|
|
2
|
+
import { WebcryptoKey } from '@atproto/jwk-webcrypto';
|
|
3
|
+
|
|
4
|
+
interface HappyViewSessionOptions {
|
|
5
|
+
did: string;
|
|
6
|
+
dpopKey: Key;
|
|
7
|
+
accessToken: string;
|
|
8
|
+
clientKey: string;
|
|
9
|
+
instanceUrl: string;
|
|
10
|
+
fetch?: typeof globalThis.fetch;
|
|
7
11
|
}
|
|
12
|
+
declare class HappyViewSession {
|
|
13
|
+
readonly did: string;
|
|
14
|
+
private readonly dpopKey;
|
|
15
|
+
private readonly accessToken;
|
|
16
|
+
private readonly clientKey;
|
|
17
|
+
private readonly instanceUrl;
|
|
18
|
+
private readonly _fetch;
|
|
19
|
+
private dpopNonce;
|
|
20
|
+
constructor(options: HappyViewSessionOptions);
|
|
21
|
+
fetchHandler(url: string, init: RequestInit): Promise<Response>;
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
interface StorageAdapter {
|
|
9
25
|
get(key: string): Promise<string | null>;
|
|
10
26
|
set(key: string, value: string): Promise<void>;
|
|
@@ -14,7 +30,6 @@ interface HappyViewOAuthClientOptions {
|
|
|
14
30
|
instanceUrl: string;
|
|
15
31
|
clientKey: string;
|
|
16
32
|
clientSecret?: string;
|
|
17
|
-
crypto: CryptoAdapter;
|
|
18
33
|
storage?: StorageAdapter;
|
|
19
34
|
}
|
|
20
35
|
interface DpopProvision {
|
|
@@ -54,42 +69,20 @@ interface RegisterSessionResponse {
|
|
|
54
69
|
did: string;
|
|
55
70
|
}
|
|
56
71
|
|
|
57
|
-
interface HappyViewSessionOptions {
|
|
58
|
-
did: string;
|
|
59
|
-
dpopKey: JsonWebKey;
|
|
60
|
-
accessToken: string;
|
|
61
|
-
clientKey: string;
|
|
62
|
-
instanceUrl: string;
|
|
63
|
-
crypto: CryptoAdapter;
|
|
64
|
-
fetch?: typeof globalThis.fetch;
|
|
65
|
-
}
|
|
66
|
-
declare class HappyViewSession {
|
|
67
|
-
readonly did: string;
|
|
68
|
-
private readonly dpopKey;
|
|
69
|
-
private readonly accessToken;
|
|
70
|
-
private readonly clientKey;
|
|
71
|
-
private readonly instanceUrl;
|
|
72
|
-
private readonly crypto;
|
|
73
|
-
private readonly _fetch;
|
|
74
|
-
private dpopNonce;
|
|
75
|
-
constructor(options: HappyViewSessionOptions);
|
|
76
|
-
fetchHandler(url: string, init: RequestInit): Promise<Response>;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
72
|
declare class HappyViewOAuthClient {
|
|
80
73
|
protected readonly instanceUrl: string;
|
|
81
74
|
protected readonly clientKey: string;
|
|
82
|
-
protected readonly crypto: CryptoAdapter;
|
|
83
75
|
protected readonly storage: StorageAdapter;
|
|
84
76
|
private readonly clientSecret;
|
|
85
|
-
|
|
77
|
+
protected readonly _fetch: typeof globalThis.fetch;
|
|
86
78
|
constructor(options: HappyViewOAuthClientOptions & {
|
|
87
79
|
fetch?: typeof globalThis.fetch;
|
|
88
80
|
});
|
|
89
81
|
get isConfidential(): boolean;
|
|
90
82
|
provisionDpopKey(): Promise<{
|
|
91
83
|
provisionId: string;
|
|
92
|
-
dpopKey:
|
|
84
|
+
dpopKey: Key;
|
|
85
|
+
rawJwk: JsonWebKey;
|
|
93
86
|
pkceVerifier?: string;
|
|
94
87
|
}>;
|
|
95
88
|
registerSession(params: RegisterSessionParams): Promise<HappyViewSession>;
|
|
@@ -98,15 +91,11 @@ declare class HappyViewOAuthClient {
|
|
|
98
91
|
restore(): Promise<HappyViewSession | null>;
|
|
99
92
|
}
|
|
100
93
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
accessToken?: string;
|
|
107
|
-
nonce?: string;
|
|
108
|
-
}
|
|
109
|
-
declare function generateDpopProof(crypto: CryptoAdapter, params: DpopProofParams): Promise<string>;
|
|
94
|
+
/**
|
|
95
|
+
* Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)
|
|
96
|
+
* into a WebcryptoKey that can sign JWTs.
|
|
97
|
+
*/
|
|
98
|
+
declare function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey>;
|
|
110
99
|
|
|
111
100
|
declare class HappyViewError extends Error {
|
|
112
101
|
constructor(message: string);
|
|
@@ -139,4 +128,4 @@ declare class MemoryStorage implements StorageAdapter {
|
|
|
139
128
|
delete(key: string): Promise<void>;
|
|
140
129
|
}
|
|
141
130
|
|
|
142
|
-
export { ApiError, AuthenticationError, type
|
|
131
|
+
export { ApiError, AuthenticationError, type DpopProvision, HappyViewError, HappyViewOAuthClient, type HappyViewOAuthClientOptions, HappyViewSession, type HappyViewSessionOptions, InvalidStateError, MemoryStorage, type ProvisionKeyResponse, type RegisterSessionParams, type RegisterSessionResponse, ResolutionError, type StorageAdapter, type StoredSession, TokenExchangeError, importJwk };
|
package/dist/index.js
CHANGED
|
@@ -46,63 +46,46 @@ var ResolutionError = class extends HappyViewError {
|
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
// src/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const { privateKey, method, url, accessToken, nonce } = params;
|
|
59
|
-
const publicJwk = {
|
|
60
|
-
kty: privateKey.kty,
|
|
61
|
-
crv: privateKey.crv,
|
|
62
|
-
x: privateKey.x,
|
|
63
|
-
y: privateKey.y
|
|
64
|
-
};
|
|
65
|
-
const header = {
|
|
66
|
-
alg: "ES256",
|
|
67
|
-
typ: "dpop+jwt",
|
|
68
|
-
jwk: publicJwk
|
|
69
|
-
};
|
|
70
|
-
const jtiBytes = crypto.getRandomValues(16);
|
|
71
|
-
const jti = Array.from(jtiBytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
72
|
-
const payload = {
|
|
73
|
-
htm: method,
|
|
74
|
-
htu: url,
|
|
75
|
-
iat: Math.floor(Date.now() / 1e3),
|
|
76
|
-
jti
|
|
77
|
-
};
|
|
78
|
-
if (accessToken) {
|
|
79
|
-
const tokenBytes = new TextEncoder().encode(accessToken);
|
|
80
|
-
const hashBuf = await crypto.sha256(tokenBytes);
|
|
81
|
-
payload.ath = base64urlEncode(hashBuf);
|
|
82
|
-
}
|
|
83
|
-
if (nonce !== void 0) {
|
|
84
|
-
payload.nonce = nonce;
|
|
85
|
-
}
|
|
86
|
-
const headerB64 = base64urlEncode(
|
|
87
|
-
new TextEncoder().encode(JSON.stringify(header))
|
|
49
|
+
// src/import-jwk.ts
|
|
50
|
+
import { WebcryptoKey } from "@atproto/jwk-webcrypto";
|
|
51
|
+
async function importJwk(jwk) {
|
|
52
|
+
const privateKey = await crypto.subtle.importKey(
|
|
53
|
+
"jwk",
|
|
54
|
+
jwk,
|
|
55
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
56
|
+
true,
|
|
57
|
+
["sign"]
|
|
88
58
|
);
|
|
89
|
-
const
|
|
90
|
-
|
|
59
|
+
const { d: _, ...publicJwkFields } = jwk;
|
|
60
|
+
const publicKey = await crypto.subtle.importKey(
|
|
61
|
+
"jwk",
|
|
62
|
+
publicJwkFields,
|
|
63
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
64
|
+
true,
|
|
65
|
+
["verify"]
|
|
91
66
|
);
|
|
92
|
-
|
|
93
|
-
const signature = await crypto.signEs256(privateKey, message);
|
|
94
|
-
const sigB64 = base64urlEncode(signature);
|
|
95
|
-
return `${headerB64}.${payloadB64}.${sigB64}`;
|
|
67
|
+
return WebcryptoKey.fromKeypair({ privateKey, publicKey });
|
|
96
68
|
}
|
|
97
69
|
|
|
98
70
|
// src/session.ts
|
|
71
|
+
function randomHex(byteLength) {
|
|
72
|
+
const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
|
|
73
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
74
|
+
}
|
|
75
|
+
function base64url(buffer) {
|
|
76
|
+
const bytes = new Uint8Array(buffer);
|
|
77
|
+
let binary = "";
|
|
78
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
79
|
+
binary += String.fromCharCode(bytes[i]);
|
|
80
|
+
}
|
|
81
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
82
|
+
}
|
|
99
83
|
var HappyViewSession = class {
|
|
100
84
|
did;
|
|
101
85
|
dpopKey;
|
|
102
86
|
accessToken;
|
|
103
87
|
clientKey;
|
|
104
88
|
instanceUrl;
|
|
105
|
-
crypto;
|
|
106
89
|
_fetch;
|
|
107
90
|
dpopNonce;
|
|
108
91
|
constructor(options) {
|
|
@@ -111,29 +94,61 @@ var HappyViewSession = class {
|
|
|
111
94
|
this.accessToken = options.accessToken;
|
|
112
95
|
this.clientKey = options.clientKey;
|
|
113
96
|
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
114
|
-
this.
|
|
115
|
-
this._fetch = options.fetch ?? globalThis.fetch;
|
|
97
|
+
this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
116
98
|
}
|
|
117
99
|
async fetchHandler(url, init) {
|
|
118
100
|
const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.instanceUrl}${url}`;
|
|
119
101
|
const method = (init.method ?? "GET").toUpperCase();
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
102
|
+
const htu = fullUrl.split("?")[0];
|
|
103
|
+
const sendWithProof = async () => {
|
|
104
|
+
const tokenHash = await crypto.subtle.digest(
|
|
105
|
+
"SHA-256",
|
|
106
|
+
new TextEncoder().encode(this.accessToken)
|
|
107
|
+
);
|
|
108
|
+
const ath = base64url(tokenHash);
|
|
109
|
+
const proof = await this.dpopKey.createJwt(
|
|
110
|
+
{
|
|
111
|
+
alg: "ES256",
|
|
112
|
+
typ: "dpop+jwt",
|
|
113
|
+
jwk: this.dpopKey.publicJwk
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
htm: method,
|
|
117
|
+
htu,
|
|
118
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
119
|
+
jti: randomHex(16),
|
|
120
|
+
ath,
|
|
121
|
+
nonce: this.dpopNonce
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
const headers = new Headers(init.headers);
|
|
125
|
+
headers.set("authorization", `DPoP ${this.accessToken}`);
|
|
126
|
+
headers.set("dpop", proof);
|
|
127
|
+
headers.set("x-client-key", this.clientKey);
|
|
128
|
+
return this._fetch(fullUrl, { ...init, headers });
|
|
129
|
+
};
|
|
130
|
+
let response;
|
|
131
|
+
try {
|
|
132
|
+
response = await sendWithProof();
|
|
133
|
+
} catch (e) {
|
|
134
|
+
const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;
|
|
135
|
+
throw new Error(`fetchHandler failed: ${e.message}
|
|
136
|
+
|
|
137
|
+
DEBUG: ${info}`);
|
|
138
|
+
}
|
|
135
139
|
const nonce = response.headers.get("dpop-nonce");
|
|
136
|
-
if (nonce) {
|
|
140
|
+
if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {
|
|
141
|
+
this.dpopNonce = nonce;
|
|
142
|
+
try {
|
|
143
|
+
response = await sendWithProof();
|
|
144
|
+
} catch (e) {
|
|
145
|
+
throw new Error(`fetchHandler retry failed: ${e.message}`);
|
|
146
|
+
}
|
|
147
|
+
const retryNonce = response.headers.get("dpop-nonce");
|
|
148
|
+
if (retryNonce) {
|
|
149
|
+
this.dpopNonce = retryNonce;
|
|
150
|
+
}
|
|
151
|
+
} else if (nonce) {
|
|
137
152
|
this.dpopNonce = nonce;
|
|
138
153
|
}
|
|
139
154
|
return response;
|
|
@@ -160,7 +175,6 @@ var LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
|
160
175
|
var HappyViewOAuthClient = class {
|
|
161
176
|
instanceUrl;
|
|
162
177
|
clientKey;
|
|
163
|
-
crypto;
|
|
164
178
|
storage;
|
|
165
179
|
clientSecret;
|
|
166
180
|
_fetch;
|
|
@@ -168,9 +182,8 @@ var HappyViewOAuthClient = class {
|
|
|
168
182
|
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
169
183
|
this.clientKey = options.clientKey;
|
|
170
184
|
this.clientSecret = options.clientSecret;
|
|
171
|
-
this.crypto = options.crypto;
|
|
172
185
|
this.storage = options.storage ?? new MemoryStorage();
|
|
173
|
-
this._fetch = options.fetch ??
|
|
186
|
+
this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
174
187
|
}
|
|
175
188
|
get isConfidential() {
|
|
176
189
|
return this.clientSecret !== void 0;
|
|
@@ -186,8 +199,8 @@ var HappyViewOAuthClient = class {
|
|
|
186
199
|
const body = {};
|
|
187
200
|
let pkceVerifier;
|
|
188
201
|
if (!this.isConfidential) {
|
|
189
|
-
pkceVerifier =
|
|
190
|
-
const challenge = await
|
|
202
|
+
pkceVerifier = generatePkceVerifier();
|
|
203
|
+
const challenge = await computePkceChallenge(pkceVerifier);
|
|
191
204
|
body.pkce_challenge = challenge;
|
|
192
205
|
}
|
|
193
206
|
const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {
|
|
@@ -204,9 +217,11 @@ var HappyViewOAuthClient = class {
|
|
|
204
217
|
);
|
|
205
218
|
}
|
|
206
219
|
const data = await resp.json();
|
|
220
|
+
const dpopKey = await importJwk(data.dpop_key);
|
|
207
221
|
return {
|
|
208
222
|
provisionId: data.provision_id,
|
|
209
|
-
dpopKey
|
|
223
|
+
dpopKey,
|
|
224
|
+
rawJwk: data.dpop_key,
|
|
210
225
|
pkceVerifier
|
|
211
226
|
};
|
|
212
227
|
}
|
|
@@ -244,6 +259,7 @@ var HappyViewOAuthClient = class {
|
|
|
244
259
|
);
|
|
245
260
|
}
|
|
246
261
|
const data = await resp.json();
|
|
262
|
+
const dpopKey = await importJwk(params.dpopKey);
|
|
247
263
|
const storedSession = {
|
|
248
264
|
did: data.did,
|
|
249
265
|
dpopKey: params.dpopKey,
|
|
@@ -258,34 +274,28 @@ var HappyViewOAuthClient = class {
|
|
|
258
274
|
await this.storage.set(LAST_ACTIVE_KEY, data.did);
|
|
259
275
|
return new HappyViewSession({
|
|
260
276
|
did: data.did,
|
|
261
|
-
dpopKey
|
|
277
|
+
dpopKey,
|
|
262
278
|
accessToken: params.accessToken,
|
|
263
279
|
clientKey: this.clientKey,
|
|
264
280
|
instanceUrl: this.instanceUrl,
|
|
265
|
-
|
|
281
|
+
fetch: this._fetch
|
|
266
282
|
});
|
|
267
283
|
}
|
|
268
284
|
async deleteSession(did) {
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
const resp = await this._fetch(
|
|
276
|
-
`${this.instanceUrl}/oauth/sessions/${did}`,
|
|
277
|
-
{
|
|
278
|
-
method: "DELETE",
|
|
279
|
-
headers
|
|
280
|
-
}
|
|
281
|
-
);
|
|
282
|
-
if (!resp.ok && resp.status !== 404) {
|
|
283
|
-
const body = await resp.json().catch(() => ({}));
|
|
284
|
-
throw new ApiError(
|
|
285
|
-
`Failed to delete session: ${resp.status} ${body.message ?? resp.statusText}`,
|
|
286
|
-
resp.status,
|
|
287
|
-
body
|
|
285
|
+
const session = await this.restoreSession(did);
|
|
286
|
+
if (session) {
|
|
287
|
+
const resp = await session.fetchHandler(
|
|
288
|
+
`${this.instanceUrl}/oauth/sessions/${did}`,
|
|
289
|
+
{ method: "DELETE" }
|
|
288
290
|
);
|
|
291
|
+
if (!resp.ok && resp.status !== 404) {
|
|
292
|
+
const body = await resp.json().catch(() => ({}));
|
|
293
|
+
throw new ApiError(
|
|
294
|
+
`Failed to delete session: ${resp.status} ${body.message ?? resp.statusText}`,
|
|
295
|
+
resp.status,
|
|
296
|
+
body
|
|
297
|
+
);
|
|
298
|
+
}
|
|
289
299
|
}
|
|
290
300
|
await this.storage.delete(`${STORAGE_PREFIX}${did}`);
|
|
291
301
|
const lastActive = await this.storage.get(LAST_ACTIVE_KEY);
|
|
@@ -297,13 +307,14 @@ var HappyViewOAuthClient = class {
|
|
|
297
307
|
const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);
|
|
298
308
|
if (!stored) return null;
|
|
299
309
|
const data = JSON.parse(stored);
|
|
310
|
+
const dpopKey = await importJwk(data.dpopKey);
|
|
300
311
|
return new HappyViewSession({
|
|
301
312
|
did: data.did,
|
|
302
|
-
dpopKey
|
|
313
|
+
dpopKey,
|
|
303
314
|
accessToken: data.accessToken,
|
|
304
315
|
clientKey: data.clientKey,
|
|
305
316
|
instanceUrl: data.instanceUrl,
|
|
306
|
-
|
|
317
|
+
fetch: this._fetch
|
|
307
318
|
});
|
|
308
319
|
}
|
|
309
320
|
async restore() {
|
|
@@ -312,6 +323,26 @@ var HappyViewOAuthClient = class {
|
|
|
312
323
|
return this.restoreSession(did);
|
|
313
324
|
}
|
|
314
325
|
};
|
|
326
|
+
function generatePkceVerifier() {
|
|
327
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
328
|
+
let binary = "";
|
|
329
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
330
|
+
binary += String.fromCharCode(bytes[i]);
|
|
331
|
+
}
|
|
332
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
333
|
+
}
|
|
334
|
+
async function computePkceChallenge(verifier) {
|
|
335
|
+
const hash = await crypto.subtle.digest(
|
|
336
|
+
"SHA-256",
|
|
337
|
+
new TextEncoder().encode(verifier)
|
|
338
|
+
);
|
|
339
|
+
const bytes = new Uint8Array(hash);
|
|
340
|
+
let binary = "";
|
|
341
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
342
|
+
binary += String.fromCharCode(bytes[i]);
|
|
343
|
+
}
|
|
344
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
345
|
+
}
|
|
315
346
|
export {
|
|
316
347
|
ApiError,
|
|
317
348
|
AuthenticationError,
|
|
@@ -322,7 +353,6 @@ export {
|
|
|
322
353
|
MemoryStorage,
|
|
323
354
|
ResolutionError,
|
|
324
355
|
TokenExchangeError,
|
|
325
|
-
|
|
326
|
-
generateDpopProof
|
|
356
|
+
importJwk
|
|
327
357
|
};
|
|
328
358
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/dpop-proof.ts","../src/session.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n","import type { CryptoAdapter } from \"./types\";\n\nexport function base64urlEncode(data: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n binary += String.fromCharCode(data[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport interface DpopProofParams {\n privateKey: JsonWebKey;\n method: string;\n url: string;\n accessToken?: string;\n nonce?: string;\n}\n\nexport async function generateDpopProof(\n crypto: CryptoAdapter,\n params: DpopProofParams,\n): Promise<string> {\n const { privateKey, method, url, accessToken, nonce } = params;\n\n const publicJwk = {\n kty: privateKey.kty,\n crv: privateKey.crv,\n x: privateKey.x,\n y: privateKey.y,\n };\n\n const header = {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: publicJwk,\n };\n\n // Generate jti\n const jtiBytes = crypto.getRandomValues(16);\n const jti = Array.from(jtiBytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n\n const payload: Record<string, unknown> = {\n htm: method,\n htu: url,\n iat: Math.floor(Date.now() / 1000),\n jti,\n };\n\n // Compute ath: base64url(SHA-256(access_token)) — only when token is present\n if (accessToken) {\n const tokenBytes = new TextEncoder().encode(accessToken);\n const hashBuf = await crypto.sha256(tokenBytes);\n payload.ath = base64urlEncode(hashBuf);\n }\n\n if (nonce !== undefined) {\n payload.nonce = nonce;\n }\n\n const headerB64 = base64urlEncode(\n new TextEncoder().encode(JSON.stringify(header)),\n );\n const payloadB64 = base64urlEncode(\n new TextEncoder().encode(JSON.stringify(payload)),\n );\n\n const message = new TextEncoder().encode(`${headerB64}.${payloadB64}`);\n const signature = await crypto.signEs256(privateKey, message);\n const sigB64 = base64urlEncode(signature);\n\n return `${headerB64}.${payloadB64}.${sigB64}`;\n}\n","import { generateDpopProof } from \"./dpop-proof\";\nimport type { CryptoAdapter } from \"./types\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: JsonWebKey;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n crypto: CryptoAdapter;\n fetch?: typeof globalThis.fetch;\n}\n\nexport class HappyViewSession {\n readonly did: string;\n\n private readonly dpopKey: JsonWebKey;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly crypto: CryptoAdapter;\n private readonly _fetch: typeof globalThis.fetch;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.crypto = options.crypto;\n this._fetch = options.fetch ?? globalThis.fetch;\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url) ? url : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n\n const proof = await generateDpopProof(this.crypto, {\n privateKey: this.dpopKey,\n method,\n url: fullUrl,\n accessToken: this.accessToken,\n nonce: this.dpopNonce,\n });\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n const response = await this._fetch(fullUrl, {\n ...init,\n headers,\n });\n\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import { ApiError } from \"./errors\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n CryptoAdapter,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\nconst STORAGE_PREFIX = \"happyview:session:\";\nconst LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport class HappyViewOAuthClient {\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly crypto: CryptoAdapter;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n private readonly _fetch: typeof globalThis.fetch;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.crypto = options.crypto;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch = options.fetch ?? globalThis.fetch;\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = await this.crypto.generatePkceVerifier();\n const challenge = await this.crypto.computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n return {\n provisionId: data.provision_id,\n dpopKey: data.dpop_key,\n pkceVerifier,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n return new HappyViewSession({\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n crypto: this.crypto,\n });\n }\n\n async deleteSession(did: string): Promise<void> {\n const headers: Record<string, string> = {\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const resp = await this._fetch(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n {\n method: \"DELETE\",\n headers,\n },\n );\n\n if (!resp.ok && resp.status !== 404) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n return new HappyViewSession({\n did: data.did,\n dpopKey: data.dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n crypto: this.crypto,\n });\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n"],"mappings":"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","names":["body"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/import-jwk.ts","../src/session.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n","import { WebcryptoKey } from \"@atproto/jwk-webcrypto\";\n\n/**\n * Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)\n * into a WebcryptoKey that can sign JWTs.\n */\nexport async function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey> {\n const privateKey = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"],\n );\n\n // Derive public key by stripping the private component\n const { d: _, ...publicJwkFields } = jwk;\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n publicJwkFields,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"],\n );\n\n return WebcryptoKey.fromKeypair({ privateKey, publicKey });\n}\n","import type { Key } from \"@atproto/jwk\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: Key;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n fetch?: typeof globalThis.fetch;\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(byteLength));\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class HappyViewSession {\n readonly did: string;\n\n private readonly dpopKey: Key;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly _fetch: typeof globalThis.fetch;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url)\n ? url\n : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const htu = fullUrl.split(\"?\")[0];\n\n const sendWithProof = async (): Promise<Response> => {\n const tokenHash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(this.accessToken),\n );\n const ath = base64url(tokenHash);\n\n const proof = await this.dpopKey.createJwt(\n {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: this.dpopKey.publicJwk!,\n },\n {\n htm: method,\n htu,\n iat: Math.floor(Date.now() / 1000),\n jti: randomHex(16),\n ath,\n nonce: this.dpopNonce,\n },\n );\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n return this._fetch(fullUrl, { ...init, headers });\n };\n\n let response: Response;\n try {\n response = await sendWithProof();\n } catch (e) {\n const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;\n throw new Error(`fetchHandler failed: ${(e as Error).message}\\n\\nDEBUG: ${info}`);\n }\n\n // Retry once if the server requires a DPoP nonce we didn't have\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {\n this.dpopNonce = nonce;\n try {\n response = await sendWithProof();\n } catch (e) {\n throw new Error(`fetchHandler retry failed: ${(e as Error).message}`);\n }\n const retryNonce = response.headers.get(\"dpop-nonce\");\n if (retryNonce) {\n this.dpopNonce = retryNonce;\n }\n } else if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import type { Key } from \"@atproto/jwk\";\nimport { ApiError } from \"./errors\";\nimport { importJwk } from \"./import-jwk\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\nconst STORAGE_PREFIX = \"happyview:session:\";\nconst LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport class HappyViewOAuthClient {\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n protected readonly _fetch: typeof globalThis.fetch;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: Key;\n rawJwk: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = generatePkceVerifier();\n const challenge = await computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n const dpopKey = await importJwk(data.dpop_key);\n return {\n provisionId: data.provision_id,\n dpopKey,\n rawJwk: data.dpop_key,\n pkceVerifier,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n const dpopKey = await importJwk(params.dpopKey);\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n fetch: this._fetch,\n });\n }\n\n async deleteSession(did: string): Promise<void> {\n const session = await this.restoreSession(did);\n if (session) {\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"DELETE\" },\n );\n\n if (!resp.ok && resp.status !== 404) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n }\n\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n const dpopKey = await importJwk(data.dpopKey);\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n fetch: this._fetch,\n });\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n\n// PKCE helpers — inline since they're just a few lines of Web Crypto\nfunction generatePkceVerifier(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nasync function computePkceChallenge(verifier: string): Promise<string> {\n const hash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n const bytes = new Uint8Array(hash);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n"],"mappings":"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","names":["body"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyview/oauth-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "HappyView OAuth client for ATProto DPoP authentication",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"url": "https://github.com/gamesgamesgamesgamesgames/happyview.git",
|
|
9
9
|
"directory": "packages/oauth-client"
|
|
10
10
|
},
|
|
11
|
+
"bugs": "https://github.com/gamesgamesgamesgamesgames/happyview/issues",
|
|
12
|
+
"homepage": "https://happyview.dev",
|
|
11
13
|
"publishConfig": {
|
|
12
14
|
"access": "public"
|
|
13
15
|
},
|
|
@@ -22,16 +24,22 @@
|
|
|
22
24
|
"main": "./dist/index.cjs",
|
|
23
25
|
"module": "./dist/index.js",
|
|
24
26
|
"types": "./dist/index.d.ts",
|
|
25
|
-
"files": [
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
26
30
|
"scripts": {
|
|
27
31
|
"build": "tsup",
|
|
28
32
|
"test": "bun test",
|
|
29
33
|
"typecheck": "tsc --noEmit"
|
|
30
34
|
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@atproto/jwk": "^0.6.0",
|
|
37
|
+
"@atproto/jwk-webcrypto": "^0.2.0"
|
|
38
|
+
},
|
|
31
39
|
"devDependencies": {
|
|
32
40
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
33
41
|
"@semantic-release/github": "^12.0.6",
|
|
34
|
-
"@semantic-release/
|
|
42
|
+
"@semantic-release/exec": "^7.0.0",
|
|
35
43
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
36
44
|
"@types/bun": "^1.3.12",
|
|
37
45
|
"semantic-release": "^25.0.3",
|