@happyvertical/auth 0.103.1 → 0.103.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.
@@ -0,0 +1,58 @@
1
+ export declare class ChatGPTSessionError extends Error {
2
+ readonly code: string;
3
+ constructor(code: string, message: string);
4
+ }
5
+ export interface ChatGPTSession {
6
+ subject: string;
7
+ email?: string;
8
+ clientId: string;
9
+ hostId: string;
10
+ idToken: string;
11
+ accessToken: string;
12
+ refreshToken: string;
13
+ scopes: string[];
14
+ expiresAt: number;
15
+ }
16
+ export interface LocalChatGPTOptions {
17
+ appName: string;
18
+ path?: string;
19
+ openBrowser?: (url: string) => Promise<void> | void;
20
+ fetch?: typeof fetch;
21
+ now?: () => number;
22
+ }
23
+ /**
24
+ * Stores credentials only in an owner-readable local file. Callers should pass
25
+ * a per-application config path; no credential is ever written to a project.
26
+ */
27
+ export declare class LocalChatGPTSessionManager {
28
+ private readonly options;
29
+ private pending;
30
+ private refreshing;
31
+ private readonly file;
32
+ private hostId?;
33
+ private readonly fetcher;
34
+ private readonly now;
35
+ private readonly remoteRevocationTimeoutMs;
36
+ constructor(options: LocalChatGPTOptions);
37
+ private disk;
38
+ private generation;
39
+ private processIsAlive;
40
+ private locked;
41
+ private save;
42
+ sessions(): Promise<Array<Omit<ChatGPTSession, 'accessToken' | 'refreshToken' | 'idToken'>>>;
43
+ begin(selectedClientId?: string): Promise<{
44
+ authorizationUrl: string;
45
+ callback: Promise<ChatGPTSession>;
46
+ }>;
47
+ private complete;
48
+ private verify;
49
+ /** Return a currently usable token, serializing rotating-token refreshes per registration. */
50
+ accessToken(clientId: string): Promise<string>;
51
+ refresh(clientId: string): Promise<ChatGPTSession>;
52
+ private doRefresh;
53
+ /** Clear local credentials after attempting remote refresh-token revocation. */
54
+ logout(clientId: string): Promise<{
55
+ remoteRevocationConfirmed: boolean;
56
+ }>;
57
+ }
58
+ //# sourceMappingURL=local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../src/local.ts"],"names":[],"mappings":"AAuBA,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EAC5B,OAAO,EAAE,MAAM;CAKlB;AACD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpD,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AA4CD;;;GAGG;AACH,qBAAa,0BAA0B;IAQzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpC,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAS;gBACtB,OAAO,EAAE,mBAAmB;YAK3C,IAAI;IAgBlB,OAAO,CAAC,UAAU;IAGlB,OAAO,CAAC,cAAc;YAQR,MAAM;YAiCN,IAAI;IAQZ,QAAQ,IAAI,OAAO,CACvB,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,aAAa,GAAG,cAAc,GAAG,SAAS,CAAC,CAAC,CACxE;IAKK,KAAK,CACT,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;KAAE,CAAC;YA0G7D,QAAQ;YAuGR,MAAM;IAyCpB,8FAA8F;IACxF,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAY9C,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;YAS1C,SAAS;IA+JvB,gFAAgF;IAC1E,MAAM,CACV,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,yBAAyB,EAAE,OAAO,CAAA;KAAE,CAAC;CAoDnD"}
package/dist/local.js ADDED
@@ -0,0 +1,481 @@
1
+ import { n as jwtVerify, t as createRemoteJWKSet } from "./chunks/remote-C3O0f74l.js";
2
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
3
+ import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
4
+ import { createServer } from "node:http";
5
+ import { tmpdir } from "node:os";
6
+ import { dirname, join } from "node:path";
7
+ import { DatabaseSync } from "node:sqlite";
8
+ //#region src/local.ts
9
+ /** Node-only local Sign in with ChatGPT session runtime. */
10
+ var AUTHORIZE = "https://auth.openai.com/api/accounts/authorize";
11
+ var TOKEN = "https://auth.openai.com/api/accounts/oauth/token";
12
+ var DISCOVERY = "https://auth.openai.com/.well-known/openid-configuration";
13
+ var RESOURCE = "https://api.openai.com/v1";
14
+ var PLAN_SCOPE = "chatgpt.tokens.use.direct";
15
+ var SCOPES = [
16
+ "openid",
17
+ "profile",
18
+ "email",
19
+ "offline_access",
20
+ "resource.invoke",
21
+ PLAN_SCOPE
22
+ ];
23
+ var ChatGPTSessionError = class extends Error {
24
+ code;
25
+ constructor(code, message) {
26
+ super(message);
27
+ this.code = code;
28
+ this.name = "ChatGPTSessionError";
29
+ }
30
+ };
31
+ function b64(bytes) {
32
+ return Buffer.from(bytes).toString("base64url");
33
+ }
34
+ function random() {
35
+ return b64(randomBytes(32));
36
+ }
37
+ function pathFor(options) {
38
+ return options.path ?? join(process.env.XDG_CONFIG_HOME ?? join(process.env.HOME ?? tmpdir(), ".config"), options.appName, "chatgpt.json");
39
+ }
40
+ function required(value, label) {
41
+ if (!value) throw new ChatGPTSessionError("invalid_callback", `Missing ${label} in OAuth callback`);
42
+ return value;
43
+ }
44
+ /**
45
+ * Stores credentials only in an owner-readable local file. Callers should pass
46
+ * a per-application config path; no credential is ever written to a project.
47
+ */
48
+ var LocalChatGPTSessionManager = class {
49
+ options;
50
+ pending = /* @__PURE__ */ new Map();
51
+ refreshing = /* @__PURE__ */ new Map();
52
+ file;
53
+ hostId;
54
+ fetcher;
55
+ now;
56
+ remoteRevocationTimeoutMs = 5e3;
57
+ constructor(options) {
58
+ this.options = options;
59
+ this.file = pathFor(options);
60
+ this.fetcher = options.fetch ?? fetch;
61
+ this.now = options.now ?? Date.now;
62
+ }
63
+ async disk() {
64
+ try {
65
+ const disk = JSON.parse(await readFile(this.file, "utf8"));
66
+ this.hostId = disk.hostId;
67
+ return disk;
68
+ } catch (e) {
69
+ if (e?.code === "ENOENT") {
70
+ if (!this.hostId) this.hostId = `urn:uuid:${randomUUID()}`;
71
+ return {
72
+ hostId: this.hostId,
73
+ sessions: []
74
+ };
75
+ }
76
+ throw e;
77
+ }
78
+ }
79
+ generation(disk, clientId) {
80
+ return disk.generations?.[clientId] ?? 0;
81
+ }
82
+ processIsAlive(pid) {
83
+ try {
84
+ process.kill(pid, 0);
85
+ return true;
86
+ } catch (error) {
87
+ return error?.code !== "ESRCH";
88
+ }
89
+ }
90
+ async locked(operation) {
91
+ const mutex = `${this.file}.mutex.sqlite`;
92
+ await mkdir(dirname(this.file), {
93
+ recursive: true,
94
+ mode: 448
95
+ });
96
+ for (let attempt = 0; attempt < 200; attempt++) {
97
+ const database = new DatabaseSync(mutex, { timeout: 0 });
98
+ let busy = false;
99
+ try {
100
+ await chmod(mutex, 384);
101
+ try {
102
+ database.exec("BEGIN IMMEDIATE");
103
+ } catch (error) {
104
+ if (error?.code === "ERR_SQLITE_ERROR" && typeof error?.errcode === "number" && (error.errcode & 255) === 5) busy = true;
105
+ else throw error;
106
+ }
107
+ if (!busy) try {
108
+ return await operation();
109
+ } finally {
110
+ database.exec("ROLLBACK");
111
+ }
112
+ } finally {
113
+ database.close();
114
+ }
115
+ await new Promise((resolve) => setTimeout(resolve, 10));
116
+ }
117
+ throw new ChatGPTSessionError("storage_busy", "Credential storage is busy");
118
+ }
119
+ async save(disk) {
120
+ await mkdir(dirname(this.file), {
121
+ recursive: true,
122
+ mode: 448
123
+ });
124
+ const temp = `${this.file}.${randomUUID()}.tmp`;
125
+ await writeFile(temp, JSON.stringify(disk), { mode: 384 });
126
+ await chmod(temp, 384);
127
+ await rename(temp, this.file);
128
+ await chmod(this.file, 384);
129
+ }
130
+ async sessions() {
131
+ return (await this.disk()).sessions.map(({ accessToken, refreshToken, idToken, ...safe }) => safe);
132
+ }
133
+ async begin(selectedClientId) {
134
+ const disk = await this.locked(async () => {
135
+ const current = await this.disk();
136
+ await this.save(current);
137
+ return current;
138
+ });
139
+ const selected = selectedClientId ? disk.sessions.find((s) => s.clientId === selectedClientId) : void 0;
140
+ if (selectedClientId && !selected) throw new ChatGPTSessionError("unknown_account", "Selected ChatGPT account is not stored locally");
141
+ const state = random(), nonce = random(), verifier = random();
142
+ const challenge = b64(createHash("sha256").update(verifier).digest());
143
+ let resolve, reject;
144
+ const callback = new Promise((a, b) => {
145
+ resolve = a;
146
+ reject = b;
147
+ });
148
+ let redirectUri = "";
149
+ const server = createServer((req, res) => {
150
+ let url;
151
+ try {
152
+ url = new URL(req.url ?? "/", redirectUri);
153
+ } catch {
154
+ res.writeHead(400, { "Content-Type": "text/plain" });
155
+ res.end("Invalid sign-in callback.");
156
+ return;
157
+ }
158
+ if (!this.pending.has(state)) {
159
+ res.writeHead(400, { "Content-Type": "text/plain" });
160
+ res.end("Sign-in callback was already consumed.");
161
+ return;
162
+ }
163
+ const finish = async () => {
164
+ try {
165
+ const result = await this.complete(url, state);
166
+ res.writeHead(200, { "Content-Type": "text/plain" });
167
+ res.end("Sign-in complete. You may close this window.");
168
+ resolve(result);
169
+ } catch (error) {
170
+ res.writeHead(400, { "Content-Type": "text/plain" });
171
+ res.end("Sign-in failed. Return to the application.");
172
+ reject(error);
173
+ } finally {
174
+ server.close();
175
+ this.pending.delete(state);
176
+ }
177
+ };
178
+ finish();
179
+ });
180
+ await new Promise((resolveListen, rejectListen) => {
181
+ server.once("error", rejectListen);
182
+ server.listen(0, "127.0.0.1", () => resolveListen());
183
+ });
184
+ const address = server.address();
185
+ if (!address || typeof address === "string") throw new ChatGPTSessionError("loopback_unavailable", "Could not bind 127.0.0.1 loopback callback");
186
+ redirectUri = `http://127.0.0.1:${address.port}/auth/callback`;
187
+ const clientId = selected?.clientId ?? "dynamic_agent_client";
188
+ this.pending.set(state, {
189
+ state,
190
+ nonce,
191
+ verifier,
192
+ redirectUri,
193
+ clientId,
194
+ selected,
195
+ generation: selected ? this.generation(disk, selected.clientId) : 0,
196
+ server
197
+ });
198
+ const params = new URLSearchParams({
199
+ client_id: clientId,
200
+ response_type: "code",
201
+ redirect_uri: redirectUri,
202
+ scope: SCOPES.join(" "),
203
+ resource: RESOURCE,
204
+ state,
205
+ nonce,
206
+ code_challenge_method: "S256",
207
+ code_challenge: challenge,
208
+ ext_agent_host_id: disk.hostId
209
+ });
210
+ if (selected) {
211
+ params.set("id_token_hint", selected.idToken);
212
+ if (selected.email) params.set("login_hint", selected.email);
213
+ } else params.set("agent_name_hint", this.options.appName);
214
+ const authorizationUrl = `${AUTHORIZE}?${params}`;
215
+ try {
216
+ await this.options.openBrowser?.(authorizationUrl);
217
+ } catch (error) {
218
+ this.pending.delete(state);
219
+ await new Promise((resolveClose) => server.close(() => resolveClose()));
220
+ throw error;
221
+ }
222
+ return {
223
+ authorizationUrl,
224
+ callback
225
+ };
226
+ }
227
+ async complete(url, expectedState) {
228
+ const pending = this.pending.get(expectedState);
229
+ if (!pending || url.searchParams.get("state") !== expectedState) throw new ChatGPTSessionError("state_mismatch", "Rejected OAuth callback with unbound state");
230
+ if (url.searchParams.get("error")) throw new ChatGPTSessionError("consent_declined", "ChatGPT authorization was declined");
231
+ this.pending.delete(expectedState);
232
+ const code = required(url.searchParams.get("code"), "code");
233
+ const callbackClientId = url.searchParams.get("client_id");
234
+ const clientId = pending.selected ? pending.clientId : required(callbackClientId, "issued client_id");
235
+ if (!pending.selected && clientId === "dynamic_agent_client") throw new ChatGPTSessionError("registration_incomplete", "OAuth registration did not return an issued client id");
236
+ if (pending.selected && callbackClientId && callbackClientId !== clientId) throw new ChatGPTSessionError("client_mismatch", "OAuth callback changed the registered client");
237
+ const body = new URLSearchParams({
238
+ grant_type: "authorization_code",
239
+ client_id: clientId,
240
+ code,
241
+ code_verifier: pending.verifier,
242
+ redirect_uri: pending.redirectUri,
243
+ resource: RESOURCE
244
+ });
245
+ const response = await this.fetcher(TOKEN, {
246
+ method: "POST",
247
+ headers: { "content-type": "application/x-www-form-urlencoded" },
248
+ body
249
+ });
250
+ if (!response.ok) throw new ChatGPTSessionError("token_exchange_failed", "OAuth token exchange failed");
251
+ const token = await response.json();
252
+ const identity = await this.verify(token.id_token, clientId, pending.nonce);
253
+ const scopes = String(token.scope ?? "").split(" ").filter(Boolean);
254
+ if (!scopes.includes(PLAN_SCOPE)) throw new ChatGPTSessionError("plan_permission_missing", "ChatGPT plan usage permission was not granted");
255
+ const session = {
256
+ subject: identity.sub,
257
+ email: identity.email,
258
+ clientId,
259
+ hostId: (await this.disk()).hostId,
260
+ idToken: token.id_token,
261
+ accessToken: token.access_token,
262
+ refreshToken: token.refresh_token,
263
+ scopes,
264
+ expiresAt: this.now() + Number(token.expires_in ?? 0) * 1e3
265
+ };
266
+ if (!session.accessToken || !session.refreshToken) throw new ChatGPTSessionError("invalid_token_response", "OAuth response omitted renewable credentials");
267
+ if (pending.selected && session.subject !== pending.selected.subject) throw new ChatGPTSessionError("account_mismatch", "OAuth identity did not match the selected account");
268
+ await this.locked(async () => {
269
+ const disk = await this.disk();
270
+ if (pending.selected && this.generation(disk, clientId) !== pending.generation) throw new ChatGPTSessionError("session_invalidated", "ChatGPT session changed while authorization was in progress");
271
+ disk.sessions = [...disk.sessions.filter((s) => s.clientId !== clientId), session];
272
+ disk.generations = {
273
+ ...disk.generations,
274
+ [clientId]: this.generation(disk, clientId) + 1
275
+ };
276
+ if (disk.refreshes) delete disk.refreshes[clientId];
277
+ await this.save(disk);
278
+ });
279
+ return session;
280
+ }
281
+ async verify(token, clientId, nonce) {
282
+ if (!token) throw new ChatGPTSessionError("invalid_token_response", "OAuth response omitted id_token");
283
+ const config = await (await this.fetcher(DISCOVERY)).json();
284
+ if (!config.jwks_uri) throw new ChatGPTSessionError("discovery_failed", "OpenID discovery did not supply JWKS");
285
+ const result = await jwtVerify(token, createRemoteJWKSet(new URL(config.jwks_uri)), {
286
+ issuer: "https://auth.openai.com",
287
+ audience: clientId
288
+ });
289
+ if (result.payload.nonce !== nonce || typeof result.payload.sub !== "string" || typeof result.payload.exp !== "number" || typeof result.payload.iat !== "number") throw new ChatGPTSessionError("invalid_identity", "OIDC token nonce or subject is invalid");
290
+ return {
291
+ sub: result.payload.sub,
292
+ email: typeof result.payload.email === "string" ? result.payload.email : void 0
293
+ };
294
+ }
295
+ /** Return a currently usable token, serializing rotating-token refreshes per registration. */
296
+ async accessToken(clientId) {
297
+ const session = (await this.disk()).sessions.find((s) => s.clientId === clientId);
298
+ if (!session) throw new ChatGPTSessionError("unknown_account", "Selected ChatGPT account is not stored locally");
299
+ if (session.expiresAt > this.now() + 6e4) return session.accessToken;
300
+ return (await this.refresh(clientId)).accessToken;
301
+ }
302
+ async refresh(clientId) {
303
+ const active = this.refreshing.get(clientId);
304
+ if (active) return active;
305
+ const refresh = this.doRefresh(clientId).finally(() => this.refreshing.delete(clientId));
306
+ this.refreshing.set(clientId, refresh);
307
+ return refresh;
308
+ }
309
+ async doRefresh(clientId) {
310
+ const leaseId = randomUUID();
311
+ let observedRefreshToken;
312
+ let current;
313
+ let generation = 0;
314
+ for (let attempt = 0; attempt < 200; attempt++) {
315
+ const claim = await this.locked(async () => {
316
+ const disk = await this.disk();
317
+ const session = disk.sessions.find((s) => s.clientId === clientId);
318
+ if (!session) throw new ChatGPTSessionError("unknown_account", "Selected ChatGPT account is not stored locally");
319
+ observedRefreshToken ??= session.refreshToken;
320
+ if (session.refreshToken !== observedRefreshToken) return {
321
+ kind: "updated",
322
+ session
323
+ };
324
+ const active = disk.refreshes?.[clientId];
325
+ if (active && this.processIsAlive(active.pid)) return { kind: "wait" };
326
+ disk.refreshes = {
327
+ ...disk.refreshes,
328
+ [clientId]: {
329
+ id: leaseId,
330
+ pid: process.pid
331
+ }
332
+ };
333
+ await this.save(disk);
334
+ return {
335
+ kind: "owned",
336
+ session,
337
+ generation: this.generation(disk, clientId)
338
+ };
339
+ });
340
+ if (claim.kind === "updated") return claim.session;
341
+ if (claim.kind === "owned") {
342
+ current = claim.session;
343
+ generation = claim.generation;
344
+ break;
345
+ }
346
+ await new Promise((resolve) => setTimeout(resolve, 10));
347
+ }
348
+ if (!current) throw new ChatGPTSessionError("storage_busy", "Credential refresh is already in progress");
349
+ const releaseLease = async () => {
350
+ await this.locked(async () => {
351
+ const disk = await this.disk();
352
+ if (disk.refreshes?.[clientId]?.id === leaseId) {
353
+ delete disk.refreshes[clientId];
354
+ await this.save(disk);
355
+ }
356
+ });
357
+ };
358
+ const invalidate = async (code, message) => {
359
+ await this.locked(async () => {
360
+ const disk = await this.disk();
361
+ if (disk.refreshes?.[clientId]?.id === leaseId && this.generation(disk, clientId) === generation && disk.sessions.find((session) => session.clientId === clientId)?.refreshToken === current.refreshToken) {
362
+ disk.sessions = disk.sessions.filter((s) => s.clientId !== clientId);
363
+ disk.generations = {
364
+ ...disk.generations,
365
+ [clientId]: generation + 1
366
+ };
367
+ delete disk.refreshes[clientId];
368
+ await this.save(disk);
369
+ }
370
+ });
371
+ throw new ChatGPTSessionError(code, message);
372
+ };
373
+ let response;
374
+ try {
375
+ response = await this.fetcher(TOKEN, {
376
+ method: "POST",
377
+ headers: { "content-type": "application/x-www-form-urlencoded" },
378
+ body: new URLSearchParams({
379
+ grant_type: "refresh_token",
380
+ client_id: current.clientId,
381
+ refresh_token: current.refreshToken,
382
+ resource: RESOURCE
383
+ })
384
+ });
385
+ } catch (error) {
386
+ await releaseLease();
387
+ throw error;
388
+ }
389
+ if (!response.ok) {
390
+ if (response.status === 400 || response.status === 401) return invalidate("refresh_invalid", "Saved ChatGPT session is no longer renewable; sign in again");
391
+ await releaseLease();
392
+ throw new ChatGPTSessionError("refresh_failed", "ChatGPT session refresh failed; credentials were retained");
393
+ }
394
+ let token;
395
+ try {
396
+ token = await response.json();
397
+ } catch (error) {
398
+ await releaseLease();
399
+ throw error;
400
+ }
401
+ if (!token.access_token || !token.refresh_token) {
402
+ await releaseLease();
403
+ throw new ChatGPTSessionError("invalid_refresh_response", "Refresh response omitted replacement credentials");
404
+ }
405
+ const scopes = String(token.scope ?? current.scopes.join(" ")).split(" ").filter(Boolean);
406
+ if (!scopes.includes(PLAN_SCOPE)) return invalidate("plan_permission_missing", "ChatGPT plan usage permission was not granted");
407
+ const updated = {
408
+ ...current,
409
+ accessToken: token.access_token,
410
+ refreshToken: token.refresh_token,
411
+ idToken: token.id_token ?? current.idToken,
412
+ scopes,
413
+ expiresAt: this.now() + Number(token.expires_in ?? 0) * 1e3
414
+ };
415
+ return this.locked(async () => {
416
+ const disk = await this.disk();
417
+ const stored = disk.sessions.find((s) => s.clientId === clientId);
418
+ const ownsLease = disk.refreshes?.[clientId]?.id === leaseId;
419
+ if (!ownsLease || this.generation(disk, clientId) !== generation || stored?.refreshToken !== current.refreshToken) {
420
+ if (ownsLease) {
421
+ delete disk.refreshes[clientId];
422
+ await this.save(disk);
423
+ }
424
+ throw new ChatGPTSessionError("session_invalidated", "ChatGPT session changed while refresh was in progress");
425
+ }
426
+ disk.sessions = disk.sessions.map((s) => s.clientId === clientId ? updated : s);
427
+ delete disk.refreshes[clientId];
428
+ await this.save(disk);
429
+ return updated;
430
+ });
431
+ }
432
+ /** Clear local credentials after attempting remote refresh-token revocation. */
433
+ async logout(clientId) {
434
+ const current = await this.locked(async () => {
435
+ const disk = await this.disk();
436
+ const current = disk.sessions.find((s) => s.clientId === clientId);
437
+ if (!current) return void 0;
438
+ disk.sessions = disk.sessions.filter((s) => s.clientId !== clientId);
439
+ disk.generations = {
440
+ ...disk.generations,
441
+ [clientId]: this.generation(disk, clientId) + 1
442
+ };
443
+ if (disk.refreshes) delete disk.refreshes[clientId];
444
+ await this.save(disk);
445
+ return current;
446
+ });
447
+ if (!current) return { remoteRevocationConfirmed: true };
448
+ const controller = new AbortController();
449
+ let timer;
450
+ try {
451
+ return { remoteRevocationConfirmed: await Promise.race([(async () => {
452
+ const discovery = await (await this.fetcher(DISCOVERY, { signal: controller.signal })).json();
453
+ if (!discovery.revocation_endpoint) return false;
454
+ const body = new URLSearchParams({
455
+ token: current.refreshToken,
456
+ token_type_hint: "refresh_token",
457
+ client_id: current.clientId
458
+ });
459
+ return (await this.fetcher(discovery.revocation_endpoint, {
460
+ method: "POST",
461
+ headers: { "content-type": "application/x-www-form-urlencoded" },
462
+ body,
463
+ signal: controller.signal
464
+ })).ok;
465
+ })(), new Promise((resolve) => {
466
+ timer = setTimeout(() => {
467
+ controller.abort();
468
+ resolve(false);
469
+ }, this.remoteRevocationTimeoutMs);
470
+ })]) };
471
+ } catch {
472
+ return { remoteRevocationConfirmed: false };
473
+ } finally {
474
+ if (timer) clearTimeout(timer);
475
+ }
476
+ }
477
+ };
478
+ //#endregion
479
+ export { ChatGPTSessionError, LocalChatGPTSessionManager };
480
+
481
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","names":[],"sources":["../src/local.ts"],"sourcesContent":["/** Node-only local Sign in with ChatGPT session runtime. */\nimport { createHash, randomBytes, randomUUID } from 'node:crypto';\nimport { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { createServer } from 'node:http';\nimport { tmpdir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\n\nconst AUTHORIZE = 'https://auth.openai.com/api/accounts/authorize';\nconst TOKEN = 'https://auth.openai.com/api/accounts/oauth/token';\nconst DISCOVERY = 'https://auth.openai.com/.well-known/openid-configuration';\nconst RESOURCE = 'https://api.openai.com/v1';\nconst PLAN_SCOPE = 'chatgpt.tokens.use.direct';\nconst SCOPES = [\n 'openid',\n 'profile',\n 'email',\n 'offline_access',\n 'resource.invoke',\n PLAN_SCOPE,\n];\n\nexport class ChatGPTSessionError extends Error {\n constructor(\n public readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = 'ChatGPTSessionError';\n }\n}\nexport interface ChatGPTSession {\n subject: string;\n email?: string;\n clientId: string;\n hostId: string;\n idToken: string;\n accessToken: string;\n refreshToken: string;\n scopes: string[];\n expiresAt: number;\n}\nexport interface LocalChatGPTOptions {\n appName: string;\n path?: string;\n openBrowser?: (url: string) => Promise<void> | void;\n fetch?: typeof fetch;\n now?: () => number;\n}\ninterface Disk {\n hostId: string;\n sessions: ChatGPTSession[];\n generations?: Record<string, number>;\n refreshes?: Record<string, { id: string; pid: number }>;\n}\ninterface Pending {\n state: string;\n nonce: string;\n verifier: string;\n redirectUri: string;\n clientId: string;\n selected?: ChatGPTSession;\n generation: number;\n server: ReturnType<typeof createServer>;\n}\n\nfunction b64(bytes: Uint8Array) {\n return Buffer.from(bytes).toString('base64url');\n}\nfunction random() {\n return b64(randomBytes(32));\n}\nfunction pathFor(options: LocalChatGPTOptions) {\n return (\n options.path ??\n join(\n process.env.XDG_CONFIG_HOME ??\n join(process.env.HOME ?? tmpdir(), '.config'),\n options.appName,\n 'chatgpt.json',\n )\n );\n}\nfunction required(value: string | null, label: string) {\n if (!value)\n throw new ChatGPTSessionError(\n 'invalid_callback',\n `Missing ${label} in OAuth callback`,\n );\n return value;\n}\n\n/**\n * Stores credentials only in an owner-readable local file. Callers should pass\n * a per-application config path; no credential is ever written to a project.\n */\nexport class LocalChatGPTSessionManager {\n private pending = new Map<string, Pending>();\n private refreshing = new Map<string, Promise<ChatGPTSession>>();\n private readonly file: string;\n private hostId?: string;\n private readonly fetcher: typeof fetch;\n private readonly now: () => number;\n private readonly remoteRevocationTimeoutMs = 5_000;\n constructor(private readonly options: LocalChatGPTOptions) {\n this.file = pathFor(options);\n this.fetcher = options.fetch ?? fetch;\n this.now = options.now ?? Date.now;\n }\n private async disk(): Promise<Disk> {\n try {\n const disk = JSON.parse(await readFile(this.file, 'utf8')) as Disk;\n this.hostId = disk.hostId;\n return disk;\n } catch (e: any) {\n if (e?.code === 'ENOENT') {\n if (!this.hostId) this.hostId = `urn:uuid:${randomUUID()}`;\n return {\n hostId: this.hostId,\n sessions: [],\n };\n }\n throw e;\n }\n }\n private generation(disk: Disk, clientId: string) {\n return disk.generations?.[clientId] ?? 0;\n }\n private processIsAlive(pid: number) {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error: any) {\n return error?.code !== 'ESRCH';\n }\n }\n private async locked<T>(operation: () => Promise<T>): Promise<T> {\n const mutex = `${this.file}.mutex.sqlite`;\n await mkdir(dirname(this.file), { recursive: true, mode: 0o700 });\n for (let attempt = 0; attempt < 200; attempt++) {\n const database = new DatabaseSync(mutex, { timeout: 0 });\n let busy = false;\n try {\n await chmod(mutex, 0o600);\n try {\n database.exec('BEGIN IMMEDIATE');\n } catch (error: any) {\n if (\n error?.code === 'ERR_SQLITE_ERROR' &&\n typeof error?.errcode === 'number' &&\n (error.errcode & 0xff) === 5\n )\n busy = true;\n else throw error;\n }\n if (!busy) {\n try {\n return await operation();\n } finally {\n database.exec('ROLLBACK');\n }\n }\n } finally {\n database.close();\n }\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n throw new ChatGPTSessionError('storage_busy', 'Credential storage is busy');\n }\n private async save(disk: Disk) {\n await mkdir(dirname(this.file), { recursive: true, mode: 0o700 });\n const temp = `${this.file}.${randomUUID()}.tmp`;\n await writeFile(temp, JSON.stringify(disk), { mode: 0o600 });\n await chmod(temp, 0o600);\n await rename(temp, this.file);\n await chmod(this.file, 0o600);\n }\n async sessions(): Promise<\n Array<Omit<ChatGPTSession, 'accessToken' | 'refreshToken' | 'idToken'>>\n > {\n return (await this.disk()).sessions.map(\n ({ accessToken, refreshToken, idToken, ...safe }) => safe,\n );\n }\n async begin(\n selectedClientId?: string,\n ): Promise<{ authorizationUrl: string; callback: Promise<ChatGPTSession> }> {\n const disk = await this.locked(async () => {\n const current = await this.disk();\n await this.save(current);\n return current;\n });\n const selected = selectedClientId\n ? disk.sessions.find((s) => s.clientId === selectedClientId)\n : undefined;\n if (selectedClientId && !selected)\n throw new ChatGPTSessionError(\n 'unknown_account',\n 'Selected ChatGPT account is not stored locally',\n );\n const state = random(),\n nonce = random(),\n verifier = random();\n const challenge = b64(createHash('sha256').update(verifier).digest());\n let resolve!: (value: ChatGPTSession) => void,\n reject!: (reason: Error) => void;\n const callback = new Promise<ChatGPTSession>((a, b) => {\n resolve = a;\n reject = b;\n });\n let redirectUri = '';\n const server = createServer((req, res) => {\n let url: URL;\n try {\n url = new URL(req.url ?? '/', redirectUri);\n } catch {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Invalid sign-in callback.');\n return;\n }\n if (!this.pending.has(state)) {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Sign-in callback was already consumed.');\n return;\n }\n const finish = async () => {\n try {\n const result = await this.complete(url, state);\n res.writeHead(200, { 'Content-Type': 'text/plain' });\n res.end('Sign-in complete. You may close this window.');\n resolve(result);\n } catch (error) {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Sign-in failed. Return to the application.');\n reject(error as Error);\n } finally {\n server.close();\n this.pending.delete(state);\n }\n };\n void finish();\n });\n await new Promise<void>((resolveListen, rejectListen) => {\n server.once('error', rejectListen);\n server.listen(0, '127.0.0.1', () => resolveListen());\n });\n const address = server.address();\n if (!address || typeof address === 'string')\n throw new ChatGPTSessionError(\n 'loopback_unavailable',\n 'Could not bind 127.0.0.1 loopback callback',\n );\n redirectUri = `http://127.0.0.1:${address.port}/auth/callback`;\n const clientId = selected?.clientId ?? 'dynamic_agent_client';\n this.pending.set(state, {\n state,\n nonce,\n verifier,\n redirectUri,\n clientId,\n selected,\n generation: selected ? this.generation(disk, selected.clientId) : 0,\n server,\n });\n const params = new URLSearchParams({\n client_id: clientId,\n response_type: 'code',\n redirect_uri: redirectUri,\n scope: SCOPES.join(' '),\n resource: RESOURCE,\n state,\n nonce,\n code_challenge_method: 'S256',\n code_challenge: challenge,\n ext_agent_host_id: disk.hostId,\n });\n if (selected) {\n params.set('id_token_hint', selected.idToken);\n if (selected.email) params.set('login_hint', selected.email);\n } else params.set('agent_name_hint', this.options.appName);\n const authorizationUrl = `${AUTHORIZE}?${params}`;\n try {\n await this.options.openBrowser?.(authorizationUrl);\n } catch (error) {\n this.pending.delete(state);\n await new Promise<void>((resolveClose) =>\n server.close(() => resolveClose()),\n );\n throw error;\n }\n return { authorizationUrl, callback };\n }\n private async complete(\n url: URL,\n expectedState: string,\n ): Promise<ChatGPTSession> {\n const pending = this.pending.get(expectedState);\n if (!pending || url.searchParams.get('state') !== expectedState)\n throw new ChatGPTSessionError(\n 'state_mismatch',\n 'Rejected OAuth callback with unbound state',\n );\n if (url.searchParams.get('error'))\n throw new ChatGPTSessionError(\n 'consent_declined',\n 'ChatGPT authorization was declined',\n );\n this.pending.delete(expectedState);\n const code = required(url.searchParams.get('code'), 'code');\n const callbackClientId = url.searchParams.get('client_id');\n const clientId = pending.selected\n ? pending.clientId\n : required(callbackClientId, 'issued client_id');\n if (!pending.selected && clientId === 'dynamic_agent_client')\n throw new ChatGPTSessionError(\n 'registration_incomplete',\n 'OAuth registration did not return an issued client id',\n );\n if (pending.selected && callbackClientId && callbackClientId !== clientId)\n throw new ChatGPTSessionError(\n 'client_mismatch',\n 'OAuth callback changed the registered client',\n );\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: clientId,\n code,\n code_verifier: pending.verifier,\n redirect_uri: pending.redirectUri,\n resource: RESOURCE,\n });\n const response = await this.fetcher(TOKEN, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body,\n });\n if (!response.ok)\n throw new ChatGPTSessionError(\n 'token_exchange_failed',\n 'OAuth token exchange failed',\n );\n const token: any = await response.json();\n const identity = await this.verify(token.id_token, clientId, pending.nonce);\n const scopes = String(token.scope ?? '')\n .split(' ')\n .filter(Boolean);\n if (!scopes.includes(PLAN_SCOPE))\n throw new ChatGPTSessionError(\n 'plan_permission_missing',\n 'ChatGPT plan usage permission was not granted',\n );\n const session: ChatGPTSession = {\n subject: identity.sub,\n email: identity.email,\n clientId,\n hostId: (await this.disk()).hostId,\n idToken: token.id_token,\n accessToken: token.access_token,\n refreshToken: token.refresh_token,\n scopes,\n expiresAt: this.now() + Number(token.expires_in ?? 0) * 1000,\n };\n if (!session.accessToken || !session.refreshToken)\n throw new ChatGPTSessionError(\n 'invalid_token_response',\n 'OAuth response omitted renewable credentials',\n );\n if (pending.selected && session.subject !== pending.selected.subject)\n throw new ChatGPTSessionError(\n 'account_mismatch',\n 'OAuth identity did not match the selected account',\n );\n await this.locked(async () => {\n const disk = await this.disk();\n if (\n pending.selected &&\n this.generation(disk, clientId) !== pending.generation\n )\n throw new ChatGPTSessionError(\n 'session_invalidated',\n 'ChatGPT session changed while authorization was in progress',\n );\n disk.sessions = [\n ...disk.sessions.filter((s) => s.clientId !== clientId),\n session,\n ];\n disk.generations = {\n ...disk.generations,\n [clientId]: this.generation(disk, clientId) + 1,\n };\n if (disk.refreshes) delete disk.refreshes[clientId];\n await this.save(disk);\n });\n return session;\n }\n private async verify(\n token: string,\n clientId: string,\n nonce: string,\n ): Promise<{ sub: string; email?: string }> {\n if (!token)\n throw new ChatGPTSessionError(\n 'invalid_token_response',\n 'OAuth response omitted id_token',\n );\n const config = (await (await this.fetcher(DISCOVERY)).json()) as {\n jwks_uri?: string;\n };\n if (!config.jwks_uri)\n throw new ChatGPTSessionError(\n 'discovery_failed',\n 'OpenID discovery did not supply JWKS',\n );\n const result = await jwtVerify(\n token,\n createRemoteJWKSet(new URL(config.jwks_uri)),\n { issuer: 'https://auth.openai.com', audience: clientId },\n );\n if (\n result.payload.nonce !== nonce ||\n typeof result.payload.sub !== 'string' ||\n typeof result.payload.exp !== 'number' ||\n typeof result.payload.iat !== 'number'\n )\n throw new ChatGPTSessionError(\n 'invalid_identity',\n 'OIDC token nonce or subject is invalid',\n );\n return {\n sub: result.payload.sub,\n email:\n typeof result.payload.email === 'string'\n ? result.payload.email\n : undefined,\n };\n }\n /** Return a currently usable token, serializing rotating-token refreshes per registration. */\n async accessToken(clientId: string): Promise<string> {\n const session = (await this.disk()).sessions.find(\n (s) => s.clientId === clientId,\n );\n if (!session)\n throw new ChatGPTSessionError(\n 'unknown_account',\n 'Selected ChatGPT account is not stored locally',\n );\n if (session.expiresAt > this.now() + 60_000) return session.accessToken;\n return (await this.refresh(clientId)).accessToken;\n }\n async refresh(clientId: string): Promise<ChatGPTSession> {\n const active = this.refreshing.get(clientId);\n if (active) return active;\n const refresh = this.doRefresh(clientId).finally(() =>\n this.refreshing.delete(clientId),\n );\n this.refreshing.set(clientId, refresh);\n return refresh;\n }\n private async doRefresh(clientId: string): Promise<ChatGPTSession> {\n const leaseId = randomUUID();\n let observedRefreshToken: string | undefined;\n let current: ChatGPTSession | undefined;\n let generation = 0;\n for (let attempt = 0; attempt < 200; attempt++) {\n const claim = await this.locked(async () => {\n const disk = await this.disk();\n const session = disk.sessions.find((s) => s.clientId === clientId);\n if (!session)\n throw new ChatGPTSessionError(\n 'unknown_account',\n 'Selected ChatGPT account is not stored locally',\n );\n observedRefreshToken ??= session.refreshToken;\n if (session.refreshToken !== observedRefreshToken)\n return { kind: 'updated' as const, session };\n const active = disk.refreshes?.[clientId];\n if (active && this.processIsAlive(active.pid))\n return { kind: 'wait' as const };\n disk.refreshes = {\n ...disk.refreshes,\n [clientId]: { id: leaseId, pid: process.pid },\n };\n await this.save(disk);\n return {\n kind: 'owned' as const,\n session,\n generation: this.generation(disk, clientId),\n };\n });\n if (claim.kind === 'updated') return claim.session;\n if (claim.kind === 'owned') {\n current = claim.session;\n generation = claim.generation;\n break;\n }\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n if (!current)\n throw new ChatGPTSessionError(\n 'storage_busy',\n 'Credential refresh is already in progress',\n );\n\n const releaseLease = async () => {\n await this.locked(async () => {\n const disk = await this.disk();\n if (disk.refreshes?.[clientId]?.id === leaseId) {\n delete disk.refreshes[clientId];\n await this.save(disk);\n }\n });\n };\n const invalidate = async (code: string, message: string) => {\n await this.locked(async () => {\n const disk = await this.disk();\n if (\n disk.refreshes?.[clientId]?.id === leaseId &&\n this.generation(disk, clientId) === generation &&\n disk.sessions.find((session) => session.clientId === clientId)\n ?.refreshToken === current.refreshToken\n ) {\n disk.sessions = disk.sessions.filter((s) => s.clientId !== clientId);\n disk.generations = {\n ...disk.generations,\n [clientId]: generation + 1,\n };\n delete disk.refreshes[clientId];\n await this.save(disk);\n }\n });\n throw new ChatGPTSessionError(code, message);\n };\n\n let response: Response;\n try {\n response = await this.fetcher(TOKEN, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: current.clientId,\n refresh_token: current.refreshToken,\n resource: RESOURCE,\n }),\n });\n } catch (error) {\n await releaseLease();\n throw error;\n }\n if (!response.ok) {\n if (response.status === 400 || response.status === 401)\n return invalidate(\n 'refresh_invalid',\n 'Saved ChatGPT session is no longer renewable; sign in again',\n );\n await releaseLease();\n throw new ChatGPTSessionError(\n 'refresh_failed',\n 'ChatGPT session refresh failed; credentials were retained',\n );\n }\n let token: any;\n try {\n token = await response.json();\n } catch (error) {\n await releaseLease();\n throw error;\n }\n if (!token.access_token || !token.refresh_token) {\n await releaseLease();\n throw new ChatGPTSessionError(\n 'invalid_refresh_response',\n 'Refresh response omitted replacement credentials',\n );\n }\n const scopes = String(token.scope ?? current.scopes.join(' '))\n .split(' ')\n .filter(Boolean);\n if (!scopes.includes(PLAN_SCOPE))\n return invalidate(\n 'plan_permission_missing',\n 'ChatGPT plan usage permission was not granted',\n );\n const updated: ChatGPTSession = {\n ...current,\n accessToken: token.access_token,\n refreshToken: token.refresh_token,\n idToken: token.id_token ?? current.idToken,\n scopes,\n expiresAt: this.now() + Number(token.expires_in ?? 0) * 1000,\n };\n return this.locked(async () => {\n const disk = await this.disk();\n const stored = disk.sessions.find((s) => s.clientId === clientId);\n const ownsLease = disk.refreshes?.[clientId]?.id === leaseId;\n if (\n !ownsLease ||\n this.generation(disk, clientId) !== generation ||\n stored?.refreshToken !== current.refreshToken\n ) {\n if (ownsLease) {\n delete disk.refreshes![clientId];\n await this.save(disk);\n }\n throw new ChatGPTSessionError(\n 'session_invalidated',\n 'ChatGPT session changed while refresh was in progress',\n );\n }\n disk.sessions = disk.sessions.map((s) =>\n s.clientId === clientId ? updated : s,\n );\n delete disk.refreshes![clientId];\n await this.save(disk);\n return updated;\n });\n }\n /** Clear local credentials after attempting remote refresh-token revocation. */\n async logout(\n clientId: string,\n ): Promise<{ remoteRevocationConfirmed: boolean }> {\n const current = await this.locked(async () => {\n const disk = await this.disk();\n const current = disk.sessions.find((s) => s.clientId === clientId);\n if (!current) return undefined;\n disk.sessions = disk.sessions.filter((s) => s.clientId !== clientId);\n disk.generations = {\n ...disk.generations,\n [clientId]: this.generation(disk, clientId) + 1,\n };\n if (disk.refreshes) delete disk.refreshes[clientId];\n await this.save(disk);\n return current;\n });\n if (!current) return { remoteRevocationConfirmed: true };\n\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const confirmed = await Promise.race([\n (async () => {\n const discovery = (await (\n await this.fetcher(DISCOVERY, { signal: controller.signal })\n ).json()) as { revocation_endpoint?: string };\n if (!discovery.revocation_endpoint) return false;\n const body = new URLSearchParams({\n token: current.refreshToken,\n token_type_hint: 'refresh_token',\n client_id: current.clientId,\n });\n const response = await this.fetcher(discovery.revocation_endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body,\n signal: controller.signal,\n });\n return response.ok;\n })(),\n new Promise<boolean>((resolve) => {\n timer = setTimeout(() => {\n controller.abort();\n resolve(false);\n }, this.remoteRevocationTimeoutMs);\n }),\n ]);\n return { remoteRevocationConfirmed: confirmed };\n } catch {\n return { remoteRevocationConfirmed: false };\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n}\n"],"mappings":"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"}
package/metadata.json CHANGED
@@ -5,9 +5,9 @@
5
5
  "index": 4,
6
6
  "count": 33
7
7
  },
8
- "description": "Unified authentication interface supporting Keycloak, AWS Cognito, and Nostr with OAuth2/OIDC and public key identity",
8
+ "description": "Unified authentication for Keycloak, Cognito, Nostr, and local ChatGPT sessions with OAuth2/OIDC and public key identity",
9
9
  "provides": [
10
- "Unified authentication interface supporting Keycloak, AWS Cognito, and Nostr with OAuth2/OIDC and public key identity"
10
+ "Unified authentication for Keycloak, Cognito, Nostr, and local ChatGPT sessions with OAuth2/OIDC and public key identity"
11
11
  ],
12
12
  "implements": [],
13
13
  "requires": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@happyvertical/auth",
3
- "version": "0.103.1",
4
- "description": "Unified authentication interface supporting Keycloak, AWS Cognito, and Nostr with OAuth2/OIDC and public key identity",
3
+ "version": "0.103.2",
4
+ "description": "Unified authentication for Keycloak, Cognito, Nostr, and local ChatGPT sessions with OAuth2/OIDC and public key identity",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -9,6 +9,10 @@
9
9
  ".": {
10
10
  "import": "./dist/index.js",
11
11
  "types": "./dist/index.d.ts"
12
+ },
13
+ "./local": {
14
+ "import": "./dist/local.js",
15
+ "types": "./dist/local.d.ts"
12
16
  }
13
17
  },
14
18
  "bin": {
@@ -36,7 +40,7 @@
36
40
  "homepage": "https://github.com/happyvertical/sdk/tree/main/packages/auth#readme",
37
41
  "license": "MIT",
38
42
  "dependencies": {
39
- "@happyvertical/utils": "0.103.1",
43
+ "@happyvertical/utils": "0.103.2",
40
44
  "jose": "^6.2.2"
41
45
  },
42
46
  "optionalDependencies": {