@happyview/oauth-client 1.0.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +365 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +142 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +328 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ApiError: () => ApiError,
|
|
24
|
+
AuthenticationError: () => AuthenticationError,
|
|
25
|
+
HappyViewError: () => HappyViewError,
|
|
26
|
+
HappyViewOAuthClient: () => HappyViewOAuthClient,
|
|
27
|
+
HappyViewSession: () => HappyViewSession,
|
|
28
|
+
InvalidStateError: () => InvalidStateError,
|
|
29
|
+
MemoryStorage: () => MemoryStorage,
|
|
30
|
+
ResolutionError: () => ResolutionError,
|
|
31
|
+
TokenExchangeError: () => TokenExchangeError,
|
|
32
|
+
base64urlEncode: () => base64urlEncode,
|
|
33
|
+
generateDpopProof: () => generateDpopProof
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/errors.ts
|
|
38
|
+
var HappyViewError = class extends Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "HappyViewError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var ApiError = class extends HappyViewError {
|
|
45
|
+
status;
|
|
46
|
+
body;
|
|
47
|
+
constructor(message, status, body) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "ApiError";
|
|
50
|
+
this.status = status;
|
|
51
|
+
this.body = body;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var AuthenticationError = class extends HappyViewError {
|
|
55
|
+
status;
|
|
56
|
+
constructor(message, status = 401) {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = "AuthenticationError";
|
|
59
|
+
this.status = status;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var InvalidStateError = class extends HappyViewError {
|
|
63
|
+
constructor(message) {
|
|
64
|
+
super(message);
|
|
65
|
+
this.name = "InvalidStateError";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var TokenExchangeError = class extends HappyViewError {
|
|
69
|
+
status;
|
|
70
|
+
body;
|
|
71
|
+
constructor(message, status, body) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.name = "TokenExchangeError";
|
|
74
|
+
this.status = status;
|
|
75
|
+
this.body = body;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var ResolutionError = class extends HappyViewError {
|
|
79
|
+
constructor(message) {
|
|
80
|
+
super(message);
|
|
81
|
+
this.name = "ResolutionError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/dpop-proof.ts
|
|
86
|
+
function base64urlEncode(data) {
|
|
87
|
+
let binary = "";
|
|
88
|
+
for (let i = 0; i < data.length; i++) {
|
|
89
|
+
binary += String.fromCharCode(data[i]);
|
|
90
|
+
}
|
|
91
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
92
|
+
}
|
|
93
|
+
async function generateDpopProof(crypto, params) {
|
|
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))
|
|
124
|
+
);
|
|
125
|
+
const payloadB64 = base64urlEncode(
|
|
126
|
+
new TextEncoder().encode(JSON.stringify(payload))
|
|
127
|
+
);
|
|
128
|
+
const message = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
129
|
+
const signature = await crypto.signEs256(privateKey, message);
|
|
130
|
+
const sigB64 = base64urlEncode(signature);
|
|
131
|
+
return `${headerB64}.${payloadB64}.${sigB64}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/session.ts
|
|
135
|
+
var HappyViewSession = class {
|
|
136
|
+
did;
|
|
137
|
+
dpopKey;
|
|
138
|
+
accessToken;
|
|
139
|
+
clientKey;
|
|
140
|
+
instanceUrl;
|
|
141
|
+
crypto;
|
|
142
|
+
_fetch;
|
|
143
|
+
dpopNonce;
|
|
144
|
+
constructor(options) {
|
|
145
|
+
this.did = options.did;
|
|
146
|
+
this.dpopKey = options.dpopKey;
|
|
147
|
+
this.accessToken = options.accessToken;
|
|
148
|
+
this.clientKey = options.clientKey;
|
|
149
|
+
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
150
|
+
this.crypto = options.crypto;
|
|
151
|
+
this._fetch = options.fetch ?? globalThis.fetch;
|
|
152
|
+
}
|
|
153
|
+
async fetchHandler(url, init) {
|
|
154
|
+
const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.instanceUrl}${url}`;
|
|
155
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
156
|
+
const proof = await generateDpopProof(this.crypto, {
|
|
157
|
+
privateKey: this.dpopKey,
|
|
158
|
+
method,
|
|
159
|
+
url: fullUrl,
|
|
160
|
+
accessToken: this.accessToken,
|
|
161
|
+
nonce: this.dpopNonce
|
|
162
|
+
});
|
|
163
|
+
const headers = new Headers(init.headers);
|
|
164
|
+
headers.set("authorization", `DPoP ${this.accessToken}`);
|
|
165
|
+
headers.set("dpop", proof);
|
|
166
|
+
headers.set("x-client-key", this.clientKey);
|
|
167
|
+
const response = await this._fetch(fullUrl, {
|
|
168
|
+
...init,
|
|
169
|
+
headers
|
|
170
|
+
});
|
|
171
|
+
const nonce = response.headers.get("dpop-nonce");
|
|
172
|
+
if (nonce) {
|
|
173
|
+
this.dpopNonce = nonce;
|
|
174
|
+
}
|
|
175
|
+
return response;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// src/storage.ts
|
|
180
|
+
var MemoryStorage = class {
|
|
181
|
+
data = /* @__PURE__ */ new Map();
|
|
182
|
+
async get(key) {
|
|
183
|
+
return this.data.get(key) ?? null;
|
|
184
|
+
}
|
|
185
|
+
async set(key, value) {
|
|
186
|
+
this.data.set(key, value);
|
|
187
|
+
}
|
|
188
|
+
async delete(key) {
|
|
189
|
+
this.data.delete(key);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/client.ts
|
|
194
|
+
var STORAGE_PREFIX = "happyview:session:";
|
|
195
|
+
var LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
196
|
+
var HappyViewOAuthClient = class {
|
|
197
|
+
instanceUrl;
|
|
198
|
+
clientKey;
|
|
199
|
+
crypto;
|
|
200
|
+
storage;
|
|
201
|
+
clientSecret;
|
|
202
|
+
_fetch;
|
|
203
|
+
constructor(options) {
|
|
204
|
+
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
205
|
+
this.clientKey = options.clientKey;
|
|
206
|
+
this.clientSecret = options.clientSecret;
|
|
207
|
+
this.crypto = options.crypto;
|
|
208
|
+
this.storage = options.storage ?? new MemoryStorage();
|
|
209
|
+
this._fetch = options.fetch ?? globalThis.fetch;
|
|
210
|
+
}
|
|
211
|
+
get isConfidential() {
|
|
212
|
+
return this.clientSecret !== void 0;
|
|
213
|
+
}
|
|
214
|
+
async provisionDpopKey() {
|
|
215
|
+
const headers = {
|
|
216
|
+
"content-type": "application/json",
|
|
217
|
+
"x-client-key": this.clientKey
|
|
218
|
+
};
|
|
219
|
+
if (this.clientSecret) {
|
|
220
|
+
headers["x-client-secret"] = this.clientSecret;
|
|
221
|
+
}
|
|
222
|
+
const body = {};
|
|
223
|
+
let pkceVerifier;
|
|
224
|
+
if (!this.isConfidential) {
|
|
225
|
+
pkceVerifier = await this.crypto.generatePkceVerifier();
|
|
226
|
+
const challenge = await this.crypto.computePkceChallenge(pkceVerifier);
|
|
227
|
+
body.pkce_challenge = challenge;
|
|
228
|
+
}
|
|
229
|
+
const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers,
|
|
232
|
+
body: JSON.stringify(body)
|
|
233
|
+
});
|
|
234
|
+
if (!resp.ok) {
|
|
235
|
+
const body2 = await resp.json().catch(() => ({}));
|
|
236
|
+
throw new ApiError(
|
|
237
|
+
`Failed to provision DPoP key: ${resp.status} ${body2.message ?? resp.statusText}`,
|
|
238
|
+
resp.status,
|
|
239
|
+
body2
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
const data = await resp.json();
|
|
243
|
+
return {
|
|
244
|
+
provisionId: data.provision_id,
|
|
245
|
+
dpopKey: data.dpop_key,
|
|
246
|
+
pkceVerifier
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
async registerSession(params) {
|
|
250
|
+
const headers = {
|
|
251
|
+
"content-type": "application/json",
|
|
252
|
+
"x-client-key": this.clientKey
|
|
253
|
+
};
|
|
254
|
+
if (this.clientSecret) {
|
|
255
|
+
headers["x-client-secret"] = this.clientSecret;
|
|
256
|
+
}
|
|
257
|
+
const body = {
|
|
258
|
+
provision_id: params.provisionId,
|
|
259
|
+
did: params.did,
|
|
260
|
+
access_token: params.accessToken,
|
|
261
|
+
refresh_token: params.refreshToken,
|
|
262
|
+
scopes: params.scopes,
|
|
263
|
+
pds_url: params.pdsUrl,
|
|
264
|
+
issuer: params.issuer
|
|
265
|
+
};
|
|
266
|
+
if (!this.isConfidential && params.pkceVerifier) {
|
|
267
|
+
body.pkce_verifier = params.pkceVerifier;
|
|
268
|
+
}
|
|
269
|
+
const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers,
|
|
272
|
+
body: JSON.stringify(body)
|
|
273
|
+
});
|
|
274
|
+
if (!resp.ok) {
|
|
275
|
+
const body2 = await resp.json().catch(() => ({}));
|
|
276
|
+
throw new ApiError(
|
|
277
|
+
`Failed to register session: ${resp.status} ${body2.message ?? resp.statusText}`,
|
|
278
|
+
resp.status,
|
|
279
|
+
body2
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const data = await resp.json();
|
|
283
|
+
const storedSession = {
|
|
284
|
+
did: data.did,
|
|
285
|
+
dpopKey: params.dpopKey,
|
|
286
|
+
accessToken: params.accessToken,
|
|
287
|
+
clientKey: this.clientKey,
|
|
288
|
+
instanceUrl: this.instanceUrl
|
|
289
|
+
};
|
|
290
|
+
await this.storage.set(
|
|
291
|
+
`${STORAGE_PREFIX}${data.did}`,
|
|
292
|
+
JSON.stringify(storedSession)
|
|
293
|
+
);
|
|
294
|
+
await this.storage.set(LAST_ACTIVE_KEY, data.did);
|
|
295
|
+
return new HappyViewSession({
|
|
296
|
+
did: data.did,
|
|
297
|
+
dpopKey: params.dpopKey,
|
|
298
|
+
accessToken: params.accessToken,
|
|
299
|
+
clientKey: this.clientKey,
|
|
300
|
+
instanceUrl: this.instanceUrl,
|
|
301
|
+
crypto: this.crypto
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
async deleteSession(did) {
|
|
305
|
+
const headers = {
|
|
306
|
+
"x-client-key": this.clientKey
|
|
307
|
+
};
|
|
308
|
+
if (this.clientSecret) {
|
|
309
|
+
headers["x-client-secret"] = this.clientSecret;
|
|
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
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
await this.storage.delete(`${STORAGE_PREFIX}${did}`);
|
|
327
|
+
const lastActive = await this.storage.get(LAST_ACTIVE_KEY);
|
|
328
|
+
if (lastActive === did) {
|
|
329
|
+
await this.storage.delete(LAST_ACTIVE_KEY);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async restoreSession(did) {
|
|
333
|
+
const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);
|
|
334
|
+
if (!stored) return null;
|
|
335
|
+
const data = JSON.parse(stored);
|
|
336
|
+
return new HappyViewSession({
|
|
337
|
+
did: data.did,
|
|
338
|
+
dpopKey: data.dpopKey,
|
|
339
|
+
accessToken: data.accessToken,
|
|
340
|
+
clientKey: data.clientKey,
|
|
341
|
+
instanceUrl: data.instanceUrl,
|
|
342
|
+
crypto: this.crypto
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
async restore() {
|
|
346
|
+
const did = await this.storage.get(LAST_ACTIVE_KEY);
|
|
347
|
+
if (!did) return null;
|
|
348
|
+
return this.restoreSession(did);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
352
|
+
0 && (module.exports = {
|
|
353
|
+
ApiError,
|
|
354
|
+
AuthenticationError,
|
|
355
|
+
HappyViewError,
|
|
356
|
+
HappyViewOAuthClient,
|
|
357
|
+
HappyViewSession,
|
|
358
|
+
InvalidStateError,
|
|
359
|
+
MemoryStorage,
|
|
360
|
+
ResolutionError,
|
|
361
|
+
TokenExchangeError,
|
|
362
|
+
base64urlEncode,
|
|
363
|
+
generateDpopProof
|
|
364
|
+
});
|
|
365
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
interface CryptoAdapter {
|
|
2
|
+
generatePkceVerifier(): Promise<string>;
|
|
3
|
+
computePkceChallenge(verifier: string): Promise<string>;
|
|
4
|
+
signEs256(privateKey: JsonWebKey, payload: Uint8Array): Promise<Uint8Array>;
|
|
5
|
+
sha256(data: Uint8Array): Promise<Uint8Array>;
|
|
6
|
+
getRandomValues(length: number): Uint8Array;
|
|
7
|
+
}
|
|
8
|
+
interface StorageAdapter {
|
|
9
|
+
get(key: string): Promise<string | null>;
|
|
10
|
+
set(key: string, value: string): Promise<void>;
|
|
11
|
+
delete(key: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
interface HappyViewOAuthClientOptions {
|
|
14
|
+
instanceUrl: string;
|
|
15
|
+
clientKey: string;
|
|
16
|
+
clientSecret?: string;
|
|
17
|
+
crypto: CryptoAdapter;
|
|
18
|
+
storage?: StorageAdapter;
|
|
19
|
+
}
|
|
20
|
+
interface DpopProvision {
|
|
21
|
+
provisionId: string;
|
|
22
|
+
dpopKey: JsonWebKey;
|
|
23
|
+
}
|
|
24
|
+
interface RegisterSessionParams {
|
|
25
|
+
provisionId: string;
|
|
26
|
+
pkceVerifier?: string;
|
|
27
|
+
did: string;
|
|
28
|
+
accessToken: string;
|
|
29
|
+
refreshToken?: string;
|
|
30
|
+
scopes: string;
|
|
31
|
+
pdsUrl?: string;
|
|
32
|
+
issuer?: string;
|
|
33
|
+
dpopKey: JsonWebKey;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Session data persisted to storage. Note: tokens and keys are stored as-is
|
|
37
|
+
* via the StorageAdapter. In browser environments using LocalStorageAdapter,
|
|
38
|
+
* this means they are accessible to any JS on the same origin. Consider using
|
|
39
|
+
* a StorageAdapter with encryption if XSS is a concern for your application.
|
|
40
|
+
*/
|
|
41
|
+
interface StoredSession {
|
|
42
|
+
did: string;
|
|
43
|
+
dpopKey: JsonWebKey;
|
|
44
|
+
accessToken: string;
|
|
45
|
+
clientKey: string;
|
|
46
|
+
instanceUrl: string;
|
|
47
|
+
}
|
|
48
|
+
interface ProvisionKeyResponse {
|
|
49
|
+
provision_id: string;
|
|
50
|
+
dpop_key: JsonWebKey;
|
|
51
|
+
}
|
|
52
|
+
interface RegisterSessionResponse {
|
|
53
|
+
session_id: string;
|
|
54
|
+
did: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface HappyViewSessionOptions {
|
|
58
|
+
did: string;
|
|
59
|
+
dpopKey: JsonWebKey;
|
|
60
|
+
accessToken: string;
|
|
61
|
+
clientKey: string;
|
|
62
|
+
instanceUrl: string;
|
|
63
|
+
crypto: CryptoAdapter;
|
|
64
|
+
fetch?: typeof globalThis.fetch;
|
|
65
|
+
}
|
|
66
|
+
declare class HappyViewSession {
|
|
67
|
+
readonly did: string;
|
|
68
|
+
private readonly dpopKey;
|
|
69
|
+
private readonly accessToken;
|
|
70
|
+
private readonly clientKey;
|
|
71
|
+
private readonly instanceUrl;
|
|
72
|
+
private readonly crypto;
|
|
73
|
+
private readonly _fetch;
|
|
74
|
+
private dpopNonce;
|
|
75
|
+
constructor(options: HappyViewSessionOptions);
|
|
76
|
+
fetchHandler(url: string, init: RequestInit): Promise<Response>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare class HappyViewOAuthClient {
|
|
80
|
+
protected readonly instanceUrl: string;
|
|
81
|
+
protected readonly clientKey: string;
|
|
82
|
+
protected readonly crypto: CryptoAdapter;
|
|
83
|
+
protected readonly storage: StorageAdapter;
|
|
84
|
+
private readonly clientSecret;
|
|
85
|
+
private readonly _fetch;
|
|
86
|
+
constructor(options: HappyViewOAuthClientOptions & {
|
|
87
|
+
fetch?: typeof globalThis.fetch;
|
|
88
|
+
});
|
|
89
|
+
get isConfidential(): boolean;
|
|
90
|
+
provisionDpopKey(): Promise<{
|
|
91
|
+
provisionId: string;
|
|
92
|
+
dpopKey: JsonWebKey;
|
|
93
|
+
pkceVerifier?: string;
|
|
94
|
+
}>;
|
|
95
|
+
registerSession(params: RegisterSessionParams): Promise<HappyViewSession>;
|
|
96
|
+
deleteSession(did: string): Promise<void>;
|
|
97
|
+
restoreSession(did: string): Promise<HappyViewSession | null>;
|
|
98
|
+
restore(): Promise<HappyViewSession | null>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
declare function base64urlEncode(data: Uint8Array): string;
|
|
102
|
+
interface DpopProofParams {
|
|
103
|
+
privateKey: JsonWebKey;
|
|
104
|
+
method: string;
|
|
105
|
+
url: string;
|
|
106
|
+
accessToken?: string;
|
|
107
|
+
nonce?: string;
|
|
108
|
+
}
|
|
109
|
+
declare function generateDpopProof(crypto: CryptoAdapter, params: DpopProofParams): Promise<string>;
|
|
110
|
+
|
|
111
|
+
declare class HappyViewError extends Error {
|
|
112
|
+
constructor(message: string);
|
|
113
|
+
}
|
|
114
|
+
declare class ApiError extends HappyViewError {
|
|
115
|
+
readonly status: number;
|
|
116
|
+
readonly body: unknown;
|
|
117
|
+
constructor(message: string, status: number, body?: unknown);
|
|
118
|
+
}
|
|
119
|
+
declare class AuthenticationError extends HappyViewError {
|
|
120
|
+
readonly status: number;
|
|
121
|
+
constructor(message: string, status?: number);
|
|
122
|
+
}
|
|
123
|
+
declare class InvalidStateError extends HappyViewError {
|
|
124
|
+
constructor(message: string);
|
|
125
|
+
}
|
|
126
|
+
declare class TokenExchangeError extends HappyViewError {
|
|
127
|
+
readonly status: number;
|
|
128
|
+
readonly body: string;
|
|
129
|
+
constructor(message: string, status: number, body: string);
|
|
130
|
+
}
|
|
131
|
+
declare class ResolutionError extends HappyViewError {
|
|
132
|
+
constructor(message: string);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
declare class MemoryStorage implements StorageAdapter {
|
|
136
|
+
private data;
|
|
137
|
+
get(key: string): Promise<string | null>;
|
|
138
|
+
set(key: string, value: string): Promise<void>;
|
|
139
|
+
delete(key: string): Promise<void>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { ApiError, AuthenticationError, type CryptoAdapter, type DpopProofParams, type DpopProvision, HappyViewError, HappyViewOAuthClient, type HappyViewOAuthClientOptions, HappyViewSession, type HappyViewSessionOptions, InvalidStateError, MemoryStorage, type ProvisionKeyResponse, type RegisterSessionParams, type RegisterSessionResponse, ResolutionError, type StorageAdapter, type StoredSession, TokenExchangeError, base64urlEncode, generateDpopProof };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
interface CryptoAdapter {
|
|
2
|
+
generatePkceVerifier(): Promise<string>;
|
|
3
|
+
computePkceChallenge(verifier: string): Promise<string>;
|
|
4
|
+
signEs256(privateKey: JsonWebKey, payload: Uint8Array): Promise<Uint8Array>;
|
|
5
|
+
sha256(data: Uint8Array): Promise<Uint8Array>;
|
|
6
|
+
getRandomValues(length: number): Uint8Array;
|
|
7
|
+
}
|
|
8
|
+
interface StorageAdapter {
|
|
9
|
+
get(key: string): Promise<string | null>;
|
|
10
|
+
set(key: string, value: string): Promise<void>;
|
|
11
|
+
delete(key: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
interface HappyViewOAuthClientOptions {
|
|
14
|
+
instanceUrl: string;
|
|
15
|
+
clientKey: string;
|
|
16
|
+
clientSecret?: string;
|
|
17
|
+
crypto: CryptoAdapter;
|
|
18
|
+
storage?: StorageAdapter;
|
|
19
|
+
}
|
|
20
|
+
interface DpopProvision {
|
|
21
|
+
provisionId: string;
|
|
22
|
+
dpopKey: JsonWebKey;
|
|
23
|
+
}
|
|
24
|
+
interface RegisterSessionParams {
|
|
25
|
+
provisionId: string;
|
|
26
|
+
pkceVerifier?: string;
|
|
27
|
+
did: string;
|
|
28
|
+
accessToken: string;
|
|
29
|
+
refreshToken?: string;
|
|
30
|
+
scopes: string;
|
|
31
|
+
pdsUrl?: string;
|
|
32
|
+
issuer?: string;
|
|
33
|
+
dpopKey: JsonWebKey;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Session data persisted to storage. Note: tokens and keys are stored as-is
|
|
37
|
+
* via the StorageAdapter. In browser environments using LocalStorageAdapter,
|
|
38
|
+
* this means they are accessible to any JS on the same origin. Consider using
|
|
39
|
+
* a StorageAdapter with encryption if XSS is a concern for your application.
|
|
40
|
+
*/
|
|
41
|
+
interface StoredSession {
|
|
42
|
+
did: string;
|
|
43
|
+
dpopKey: JsonWebKey;
|
|
44
|
+
accessToken: string;
|
|
45
|
+
clientKey: string;
|
|
46
|
+
instanceUrl: string;
|
|
47
|
+
}
|
|
48
|
+
interface ProvisionKeyResponse {
|
|
49
|
+
provision_id: string;
|
|
50
|
+
dpop_key: JsonWebKey;
|
|
51
|
+
}
|
|
52
|
+
interface RegisterSessionResponse {
|
|
53
|
+
session_id: string;
|
|
54
|
+
did: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface HappyViewSessionOptions {
|
|
58
|
+
did: string;
|
|
59
|
+
dpopKey: JsonWebKey;
|
|
60
|
+
accessToken: string;
|
|
61
|
+
clientKey: string;
|
|
62
|
+
instanceUrl: string;
|
|
63
|
+
crypto: CryptoAdapter;
|
|
64
|
+
fetch?: typeof globalThis.fetch;
|
|
65
|
+
}
|
|
66
|
+
declare class HappyViewSession {
|
|
67
|
+
readonly did: string;
|
|
68
|
+
private readonly dpopKey;
|
|
69
|
+
private readonly accessToken;
|
|
70
|
+
private readonly clientKey;
|
|
71
|
+
private readonly instanceUrl;
|
|
72
|
+
private readonly crypto;
|
|
73
|
+
private readonly _fetch;
|
|
74
|
+
private dpopNonce;
|
|
75
|
+
constructor(options: HappyViewSessionOptions);
|
|
76
|
+
fetchHandler(url: string, init: RequestInit): Promise<Response>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare class HappyViewOAuthClient {
|
|
80
|
+
protected readonly instanceUrl: string;
|
|
81
|
+
protected readonly clientKey: string;
|
|
82
|
+
protected readonly crypto: CryptoAdapter;
|
|
83
|
+
protected readonly storage: StorageAdapter;
|
|
84
|
+
private readonly clientSecret;
|
|
85
|
+
private readonly _fetch;
|
|
86
|
+
constructor(options: HappyViewOAuthClientOptions & {
|
|
87
|
+
fetch?: typeof globalThis.fetch;
|
|
88
|
+
});
|
|
89
|
+
get isConfidential(): boolean;
|
|
90
|
+
provisionDpopKey(): Promise<{
|
|
91
|
+
provisionId: string;
|
|
92
|
+
dpopKey: JsonWebKey;
|
|
93
|
+
pkceVerifier?: string;
|
|
94
|
+
}>;
|
|
95
|
+
registerSession(params: RegisterSessionParams): Promise<HappyViewSession>;
|
|
96
|
+
deleteSession(did: string): Promise<void>;
|
|
97
|
+
restoreSession(did: string): Promise<HappyViewSession | null>;
|
|
98
|
+
restore(): Promise<HappyViewSession | null>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
declare function base64urlEncode(data: Uint8Array): string;
|
|
102
|
+
interface DpopProofParams {
|
|
103
|
+
privateKey: JsonWebKey;
|
|
104
|
+
method: string;
|
|
105
|
+
url: string;
|
|
106
|
+
accessToken?: string;
|
|
107
|
+
nonce?: string;
|
|
108
|
+
}
|
|
109
|
+
declare function generateDpopProof(crypto: CryptoAdapter, params: DpopProofParams): Promise<string>;
|
|
110
|
+
|
|
111
|
+
declare class HappyViewError extends Error {
|
|
112
|
+
constructor(message: string);
|
|
113
|
+
}
|
|
114
|
+
declare class ApiError extends HappyViewError {
|
|
115
|
+
readonly status: number;
|
|
116
|
+
readonly body: unknown;
|
|
117
|
+
constructor(message: string, status: number, body?: unknown);
|
|
118
|
+
}
|
|
119
|
+
declare class AuthenticationError extends HappyViewError {
|
|
120
|
+
readonly status: number;
|
|
121
|
+
constructor(message: string, status?: number);
|
|
122
|
+
}
|
|
123
|
+
declare class InvalidStateError extends HappyViewError {
|
|
124
|
+
constructor(message: string);
|
|
125
|
+
}
|
|
126
|
+
declare class TokenExchangeError extends HappyViewError {
|
|
127
|
+
readonly status: number;
|
|
128
|
+
readonly body: string;
|
|
129
|
+
constructor(message: string, status: number, body: string);
|
|
130
|
+
}
|
|
131
|
+
declare class ResolutionError extends HappyViewError {
|
|
132
|
+
constructor(message: string);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
declare class MemoryStorage implements StorageAdapter {
|
|
136
|
+
private data;
|
|
137
|
+
get(key: string): Promise<string | null>;
|
|
138
|
+
set(key: string, value: string): Promise<void>;
|
|
139
|
+
delete(key: string): Promise<void>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { ApiError, AuthenticationError, type CryptoAdapter, type DpopProofParams, type DpopProvision, HappyViewError, HappyViewOAuthClient, type HappyViewOAuthClientOptions, HappyViewSession, type HappyViewSessionOptions, InvalidStateError, MemoryStorage, type ProvisionKeyResponse, type RegisterSessionParams, type RegisterSessionResponse, ResolutionError, type StorageAdapter, type StoredSession, TokenExchangeError, base64urlEncode, generateDpopProof };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var HappyViewError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "HappyViewError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var ApiError = class extends HappyViewError {
|
|
9
|
+
status;
|
|
10
|
+
body;
|
|
11
|
+
constructor(message, status, body) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "ApiError";
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.body = body;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var AuthenticationError = class extends HappyViewError {
|
|
19
|
+
status;
|
|
20
|
+
constructor(message, status = 401) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "AuthenticationError";
|
|
23
|
+
this.status = status;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var InvalidStateError = class extends HappyViewError {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "InvalidStateError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var TokenExchangeError = class extends HappyViewError {
|
|
33
|
+
status;
|
|
34
|
+
body;
|
|
35
|
+
constructor(message, status, body) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "TokenExchangeError";
|
|
38
|
+
this.status = status;
|
|
39
|
+
this.body = body;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var ResolutionError = class extends HappyViewError {
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "ResolutionError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/dpop-proof.ts
|
|
50
|
+
function base64urlEncode(data) {
|
|
51
|
+
let binary = "";
|
|
52
|
+
for (let i = 0; i < data.length; i++) {
|
|
53
|
+
binary += String.fromCharCode(data[i]);
|
|
54
|
+
}
|
|
55
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
56
|
+
}
|
|
57
|
+
async function generateDpopProof(crypto, params) {
|
|
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))
|
|
88
|
+
);
|
|
89
|
+
const payloadB64 = base64urlEncode(
|
|
90
|
+
new TextEncoder().encode(JSON.stringify(payload))
|
|
91
|
+
);
|
|
92
|
+
const message = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
93
|
+
const signature = await crypto.signEs256(privateKey, message);
|
|
94
|
+
const sigB64 = base64urlEncode(signature);
|
|
95
|
+
return `${headerB64}.${payloadB64}.${sigB64}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/session.ts
|
|
99
|
+
var HappyViewSession = class {
|
|
100
|
+
did;
|
|
101
|
+
dpopKey;
|
|
102
|
+
accessToken;
|
|
103
|
+
clientKey;
|
|
104
|
+
instanceUrl;
|
|
105
|
+
crypto;
|
|
106
|
+
_fetch;
|
|
107
|
+
dpopNonce;
|
|
108
|
+
constructor(options) {
|
|
109
|
+
this.did = options.did;
|
|
110
|
+
this.dpopKey = options.dpopKey;
|
|
111
|
+
this.accessToken = options.accessToken;
|
|
112
|
+
this.clientKey = options.clientKey;
|
|
113
|
+
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
114
|
+
this.crypto = options.crypto;
|
|
115
|
+
this._fetch = options.fetch ?? globalThis.fetch;
|
|
116
|
+
}
|
|
117
|
+
async fetchHandler(url, init) {
|
|
118
|
+
const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.instanceUrl}${url}`;
|
|
119
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
120
|
+
const proof = await generateDpopProof(this.crypto, {
|
|
121
|
+
privateKey: this.dpopKey,
|
|
122
|
+
method,
|
|
123
|
+
url: fullUrl,
|
|
124
|
+
accessToken: this.accessToken,
|
|
125
|
+
nonce: this.dpopNonce
|
|
126
|
+
});
|
|
127
|
+
const headers = new Headers(init.headers);
|
|
128
|
+
headers.set("authorization", `DPoP ${this.accessToken}`);
|
|
129
|
+
headers.set("dpop", proof);
|
|
130
|
+
headers.set("x-client-key", this.clientKey);
|
|
131
|
+
const response = await this._fetch(fullUrl, {
|
|
132
|
+
...init,
|
|
133
|
+
headers
|
|
134
|
+
});
|
|
135
|
+
const nonce = response.headers.get("dpop-nonce");
|
|
136
|
+
if (nonce) {
|
|
137
|
+
this.dpopNonce = nonce;
|
|
138
|
+
}
|
|
139
|
+
return response;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/storage.ts
|
|
144
|
+
var MemoryStorage = class {
|
|
145
|
+
data = /* @__PURE__ */ new Map();
|
|
146
|
+
async get(key) {
|
|
147
|
+
return this.data.get(key) ?? null;
|
|
148
|
+
}
|
|
149
|
+
async set(key, value) {
|
|
150
|
+
this.data.set(key, value);
|
|
151
|
+
}
|
|
152
|
+
async delete(key) {
|
|
153
|
+
this.data.delete(key);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// src/client.ts
|
|
158
|
+
var STORAGE_PREFIX = "happyview:session:";
|
|
159
|
+
var LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
160
|
+
var HappyViewOAuthClient = class {
|
|
161
|
+
instanceUrl;
|
|
162
|
+
clientKey;
|
|
163
|
+
crypto;
|
|
164
|
+
storage;
|
|
165
|
+
clientSecret;
|
|
166
|
+
_fetch;
|
|
167
|
+
constructor(options) {
|
|
168
|
+
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
169
|
+
this.clientKey = options.clientKey;
|
|
170
|
+
this.clientSecret = options.clientSecret;
|
|
171
|
+
this.crypto = options.crypto;
|
|
172
|
+
this.storage = options.storage ?? new MemoryStorage();
|
|
173
|
+
this._fetch = options.fetch ?? globalThis.fetch;
|
|
174
|
+
}
|
|
175
|
+
get isConfidential() {
|
|
176
|
+
return this.clientSecret !== void 0;
|
|
177
|
+
}
|
|
178
|
+
async provisionDpopKey() {
|
|
179
|
+
const headers = {
|
|
180
|
+
"content-type": "application/json",
|
|
181
|
+
"x-client-key": this.clientKey
|
|
182
|
+
};
|
|
183
|
+
if (this.clientSecret) {
|
|
184
|
+
headers["x-client-secret"] = this.clientSecret;
|
|
185
|
+
}
|
|
186
|
+
const body = {};
|
|
187
|
+
let pkceVerifier;
|
|
188
|
+
if (!this.isConfidential) {
|
|
189
|
+
pkceVerifier = await this.crypto.generatePkceVerifier();
|
|
190
|
+
const challenge = await this.crypto.computePkceChallenge(pkceVerifier);
|
|
191
|
+
body.pkce_challenge = challenge;
|
|
192
|
+
}
|
|
193
|
+
const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers,
|
|
196
|
+
body: JSON.stringify(body)
|
|
197
|
+
});
|
|
198
|
+
if (!resp.ok) {
|
|
199
|
+
const body2 = await resp.json().catch(() => ({}));
|
|
200
|
+
throw new ApiError(
|
|
201
|
+
`Failed to provision DPoP key: ${resp.status} ${body2.message ?? resp.statusText}`,
|
|
202
|
+
resp.status,
|
|
203
|
+
body2
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const data = await resp.json();
|
|
207
|
+
return {
|
|
208
|
+
provisionId: data.provision_id,
|
|
209
|
+
dpopKey: data.dpop_key,
|
|
210
|
+
pkceVerifier
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
async registerSession(params) {
|
|
214
|
+
const headers = {
|
|
215
|
+
"content-type": "application/json",
|
|
216
|
+
"x-client-key": this.clientKey
|
|
217
|
+
};
|
|
218
|
+
if (this.clientSecret) {
|
|
219
|
+
headers["x-client-secret"] = this.clientSecret;
|
|
220
|
+
}
|
|
221
|
+
const body = {
|
|
222
|
+
provision_id: params.provisionId,
|
|
223
|
+
did: params.did,
|
|
224
|
+
access_token: params.accessToken,
|
|
225
|
+
refresh_token: params.refreshToken,
|
|
226
|
+
scopes: params.scopes,
|
|
227
|
+
pds_url: params.pdsUrl,
|
|
228
|
+
issuer: params.issuer
|
|
229
|
+
};
|
|
230
|
+
if (!this.isConfidential && params.pkceVerifier) {
|
|
231
|
+
body.pkce_verifier = params.pkceVerifier;
|
|
232
|
+
}
|
|
233
|
+
const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {
|
|
234
|
+
method: "POST",
|
|
235
|
+
headers,
|
|
236
|
+
body: JSON.stringify(body)
|
|
237
|
+
});
|
|
238
|
+
if (!resp.ok) {
|
|
239
|
+
const body2 = await resp.json().catch(() => ({}));
|
|
240
|
+
throw new ApiError(
|
|
241
|
+
`Failed to register session: ${resp.status} ${body2.message ?? resp.statusText}`,
|
|
242
|
+
resp.status,
|
|
243
|
+
body2
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const data = await resp.json();
|
|
247
|
+
const storedSession = {
|
|
248
|
+
did: data.did,
|
|
249
|
+
dpopKey: params.dpopKey,
|
|
250
|
+
accessToken: params.accessToken,
|
|
251
|
+
clientKey: this.clientKey,
|
|
252
|
+
instanceUrl: this.instanceUrl
|
|
253
|
+
};
|
|
254
|
+
await this.storage.set(
|
|
255
|
+
`${STORAGE_PREFIX}${data.did}`,
|
|
256
|
+
JSON.stringify(storedSession)
|
|
257
|
+
);
|
|
258
|
+
await this.storage.set(LAST_ACTIVE_KEY, data.did);
|
|
259
|
+
return new HappyViewSession({
|
|
260
|
+
did: data.did,
|
|
261
|
+
dpopKey: params.dpopKey,
|
|
262
|
+
accessToken: params.accessToken,
|
|
263
|
+
clientKey: this.clientKey,
|
|
264
|
+
instanceUrl: this.instanceUrl,
|
|
265
|
+
crypto: this.crypto
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
async deleteSession(did) {
|
|
269
|
+
const headers = {
|
|
270
|
+
"x-client-key": this.clientKey
|
|
271
|
+
};
|
|
272
|
+
if (this.clientSecret) {
|
|
273
|
+
headers["x-client-secret"] = this.clientSecret;
|
|
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
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
await this.storage.delete(`${STORAGE_PREFIX}${did}`);
|
|
291
|
+
const lastActive = await this.storage.get(LAST_ACTIVE_KEY);
|
|
292
|
+
if (lastActive === did) {
|
|
293
|
+
await this.storage.delete(LAST_ACTIVE_KEY);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async restoreSession(did) {
|
|
297
|
+
const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);
|
|
298
|
+
if (!stored) return null;
|
|
299
|
+
const data = JSON.parse(stored);
|
|
300
|
+
return new HappyViewSession({
|
|
301
|
+
did: data.did,
|
|
302
|
+
dpopKey: data.dpopKey,
|
|
303
|
+
accessToken: data.accessToken,
|
|
304
|
+
clientKey: data.clientKey,
|
|
305
|
+
instanceUrl: data.instanceUrl,
|
|
306
|
+
crypto: this.crypto
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
async restore() {
|
|
310
|
+
const did = await this.storage.get(LAST_ACTIVE_KEY);
|
|
311
|
+
if (!did) return null;
|
|
312
|
+
return this.restoreSession(did);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
export {
|
|
316
|
+
ApiError,
|
|
317
|
+
AuthenticationError,
|
|
318
|
+
HappyViewError,
|
|
319
|
+
HappyViewOAuthClient,
|
|
320
|
+
HappyViewSession,
|
|
321
|
+
InvalidStateError,
|
|
322
|
+
MemoryStorage,
|
|
323
|
+
ResolutionError,
|
|
324
|
+
TokenExchangeError,
|
|
325
|
+
base64urlEncode,
|
|
326
|
+
generateDpopProof
|
|
327
|
+
};
|
|
328
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/dpop-proof.ts","../src/session.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n","import type { CryptoAdapter } from \"./types\";\n\nexport function base64urlEncode(data: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n binary += String.fromCharCode(data[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport interface DpopProofParams {\n privateKey: JsonWebKey;\n method: string;\n url: string;\n accessToken?: string;\n nonce?: string;\n}\n\nexport async function generateDpopProof(\n crypto: CryptoAdapter,\n params: DpopProofParams,\n): Promise<string> {\n const { privateKey, method, url, accessToken, nonce } = params;\n\n const publicJwk = {\n kty: privateKey.kty,\n crv: privateKey.crv,\n x: privateKey.x,\n y: privateKey.y,\n };\n\n const header = {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: publicJwk,\n };\n\n // Generate jti\n const jtiBytes = crypto.getRandomValues(16);\n const jti = Array.from(jtiBytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n\n const payload: Record<string, unknown> = {\n htm: method,\n htu: url,\n iat: Math.floor(Date.now() / 1000),\n jti,\n };\n\n // Compute ath: base64url(SHA-256(access_token)) — only when token is present\n if (accessToken) {\n const tokenBytes = new TextEncoder().encode(accessToken);\n const hashBuf = await crypto.sha256(tokenBytes);\n payload.ath = base64urlEncode(hashBuf);\n }\n\n if (nonce !== undefined) {\n payload.nonce = nonce;\n }\n\n const headerB64 = base64urlEncode(\n new TextEncoder().encode(JSON.stringify(header)),\n );\n const payloadB64 = base64urlEncode(\n new TextEncoder().encode(JSON.stringify(payload)),\n );\n\n const message = new TextEncoder().encode(`${headerB64}.${payloadB64}`);\n const signature = await crypto.signEs256(privateKey, message);\n const sigB64 = base64urlEncode(signature);\n\n return `${headerB64}.${payloadB64}.${sigB64}`;\n}\n","import { generateDpopProof } from \"./dpop-proof\";\nimport type { CryptoAdapter } from \"./types\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: JsonWebKey;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n crypto: CryptoAdapter;\n fetch?: typeof globalThis.fetch;\n}\n\nexport class HappyViewSession {\n readonly did: string;\n\n private readonly dpopKey: JsonWebKey;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly crypto: CryptoAdapter;\n private readonly _fetch: typeof globalThis.fetch;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.crypto = options.crypto;\n this._fetch = options.fetch ?? globalThis.fetch;\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url) ? url : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n\n const proof = await generateDpopProof(this.crypto, {\n privateKey: this.dpopKey,\n method,\n url: fullUrl,\n accessToken: this.accessToken,\n nonce: this.dpopNonce,\n });\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n const response = await this._fetch(fullUrl, {\n ...init,\n headers,\n });\n\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import { ApiError } from \"./errors\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n CryptoAdapter,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\nconst STORAGE_PREFIX = \"happyview:session:\";\nconst LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport class HappyViewOAuthClient {\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly crypto: CryptoAdapter;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n private readonly _fetch: typeof globalThis.fetch;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.crypto = options.crypto;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch = options.fetch ?? globalThis.fetch;\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = await this.crypto.generatePkceVerifier();\n const challenge = await this.crypto.computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n return {\n provisionId: data.provision_id,\n dpopKey: data.dpop_key,\n pkceVerifier,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n return new HappyViewSession({\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n crypto: this.crypto,\n });\n }\n\n async deleteSession(did: string): Promise<void> {\n const headers: Record<string, string> = {\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const resp = await this._fetch(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n {\n method: \"DELETE\",\n headers,\n },\n );\n\n if (!resp.ok && resp.status !== 404) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n return new HappyViewSession({\n did: data.did,\n dpopKey: data.dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n crypto: this.crypto,\n });\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n"],"mappings":"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","names":["body"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happyview/oauth-client",
|
|
3
|
+
"version": "1.0.0-dev.2",
|
|
4
|
+
"description": "HappyView OAuth client for ATProto DPoP authentication",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/gamesgamesgamesgamesgames/happyview.git",
|
|
9
|
+
"directory": "packages/oauth-client"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"require": "./dist/index.cjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"main": "./dist/index.cjs",
|
|
23
|
+
"module": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"test": "bun test",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
35
|
+
"@semantic-release/github": "^12.0.6",
|
|
36
|
+
"@semantic-release/exec": "^7.0.0",
|
|
37
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
38
|
+
"@types/bun": "^1.3.12",
|
|
39
|
+
"semantic-release": "^25.0.3",
|
|
40
|
+
"semantic-release-monorepo": "^8.0.2",
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"typescript": "^5.7.0"
|
|
43
|
+
}
|
|
44
|
+
}
|