@happyview/oauth-client 1.0.0 → 1.1.0-dev.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 +128 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -42
- package/dist/index.d.ts +32 -42
- package/dist/index.js +126 -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
|
@@ -26,11 +26,11 @@ __export(index_exports, {
|
|
|
26
26
|
HappyViewOAuthClient: () => HappyViewOAuthClient,
|
|
27
27
|
HappyViewSession: () => HappyViewSession,
|
|
28
28
|
InvalidStateError: () => InvalidStateError,
|
|
29
|
+
LAST_ACTIVE_KEY: () => LAST_ACTIVE_KEY,
|
|
29
30
|
MemoryStorage: () => MemoryStorage,
|
|
30
31
|
ResolutionError: () => ResolutionError,
|
|
31
32
|
TokenExchangeError: () => TokenExchangeError,
|
|
32
|
-
|
|
33
|
-
generateDpopProof: () => generateDpopProof
|
|
33
|
+
importJwk: () => importJwk
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
|
|
@@ -82,63 +82,46 @@ var ResolutionError = class extends HappyViewError {
|
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
|
|
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))
|
|
85
|
+
// src/import-jwk.ts
|
|
86
|
+
var import_jwk_webcrypto = require("@atproto/jwk-webcrypto");
|
|
87
|
+
async function importJwk(jwk) {
|
|
88
|
+
const privateKey = await crypto.subtle.importKey(
|
|
89
|
+
"jwk",
|
|
90
|
+
jwk,
|
|
91
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
92
|
+
true,
|
|
93
|
+
["sign"]
|
|
124
94
|
);
|
|
125
|
-
const
|
|
126
|
-
|
|
95
|
+
const { d: _, ...publicJwkFields } = jwk;
|
|
96
|
+
const publicKey = await crypto.subtle.importKey(
|
|
97
|
+
"jwk",
|
|
98
|
+
publicJwkFields,
|
|
99
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
100
|
+
true,
|
|
101
|
+
["verify"]
|
|
127
102
|
);
|
|
128
|
-
|
|
129
|
-
const signature = await crypto.signEs256(privateKey, message);
|
|
130
|
-
const sigB64 = base64urlEncode(signature);
|
|
131
|
-
return `${headerB64}.${payloadB64}.${sigB64}`;
|
|
103
|
+
return import_jwk_webcrypto.WebcryptoKey.fromKeypair({ privateKey, publicKey });
|
|
132
104
|
}
|
|
133
105
|
|
|
134
106
|
// src/session.ts
|
|
107
|
+
function randomHex(byteLength) {
|
|
108
|
+
const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
|
|
109
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
110
|
+
}
|
|
111
|
+
function base64url(buffer) {
|
|
112
|
+
const bytes = new Uint8Array(buffer);
|
|
113
|
+
let binary = "";
|
|
114
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
115
|
+
binary += String.fromCharCode(bytes[i]);
|
|
116
|
+
}
|
|
117
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
118
|
+
}
|
|
135
119
|
var HappyViewSession = class {
|
|
136
120
|
did;
|
|
137
121
|
dpopKey;
|
|
138
122
|
accessToken;
|
|
139
123
|
clientKey;
|
|
140
124
|
instanceUrl;
|
|
141
|
-
crypto;
|
|
142
125
|
_fetch;
|
|
143
126
|
dpopNonce;
|
|
144
127
|
constructor(options) {
|
|
@@ -147,29 +130,61 @@ var HappyViewSession = class {
|
|
|
147
130
|
this.accessToken = options.accessToken;
|
|
148
131
|
this.clientKey = options.clientKey;
|
|
149
132
|
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
150
|
-
this.
|
|
151
|
-
this._fetch = options.fetch ?? globalThis.fetch;
|
|
133
|
+
this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
152
134
|
}
|
|
153
135
|
async fetchHandler(url, init) {
|
|
154
136
|
const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.instanceUrl}${url}`;
|
|
155
137
|
const method = (init.method ?? "GET").toUpperCase();
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
138
|
+
const htu = fullUrl.split("?")[0];
|
|
139
|
+
const sendWithProof = async () => {
|
|
140
|
+
const tokenHash = await crypto.subtle.digest(
|
|
141
|
+
"SHA-256",
|
|
142
|
+
new TextEncoder().encode(this.accessToken)
|
|
143
|
+
);
|
|
144
|
+
const ath = base64url(tokenHash);
|
|
145
|
+
const proof = await this.dpopKey.createJwt(
|
|
146
|
+
{
|
|
147
|
+
alg: "ES256",
|
|
148
|
+
typ: "dpop+jwt",
|
|
149
|
+
jwk: this.dpopKey.publicJwk
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
htm: method,
|
|
153
|
+
htu,
|
|
154
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
155
|
+
jti: randomHex(16),
|
|
156
|
+
ath,
|
|
157
|
+
nonce: this.dpopNonce
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
const headers = new Headers(init.headers);
|
|
161
|
+
headers.set("authorization", `DPoP ${this.accessToken}`);
|
|
162
|
+
headers.set("dpop", proof);
|
|
163
|
+
headers.set("x-client-key", this.clientKey);
|
|
164
|
+
return this._fetch(fullUrl, { ...init, headers });
|
|
165
|
+
};
|
|
166
|
+
let response;
|
|
167
|
+
try {
|
|
168
|
+
response = await sendWithProof();
|
|
169
|
+
} catch (e) {
|
|
170
|
+
const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;
|
|
171
|
+
throw new Error(`fetchHandler failed: ${e.message}
|
|
172
|
+
|
|
173
|
+
DEBUG: ${info}`);
|
|
174
|
+
}
|
|
171
175
|
const nonce = response.headers.get("dpop-nonce");
|
|
172
|
-
if (nonce) {
|
|
176
|
+
if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {
|
|
177
|
+
this.dpopNonce = nonce;
|
|
178
|
+
try {
|
|
179
|
+
response = await sendWithProof();
|
|
180
|
+
} catch (e) {
|
|
181
|
+
throw new Error(`fetchHandler retry failed: ${e.message}`);
|
|
182
|
+
}
|
|
183
|
+
const retryNonce = response.headers.get("dpop-nonce");
|
|
184
|
+
if (retryNonce) {
|
|
185
|
+
this.dpopNonce = retryNonce;
|
|
186
|
+
}
|
|
187
|
+
} else if (nonce) {
|
|
173
188
|
this.dpopNonce = nonce;
|
|
174
189
|
}
|
|
175
190
|
return response;
|
|
@@ -196,7 +211,6 @@ var LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
|
196
211
|
var HappyViewOAuthClient = class {
|
|
197
212
|
instanceUrl;
|
|
198
213
|
clientKey;
|
|
199
|
-
crypto;
|
|
200
214
|
storage;
|
|
201
215
|
clientSecret;
|
|
202
216
|
_fetch;
|
|
@@ -204,9 +218,8 @@ var HappyViewOAuthClient = class {
|
|
|
204
218
|
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
205
219
|
this.clientKey = options.clientKey;
|
|
206
220
|
this.clientSecret = options.clientSecret;
|
|
207
|
-
this.crypto = options.crypto;
|
|
208
221
|
this.storage = options.storage ?? new MemoryStorage();
|
|
209
|
-
this._fetch = options.fetch ??
|
|
222
|
+
this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
210
223
|
}
|
|
211
224
|
get isConfidential() {
|
|
212
225
|
return this.clientSecret !== void 0;
|
|
@@ -222,8 +235,8 @@ var HappyViewOAuthClient = class {
|
|
|
222
235
|
const body = {};
|
|
223
236
|
let pkceVerifier;
|
|
224
237
|
if (!this.isConfidential) {
|
|
225
|
-
pkceVerifier =
|
|
226
|
-
const challenge = await
|
|
238
|
+
pkceVerifier = generatePkceVerifier();
|
|
239
|
+
const challenge = await computePkceChallenge(pkceVerifier);
|
|
227
240
|
body.pkce_challenge = challenge;
|
|
228
241
|
}
|
|
229
242
|
const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {
|
|
@@ -240,9 +253,11 @@ var HappyViewOAuthClient = class {
|
|
|
240
253
|
);
|
|
241
254
|
}
|
|
242
255
|
const data = await resp.json();
|
|
256
|
+
const dpopKey = await importJwk(data.dpop_key);
|
|
243
257
|
return {
|
|
244
258
|
provisionId: data.provision_id,
|
|
245
|
-
dpopKey
|
|
259
|
+
dpopKey,
|
|
260
|
+
rawJwk: data.dpop_key,
|
|
246
261
|
pkceVerifier
|
|
247
262
|
};
|
|
248
263
|
}
|
|
@@ -280,6 +295,7 @@ var HappyViewOAuthClient = class {
|
|
|
280
295
|
);
|
|
281
296
|
}
|
|
282
297
|
const data = await resp.json();
|
|
298
|
+
const dpopKey = await importJwk(params.dpopKey);
|
|
283
299
|
const storedSession = {
|
|
284
300
|
did: data.did,
|
|
285
301
|
dpopKey: params.dpopKey,
|
|
@@ -294,34 +310,28 @@ var HappyViewOAuthClient = class {
|
|
|
294
310
|
await this.storage.set(LAST_ACTIVE_KEY, data.did);
|
|
295
311
|
return new HappyViewSession({
|
|
296
312
|
did: data.did,
|
|
297
|
-
dpopKey
|
|
313
|
+
dpopKey,
|
|
298
314
|
accessToken: params.accessToken,
|
|
299
315
|
clientKey: this.clientKey,
|
|
300
316
|
instanceUrl: this.instanceUrl,
|
|
301
|
-
|
|
317
|
+
fetch: this._fetch
|
|
302
318
|
});
|
|
303
319
|
}
|
|
304
320
|
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
|
|
321
|
+
const session = await this.restoreSession(did);
|
|
322
|
+
if (session) {
|
|
323
|
+
const resp = await session.fetchHandler(
|
|
324
|
+
`${this.instanceUrl}/oauth/sessions/${did}`,
|
|
325
|
+
{ method: "DELETE" }
|
|
324
326
|
);
|
|
327
|
+
if (!resp.ok && resp.status !== 404) {
|
|
328
|
+
const body = await resp.json().catch(() => ({}));
|
|
329
|
+
throw new ApiError(
|
|
330
|
+
`Failed to delete session: ${resp.status} ${body.message ?? resp.statusText}`,
|
|
331
|
+
resp.status,
|
|
332
|
+
body
|
|
333
|
+
);
|
|
334
|
+
}
|
|
325
335
|
}
|
|
326
336
|
await this.storage.delete(`${STORAGE_PREFIX}${did}`);
|
|
327
337
|
const lastActive = await this.storage.get(LAST_ACTIVE_KEY);
|
|
@@ -333,13 +343,14 @@ var HappyViewOAuthClient = class {
|
|
|
333
343
|
const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);
|
|
334
344
|
if (!stored) return null;
|
|
335
345
|
const data = JSON.parse(stored);
|
|
346
|
+
const dpopKey = await importJwk(data.dpopKey);
|
|
336
347
|
return new HappyViewSession({
|
|
337
348
|
did: data.did,
|
|
338
|
-
dpopKey
|
|
349
|
+
dpopKey,
|
|
339
350
|
accessToken: data.accessToken,
|
|
340
351
|
clientKey: data.clientKey,
|
|
341
352
|
instanceUrl: data.instanceUrl,
|
|
342
|
-
|
|
353
|
+
fetch: this._fetch
|
|
343
354
|
});
|
|
344
355
|
}
|
|
345
356
|
async restore() {
|
|
@@ -348,6 +359,26 @@ var HappyViewOAuthClient = class {
|
|
|
348
359
|
return this.restoreSession(did);
|
|
349
360
|
}
|
|
350
361
|
};
|
|
362
|
+
function generatePkceVerifier() {
|
|
363
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
364
|
+
let binary = "";
|
|
365
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
366
|
+
binary += String.fromCharCode(bytes[i]);
|
|
367
|
+
}
|
|
368
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
369
|
+
}
|
|
370
|
+
async function computePkceChallenge(verifier) {
|
|
371
|
+
const hash = await crypto.subtle.digest(
|
|
372
|
+
"SHA-256",
|
|
373
|
+
new TextEncoder().encode(verifier)
|
|
374
|
+
);
|
|
375
|
+
const bytes = new Uint8Array(hash);
|
|
376
|
+
let binary = "";
|
|
377
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
378
|
+
binary += String.fromCharCode(bytes[i]);
|
|
379
|
+
}
|
|
380
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
381
|
+
}
|
|
351
382
|
// Annotate the CommonJS export names for ESM import in node:
|
|
352
383
|
0 && (module.exports = {
|
|
353
384
|
ApiError,
|
|
@@ -356,10 +387,10 @@ var HappyViewOAuthClient = class {
|
|
|
356
387
|
HappyViewOAuthClient,
|
|
357
388
|
HappyViewSession,
|
|
358
389
|
InvalidStateError,
|
|
390
|
+
LAST_ACTIVE_KEY,
|
|
359
391
|
MemoryStorage,
|
|
360
392
|
ResolutionError,
|
|
361
393
|
TokenExchangeError,
|
|
362
|
-
|
|
363
|
-
generateDpopProof
|
|
394
|
+
importJwk
|
|
364
395
|
});
|
|
365
396
|
//# 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, LAST_ACTIVE_KEY } 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:\";\nexport const 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,21 @@ interface RegisterSessionResponse {
|
|
|
54
69
|
did: string;
|
|
55
70
|
}
|
|
56
71
|
|
|
57
|
-
|
|
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
|
-
|
|
72
|
+
declare const LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
79
73
|
declare class HappyViewOAuthClient {
|
|
80
74
|
protected readonly instanceUrl: string;
|
|
81
75
|
protected readonly clientKey: string;
|
|
82
|
-
protected readonly crypto: CryptoAdapter;
|
|
83
76
|
protected readonly storage: StorageAdapter;
|
|
84
77
|
private readonly clientSecret;
|
|
85
|
-
|
|
78
|
+
protected readonly _fetch: typeof globalThis.fetch;
|
|
86
79
|
constructor(options: HappyViewOAuthClientOptions & {
|
|
87
80
|
fetch?: typeof globalThis.fetch;
|
|
88
81
|
});
|
|
89
82
|
get isConfidential(): boolean;
|
|
90
83
|
provisionDpopKey(): Promise<{
|
|
91
84
|
provisionId: string;
|
|
92
|
-
dpopKey:
|
|
85
|
+
dpopKey: Key;
|
|
86
|
+
rawJwk: JsonWebKey;
|
|
93
87
|
pkceVerifier?: string;
|
|
94
88
|
}>;
|
|
95
89
|
registerSession(params: RegisterSessionParams): Promise<HappyViewSession>;
|
|
@@ -98,15 +92,11 @@ declare class HappyViewOAuthClient {
|
|
|
98
92
|
restore(): Promise<HappyViewSession | null>;
|
|
99
93
|
}
|
|
100
94
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
accessToken?: string;
|
|
107
|
-
nonce?: string;
|
|
108
|
-
}
|
|
109
|
-
declare function generateDpopProof(crypto: CryptoAdapter, params: DpopProofParams): Promise<string>;
|
|
95
|
+
/**
|
|
96
|
+
* Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)
|
|
97
|
+
* into a WebcryptoKey that can sign JWTs.
|
|
98
|
+
*/
|
|
99
|
+
declare function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey>;
|
|
110
100
|
|
|
111
101
|
declare class HappyViewError extends Error {
|
|
112
102
|
constructor(message: string);
|
|
@@ -139,4 +129,4 @@ declare class MemoryStorage implements StorageAdapter {
|
|
|
139
129
|
delete(key: string): Promise<void>;
|
|
140
130
|
}
|
|
141
131
|
|
|
142
|
-
export { ApiError, AuthenticationError, type
|
|
132
|
+
export { ApiError, AuthenticationError, type DpopProvision, HappyViewError, HappyViewOAuthClient, type HappyViewOAuthClientOptions, HappyViewSession, type HappyViewSessionOptions, InvalidStateError, LAST_ACTIVE_KEY, 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,21 @@ interface RegisterSessionResponse {
|
|
|
54
69
|
did: string;
|
|
55
70
|
}
|
|
56
71
|
|
|
57
|
-
|
|
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
|
-
|
|
72
|
+
declare const LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
79
73
|
declare class HappyViewOAuthClient {
|
|
80
74
|
protected readonly instanceUrl: string;
|
|
81
75
|
protected readonly clientKey: string;
|
|
82
|
-
protected readonly crypto: CryptoAdapter;
|
|
83
76
|
protected readonly storage: StorageAdapter;
|
|
84
77
|
private readonly clientSecret;
|
|
85
|
-
|
|
78
|
+
protected readonly _fetch: typeof globalThis.fetch;
|
|
86
79
|
constructor(options: HappyViewOAuthClientOptions & {
|
|
87
80
|
fetch?: typeof globalThis.fetch;
|
|
88
81
|
});
|
|
89
82
|
get isConfidential(): boolean;
|
|
90
83
|
provisionDpopKey(): Promise<{
|
|
91
84
|
provisionId: string;
|
|
92
|
-
dpopKey:
|
|
85
|
+
dpopKey: Key;
|
|
86
|
+
rawJwk: JsonWebKey;
|
|
93
87
|
pkceVerifier?: string;
|
|
94
88
|
}>;
|
|
95
89
|
registerSession(params: RegisterSessionParams): Promise<HappyViewSession>;
|
|
@@ -98,15 +92,11 @@ declare class HappyViewOAuthClient {
|
|
|
98
92
|
restore(): Promise<HappyViewSession | null>;
|
|
99
93
|
}
|
|
100
94
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
accessToken?: string;
|
|
107
|
-
nonce?: string;
|
|
108
|
-
}
|
|
109
|
-
declare function generateDpopProof(crypto: CryptoAdapter, params: DpopProofParams): Promise<string>;
|
|
95
|
+
/**
|
|
96
|
+
* Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)
|
|
97
|
+
* into a WebcryptoKey that can sign JWTs.
|
|
98
|
+
*/
|
|
99
|
+
declare function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey>;
|
|
110
100
|
|
|
111
101
|
declare class HappyViewError extends Error {
|
|
112
102
|
constructor(message: string);
|
|
@@ -139,4 +129,4 @@ declare class MemoryStorage implements StorageAdapter {
|
|
|
139
129
|
delete(key: string): Promise<void>;
|
|
140
130
|
}
|
|
141
131
|
|
|
142
|
-
export { ApiError, AuthenticationError, type
|
|
132
|
+
export { ApiError, AuthenticationError, type DpopProvision, HappyViewError, HappyViewOAuthClient, type HappyViewOAuthClientOptions, HappyViewSession, type HappyViewSessionOptions, InvalidStateError, LAST_ACTIVE_KEY, 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,
|
|
@@ -319,10 +350,10 @@ export {
|
|
|
319
350
|
HappyViewOAuthClient,
|
|
320
351
|
HappyViewSession,
|
|
321
352
|
InvalidStateError,
|
|
353
|
+
LAST_ACTIVE_KEY,
|
|
322
354
|
MemoryStorage,
|
|
323
355
|
ResolutionError,
|
|
324
356
|
TokenExchangeError,
|
|
325
|
-
|
|
326
|
-
generateDpopProof
|
|
357
|
+
importJwk
|
|
327
358
|
};
|
|
328
359
|
//# 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":";AAAO,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACxC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,WAAN,cAAuB,eAAe;AAAA,EAClC;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,QAAgB,MAAgB;AAC3D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,sBAAN,cAAkC,eAAe;AAAA,EAC7C;AAAA,EAET,YAAY,SAAiB,SAAiB,KAAK;AACjD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAEO,IAAM,oBAAN,cAAgC,eAAe;AAAA,EACpD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,qBAAN,cAAiC,eAAe;AAAA,EAC5C;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,QAAgB,MAAc;AACzD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,kBAAN,cAA8B,eAAe;AAAA,EAClD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;;;ACnDO,SAAS,gBAAgB,MAA0B;AACxD,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,cAAU,OAAO,aAAa,KAAK,CAAC,CAAC;AAAA,EACvC;AACA,SAAO,KAAK,MAAM,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAUA,eAAsB,kBACpB,QACA,QACiB;AACjB,QAAM,EAAE,YAAY,QAAQ,KAAK,aAAa,MAAM,IAAI;AAExD,QAAM,YAAY;AAAA,IAChB,KAAK,WAAW;AAAA,IAChB,KAAK,WAAW;AAAA,IAChB,GAAG,WAAW;AAAA,IACd,GAAG,WAAW;AAAA,EAChB;AAEA,QAAM,SAAS;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAGA,QAAM,WAAW,OAAO,gBAAgB,EAAE;AAC1C,QAAM,MAAM,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAEhF,QAAM,UAAmC;AAAA,IACvC,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACjC;AAAA,EACF;AAGA,MAAI,aAAa;AACf,UAAM,aAAa,IAAI,YAAY,EAAE,OAAO,WAAW;AACvD,UAAM,UAAU,MAAM,OAAO,OAAO,UAAU;AAC9C,YAAQ,MAAM,gBAAgB,OAAO;AAAA,EACvC;AAEA,MAAI,UAAU,QAAW;AACvB,YAAQ,QAAQ;AAAA,EAClB;AAEA,QAAM,YAAY;AAAA,IAChB,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,MAAM,CAAC;AAAA,EACjD;AACA,QAAM,aAAa;AAAA,IACjB,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,OAAO,CAAC;AAAA,EAClD;AAEA,QAAM,UAAU,IAAI,YAAY,EAAE,OAAO,GAAG,SAAS,IAAI,UAAU,EAAE;AACrE,QAAM,YAAY,MAAM,OAAO,UAAU,YAAY,OAAO;AAC5D,QAAM,SAAS,gBAAgB,SAAS;AAExC,SAAO,GAAG,SAAS,IAAI,UAAU,IAAI,MAAM;AAC7C;;;AC7DO,IAAM,mBAAN,MAAuB;AAAA,EACnB;AAAA,EAEQ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EAER,YAAY,SAAkC;AAC5C,SAAK,MAAM,QAAQ;AACnB,SAAK,UAAU,QAAQ;AACvB,SAAK,cAAc,QAAQ;AAC3B,SAAK,YAAY,QAAQ;AACzB,SAAK,cAAc,QAAQ,YAAY,QAAQ,QAAQ,EAAE;AACzD,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,QAAQ,SAAS,WAAW;AAAA,EAC5C;AAAA,EAEA,MAAM,aAAa,KAAa,MAAsC;AACpE,UAAM,UAAU,gBAAgB,KAAK,GAAG,IAAI,MAAM,GAAG,KAAK,WAAW,GAAG,GAAG;AAC3E,UAAM,UAAU,KAAK,UAAU,OAAO,YAAY;AAElD,UAAM,QAAQ,MAAM,kBAAkB,KAAK,QAAQ;AAAA,MACjD,YAAY,KAAK;AAAA,MACjB;AAAA,MACA,KAAK;AAAA,MACL,aAAa,KAAK;AAAA,MAClB,OAAO,KAAK;AAAA,IACd,CAAC;AAED,UAAM,UAAU,IAAI,QAAQ,KAAK,OAAO;AACxC,YAAQ,IAAI,iBAAiB,QAAQ,KAAK,WAAW,EAAE;AACvD,YAAQ,IAAI,QAAQ,KAAK;AACzB,YAAQ,IAAI,gBAAgB,KAAK,SAAS;AAE1C,UAAM,WAAW,MAAM,KAAK,OAAO,SAAS;AAAA,MAC1C,GAAG;AAAA,MACH;AAAA,IACF,CAAC;AAED,UAAM,QAAQ,SAAS,QAAQ,IAAI,YAAY;AAC/C,QAAI,OAAO;AACT,WAAK,YAAY;AAAA,IACnB;AAEA,WAAO;AAAA,EACT;AACF;;;AC7DO,IAAM,gBAAN,MAA8C;AAAA,EAC3C,OAAO,oBAAI,IAAoB;AAAA,EAEvC,MAAM,IAAI,KAAqC;AAC7C,WAAO,KAAK,KAAK,IAAI,GAAG,KAAK;AAAA,EAC/B;AAAA,EAEA,MAAM,IAAI,KAAa,OAA8B;AACnD,SAAK,KAAK,IAAI,KAAK,KAAK;AAAA,EAC1B;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,SAAK,KAAK,OAAO,GAAG;AAAA,EACtB;AACF;;;ACHA,IAAM,iBAAiB;AACvB,IAAM,kBAAkB;AAEjB,IAAM,uBAAN,MAA2B;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACF;AAAA,EACA;AAAA,EAEjB,YACE,SAGA;AACA,SAAK,cAAc,QAAQ,YAAY,QAAQ,QAAQ,EAAE;AACzD,SAAK,YAAY,QAAQ;AACzB,SAAK,eAAe,QAAQ;AAC5B,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ,WAAW,IAAI,cAAc;AACpD,SAAK,SAAS,QAAQ,SAAS,WAAW;AAAA,EAC5C;AAAA,EAEA,IAAI,iBAA0B;AAC5B,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AAAA,EAEA,MAAM,mBAIH;AACD,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,gBAAgB,KAAK;AAAA,IACvB;AACA,QAAI,KAAK,cAAc;AACrB,cAAQ,iBAAiB,IAAI,KAAK;AAAA,IACpC;AAEA,UAAM,OAAgC,CAAC;AACvC,QAAI;AAEJ,QAAI,CAAC,KAAK,gBAAgB;AACxB,qBAAe,MAAM,KAAK,OAAO,qBAAqB;AACtD,YAAM,YAAY,MAAM,KAAK,OAAO,qBAAqB,YAAY;AACrE,WAAK,iBAAiB;AAAA,IACxB;AAEA,UAAM,OAAO,MAAM,KAAK,OAAO,GAAG,KAAK,WAAW,oBAAoB;AAAA,MACpE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAED,QAAI,CAAC,KAAK,IAAI;AACZ,YAAMA,QAAO,MAAM,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC/C,YAAM,IAAI;AAAA,QACR,iCAAiC,KAAK,MAAM,IAAKA,MAAa,WAAW,KAAK,UAAU;AAAA,QACxF,KAAK;AAAA,QACLA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAA6B,MAAM,KAAK,KAAK;AACnD,WAAO;AAAA,MACL,aAAa,KAAK;AAAA,MAClB,SAAS,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,QAC2B;AAC3B,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,gBAAgB,KAAK;AAAA,IACvB;AACA,QAAI,KAAK,cAAc;AACrB,cAAQ,iBAAiB,IAAI,KAAK;AAAA,IACpC;AAEA,UAAM,OAAgC;AAAA,MACpC,cAAc,OAAO;AAAA,MACrB,KAAK,OAAO;AAAA,MACZ,cAAc,OAAO;AAAA,MACrB,eAAe,OAAO;AAAA,MACtB,QAAQ,OAAO;AAAA,MACf,SAAS,OAAO;AAAA,MAChB,QAAQ,OAAO;AAAA,IACjB;AAEA,QAAI,CAAC,KAAK,kBAAkB,OAAO,cAAc;AAC/C,WAAK,gBAAgB,OAAO;AAAA,IAC9B;AAEA,UAAM,OAAO,MAAM,KAAK,OAAO,GAAG,KAAK,WAAW,mBAAmB;AAAA,MACnE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAED,QAAI,CAAC,KAAK,IAAI;AACZ,YAAMA,QAAO,MAAM,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC/C,YAAM,IAAI;AAAA,QACR,+BAA+B,KAAK,MAAM,IAAKA,MAAa,WAAW,KAAK,UAAU;AAAA,QACtF,KAAK;AAAA,QACLA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAAgC,MAAM,KAAK,KAAK;AAEtD,UAAM,gBAA+B;AAAA,MACnC,KAAK,KAAK;AAAA,MACV,SAAS,OAAO;AAAA,MAChB,aAAa,OAAO;AAAA,MACpB,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,IACpB;AACA,UAAM,KAAK,QAAQ;AAAA,MACjB,GAAG,cAAc,GAAG,KAAK,GAAG;AAAA,MAC5B,KAAK,UAAU,aAAa;AAAA,IAC9B;AACA,UAAM,KAAK,QAAQ,IAAI,iBAAiB,KAAK,GAAG;AAEhD,WAAO,IAAI,iBAAiB;AAAA,MAC1B,KAAK,KAAK;AAAA,MACV,SAAS,OAAO;AAAA,MAChB,aAAa,OAAO;AAAA,MACpB,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB,QAAQ,KAAK;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,cAAc,KAA4B;AAC9C,UAAM,UAAkC;AAAA,MACtC,gBAAgB,KAAK;AAAA,IACvB;AACA,QAAI,KAAK,cAAc;AACrB,cAAQ,iBAAiB,IAAI,KAAK;AAAA,IACpC;AAEA,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB,GAAG,KAAK,WAAW,mBAAmB,GAAG;AAAA,MACzC;AAAA,QACE,QAAQ;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,MAAM,KAAK,WAAW,KAAK;AACnC,YAAM,OAAO,MAAM,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC/C,YAAM,IAAI;AAAA,QACR,6BAA6B,KAAK,MAAM,IAAK,KAAa,WAAW,KAAK,UAAU;AAAA,QACpF,KAAK;AAAA,QACL;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK,QAAQ,OAAO,GAAG,cAAc,GAAG,GAAG,EAAE;AAEnD,UAAM,aAAa,MAAM,KAAK,QAAQ,IAAI,eAAe;AACzD,QAAI,eAAe,KAAK;AACtB,YAAM,KAAK,QAAQ,OAAO,eAAe;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,KAA+C;AAClE,UAAM,SAAS,MAAM,KAAK,QAAQ,IAAI,GAAG,cAAc,GAAG,GAAG,EAAE;AAC/D,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,OAAsB,KAAK,MAAM,MAAM;AAC7C,WAAO,IAAI,iBAAiB;AAAA,MAC1B,KAAK,KAAK;AAAA,MACV,SAAS,KAAK;AAAA,MACd,aAAa,KAAK;AAAA,MAClB,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB,QAAQ,KAAK;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAA4C;AAChD,UAAM,MAAM,MAAM,KAAK,QAAQ,IAAI,eAAe;AAClD,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO,KAAK,eAAe,GAAG;AAAA,EAChC;AACF;","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:\";\nexport const 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.1.0-dev.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",
|