@holeauth/core 0.0.1-alpha.0

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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/cjs-error.cjs +8 -0
  4. package/dist/adapters/index.d.ts +1 -0
  5. package/dist/adapters/index.js +3 -0
  6. package/dist/adapters/index.js.map +1 -0
  7. package/dist/cookies/index.d.ts +3 -0
  8. package/dist/cookies/index.js +74 -0
  9. package/dist/cookies/index.js.map +1 -0
  10. package/dist/errors/index.d.ts +40 -0
  11. package/dist/errors/index.js +70 -0
  12. package/dist/errors/index.js.map +1 -0
  13. package/dist/events/index.d.ts +3 -0
  14. package/dist/events/index.js +52 -0
  15. package/dist/events/index.js.map +1 -0
  16. package/dist/flows/index.d.ts +4 -0
  17. package/dist/flows/index.js +835 -0
  18. package/dist/flows/index.js.map +1 -0
  19. package/dist/index-BIXESLma.d.ts +58 -0
  20. package/dist/index-BYtkmk9_.d.ts +18 -0
  21. package/dist/index-BbEXbI_k.d.ts +116 -0
  22. package/dist/index-BmYQquGs.d.ts +563 -0
  23. package/dist/index-BwEvEa8-.d.ts +20 -0
  24. package/dist/index-CHS-socJ.d.ts +97 -0
  25. package/dist/index-CNtnPdzk.d.ts +136 -0
  26. package/dist/index-CjEXpqaW.d.ts +22 -0
  27. package/dist/index-CotvcK_b.d.ts +42 -0
  28. package/dist/index-D57PvFMN.d.ts +105 -0
  29. package/dist/index-DRN-5E_H.d.ts +26 -0
  30. package/dist/index.d.ts +39 -0
  31. package/dist/index.js +1757 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/jwt/index.d.ts +2 -0
  34. package/dist/jwt/index.js +53 -0
  35. package/dist/jwt/index.js.map +1 -0
  36. package/dist/otp/index.d.ts +1 -0
  37. package/dist/otp/index.js +16 -0
  38. package/dist/otp/index.js.map +1 -0
  39. package/dist/password/index.d.ts +1 -0
  40. package/dist/password/index.js +75 -0
  41. package/dist/password/index.js.map +1 -0
  42. package/dist/plugins/index.d.ts +4 -0
  43. package/dist/plugins/index.js +480 -0
  44. package/dist/plugins/index.js.map +1 -0
  45. package/dist/registry-CZhM1tEB.d.ts +101 -0
  46. package/dist/session/index.d.ts +3 -0
  47. package/dist/session/index.js +346 -0
  48. package/dist/session/index.js.map +1 -0
  49. package/dist/sso/index.d.ts +3 -0
  50. package/dist/sso/index.js +475 -0
  51. package/dist/sso/index.js.map +1 -0
  52. package/package.json +121 -0
@@ -0,0 +1,475 @@
1
+ import { decodeJwt, SignJWT } from 'jose';
2
+
3
+ // src/errors/index.ts
4
+ var HoleauthError = class extends Error {
5
+ code;
6
+ status;
7
+ constructor(code, message, status = 400) {
8
+ super(message);
9
+ this.name = "HoleauthError";
10
+ this.code = code;
11
+ this.status = status;
12
+ }
13
+ };
14
+ var InvalidTokenError = class extends HoleauthError {
15
+ constructor(message = "Invalid token") {
16
+ super("INVALID_TOKEN", message, 401);
17
+ }
18
+ };
19
+ var ProviderError = class extends HoleauthError {
20
+ constructor(message = "Provider error") {
21
+ super("PROVIDER_ERROR", message, 502);
22
+ }
23
+ };
24
+ var AccountConflictError = class extends HoleauthError {
25
+ constructor(message = "Account conflict") {
26
+ super("ACCOUNT_CONFLICT", message, 409);
27
+ }
28
+ };
29
+
30
+ // src/sso/client.ts
31
+ function buildAuthorizeUrl(p) {
32
+ const url = new URL(p.issuerAuthUrl);
33
+ url.searchParams.set("response_type", "code");
34
+ url.searchParams.set("client_id", p.clientId);
35
+ url.searchParams.set("redirect_uri", p.redirectUri);
36
+ url.searchParams.set("scope", (p.scopes ?? ["openid", "email", "profile"]).join(" "));
37
+ url.searchParams.set("state", p.state);
38
+ url.searchParams.set("code_challenge", p.codeChallenge);
39
+ url.searchParams.set("code_challenge_method", "S256");
40
+ if (p.nonce) url.searchParams.set("nonce", p.nonce);
41
+ for (const [k, v] of Object.entries(p.extra ?? {})) url.searchParams.set(k, v);
42
+ return url.toString();
43
+ }
44
+ async function generatePkcePair() {
45
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
46
+ const verifier = base64url(bytes);
47
+ const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
48
+ return { verifier, challenge: base64url(new Uint8Array(hash)) };
49
+ }
50
+ function generateState() {
51
+ return base64url(crypto.getRandomValues(new Uint8Array(16)));
52
+ }
53
+ function generateNonce() {
54
+ return base64url(crypto.getRandomValues(new Uint8Array(16)));
55
+ }
56
+ async function exchangeCode(i) {
57
+ const body = new URLSearchParams({
58
+ grant_type: "authorization_code",
59
+ code: i.code,
60
+ redirect_uri: i.redirectUri,
61
+ client_id: i.clientId,
62
+ client_secret: i.clientSecret,
63
+ code_verifier: i.codeVerifier
64
+ });
65
+ const res = await fetch(i.tokenUrl, {
66
+ method: "POST",
67
+ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
68
+ body
69
+ });
70
+ if (!res.ok) throw new ProviderError(`Token exchange failed: ${res.status}`);
71
+ return await res.json();
72
+ }
73
+ async function fetchUserInfo(userinfoUrl, accessToken) {
74
+ const res = await fetch(userinfoUrl, {
75
+ headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json" }
76
+ });
77
+ if (!res.ok) throw new ProviderError(`Userinfo failed: ${res.status}`);
78
+ return await res.json();
79
+ }
80
+ function base64url(bytes) {
81
+ let s = "";
82
+ for (const b of bytes) s += String.fromCharCode(b);
83
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
84
+ }
85
+
86
+ // src/events/emitter.ts
87
+ var busByConfig = /* @__PURE__ */ new WeakMap();
88
+ function getBus(cfg) {
89
+ let bus = busByConfig.get(cfg);
90
+ if (!bus) {
91
+ bus = { byType: /* @__PURE__ */ new Map(), wildcard: /* @__PURE__ */ new Set() };
92
+ busByConfig.set(cfg, bus);
93
+ }
94
+ return bus;
95
+ }
96
+ async function emit(cfg, event) {
97
+ const withTimestamp = { at: /* @__PURE__ */ new Date(), ...event };
98
+ await cfg.adapters.auditLog.record(withTimestamp);
99
+ const bus = getBus(cfg);
100
+ const typed = bus.byType.get(withTimestamp.type);
101
+ const fire = (h) => {
102
+ Promise.resolve().then(() => h(withTimestamp)).catch(() => {
103
+ });
104
+ };
105
+ if (typed) for (const h of typed) fire(h);
106
+ for (const h of bus.wildcard) fire(h);
107
+ if (cfg.onEvent) {
108
+ Promise.resolve().then(() => cfg.onEvent?.(withTimestamp)).catch(() => {
109
+ });
110
+ }
111
+ }
112
+
113
+ // src/sso/authorize.ts
114
+ function findProvider(cfg, id) {
115
+ const p = cfg.providers?.find((x) => x.id === id);
116
+ if (!p) throw new ProviderError(`unknown provider: ${id}`);
117
+ return p;
118
+ }
119
+ async function authorize(cfg, providerId) {
120
+ const p = findProvider(cfg, providerId);
121
+ const state = generateState();
122
+ const { verifier, challenge } = await generatePkcePair();
123
+ const nonce = p.kind === "oidc" ? generateNonce() : void 0;
124
+ const url = buildAuthorizeUrl({
125
+ issuerAuthUrl: p.authorizationUrl,
126
+ clientId: p.clientId,
127
+ redirectUri: p.redirectUri,
128
+ scopes: p.scopes,
129
+ state,
130
+ codeChallenge: challenge,
131
+ nonce
132
+ });
133
+ await emit(cfg, { type: "sso.authorize", data: { provider: providerId } });
134
+ return { url, state, codeVerifier: verifier, nonce };
135
+ }
136
+ function toKey(secret) {
137
+ return typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
138
+ }
139
+ async function sign(payload, secret, opts = {}) {
140
+ const jwt = new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt();
141
+ if (opts.issuer) jwt.setIssuer(opts.issuer);
142
+ if (opts.audience) jwt.setAudience(opts.audience);
143
+ if (opts.subject) jwt.setSubject(opts.subject);
144
+ if (opts.jti) jwt.setJti(opts.jti);
145
+ if (opts.expiresIn !== void 0) jwt.setExpirationTime(opts.expiresIn);
146
+ return jwt.sign(toKey(secret));
147
+ }
148
+ function decode(token) {
149
+ try {
150
+ return decodeJwt(token);
151
+ } catch (e) {
152
+ throw new InvalidTokenError(e.message);
153
+ }
154
+ }
155
+
156
+ // src/cookies/csrf.ts
157
+ var b64urlChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
158
+ function generateCsrfToken() {
159
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
160
+ let out = "";
161
+ for (const b of bytes) out += b64urlChars[b % 64];
162
+ return out;
163
+ }
164
+
165
+ // src/plugins/runner-ref.ts
166
+ var runners = /* @__PURE__ */ new WeakMap();
167
+ var NOOP = {
168
+ async runRegisterBefore() {
169
+ },
170
+ async runRegisterAfter() {
171
+ },
172
+ async runSignInBefore() {
173
+ },
174
+ async runSignInChallenge() {
175
+ return null;
176
+ },
177
+ async runSignInAfter() {
178
+ },
179
+ async runSignOutAfter() {
180
+ },
181
+ async runRefreshBefore() {
182
+ },
183
+ async runRefreshAfter() {
184
+ },
185
+ async runPasswordChangeBefore() {
186
+ },
187
+ async runPasswordChangeAfter() {
188
+ },
189
+ async runPasswordResetBefore() {
190
+ },
191
+ async runPasswordResetAfter() {
192
+ },
193
+ async runUserUpdateAfter() {
194
+ },
195
+ async runUserDeleteAfter() {
196
+ },
197
+ async runSessionIssue() {
198
+ },
199
+ async runSessionRotate() {
200
+ },
201
+ async runSessionRevoke() {
202
+ }
203
+ };
204
+ function getHookRunner(cfg) {
205
+ return runners.get(cfg) ?? NOOP;
206
+ }
207
+
208
+ // src/session/hash.ts
209
+ async function sha256b64url(input) {
210
+ const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
211
+ const bytes = new Uint8Array(buf);
212
+ let s = "";
213
+ for (const b of bytes) s += String.fromCharCode(b);
214
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
215
+ }
216
+
217
+ // src/session/issue.ts
218
+ var ACCESS_DEFAULT = 900;
219
+ var REFRESH_DEFAULT = 2592e3;
220
+ async function issueSession(cfg, input) {
221
+ const accessTtl = cfg.tokens?.accessTtl ?? ACCESS_DEFAULT;
222
+ const refreshTtl = cfg.tokens?.refreshTtl ?? REFRESH_DEFAULT;
223
+ const now = Math.floor(Date.now() / 1e3);
224
+ const familyId = input.familyId ?? crypto.randomUUID();
225
+ const sessionId = crypto.randomUUID();
226
+ const nonce = crypto.randomUUID();
227
+ const [accessToken, refreshToken] = await Promise.all([
228
+ sign(
229
+ { sid: sessionId, sub: input.userId, fam: familyId, nce: nonce },
230
+ cfg.secrets.jwtSecret,
231
+ { expiresIn: `${accessTtl}s` }
232
+ ),
233
+ sign(
234
+ { sid: sessionId, sub: input.userId, fam: familyId, typ: "refresh", nce: nonce },
235
+ cfg.secrets.jwtSecret,
236
+ { expiresIn: `${refreshTtl}s`, jti: nonce }
237
+ )
238
+ ]);
239
+ const refreshTokenHash = await sha256b64url(refreshToken);
240
+ await cfg.adapters.session.createSession({
241
+ id: sessionId,
242
+ userId: input.userId,
243
+ familyId,
244
+ refreshTokenHash,
245
+ expiresAt: new Date((now + refreshTtl) * 1e3),
246
+ createdAt: /* @__PURE__ */ new Date(),
247
+ revokedAt: null,
248
+ ip: input.ip ?? null,
249
+ userAgent: input.userAgent ?? null
250
+ });
251
+ const csrfToken = generateCsrfToken();
252
+ await emit(cfg, {
253
+ type: "session.created",
254
+ userId: input.userId,
255
+ sessionId,
256
+ ip: input.ip ?? null,
257
+ userAgent: input.userAgent ?? null,
258
+ data: { familyId }
259
+ });
260
+ await getHookRunner(cfg).runSessionIssue({ userId: input.userId, sessionId, familyId });
261
+ return {
262
+ accessToken,
263
+ refreshToken,
264
+ csrfToken,
265
+ sessionId,
266
+ familyId,
267
+ accessExpiresAt: (now + accessTtl) * 1e3,
268
+ refreshExpiresAt: (now + refreshTtl) * 1e3
269
+ };
270
+ }
271
+
272
+ // src/sso/callback.ts
273
+ async function exchangeAndProfile(p, input) {
274
+ const tokens = await exchangeCode({
275
+ tokenUrl: p.tokenUrl,
276
+ clientId: p.clientId,
277
+ clientSecret: p.clientSecret,
278
+ redirectUri: p.redirectUri,
279
+ code: input.code,
280
+ codeVerifier: input.codeVerifier
281
+ });
282
+ const accessToken = typeof tokens.access_token === "string" ? tokens.access_token : null;
283
+ if (!accessToken) throw new ProviderError("no access_token in response");
284
+ if (p.kind === "oidc") {
285
+ const idToken = typeof tokens.id_token === "string" ? tokens.id_token : null;
286
+ if (idToken) {
287
+ const claims = decode(idToken);
288
+ if (claims.sub && claims.email) {
289
+ return {
290
+ tokens,
291
+ profile: {
292
+ providerAccountId: claims.sub,
293
+ email: claims.email.toLowerCase(),
294
+ name: claims.name ?? null,
295
+ image: claims.picture ?? null,
296
+ emailVerified: claims.email_verified ?? false
297
+ }
298
+ };
299
+ }
300
+ }
301
+ const ui = await fetchUserInfo(p.userinfoUrl, accessToken);
302
+ const sub = ui.sub ?? "";
303
+ const email2 = typeof ui.email === "string" ? ui.email.toLowerCase() : "";
304
+ if (!sub || !email2) throw new ProviderError("userinfo missing sub/email");
305
+ return {
306
+ tokens,
307
+ profile: {
308
+ providerAccountId: sub,
309
+ email: email2,
310
+ name: ui.name ?? null,
311
+ image: ui.picture ?? null,
312
+ emailVerified: Boolean(ui.email_verified)
313
+ }
314
+ };
315
+ }
316
+ const raw = await fetchUserInfo(p.userinfoUrl, accessToken);
317
+ const mapped = p.profile(raw);
318
+ if (!mapped.providerAccountId) throw new ProviderError("provider profile missing id");
319
+ let email = mapped.email.toLowerCase();
320
+ let verified = false;
321
+ if (!email && p.id === "github") {
322
+ const emails = await fetchUserInfo("https://api.github.com/user/emails", accessToken);
323
+ const primary = Array.isArray(emails) ? emails.find((e) => e.primary) : void 0;
324
+ if (primary) {
325
+ email = primary.email.toLowerCase();
326
+ verified = primary.verified;
327
+ }
328
+ }
329
+ if (!email) throw new ProviderError("provider did not return an email");
330
+ return {
331
+ tokens,
332
+ profile: {
333
+ providerAccountId: mapped.providerAccountId,
334
+ email,
335
+ name: mapped.name ?? null,
336
+ image: mapped.image ?? null,
337
+ emailVerified: verified
338
+ }
339
+ };
340
+ }
341
+ async function resolveUser(cfg, provider, profile, rawTokens) {
342
+ const account = cfg.adapters.account;
343
+ if (account) {
344
+ const existing = await account.getAccountByProvider(provider.id, profile.providerAccountId);
345
+ if (existing) {
346
+ const u = await cfg.adapters.user.getUserById(existing.userId);
347
+ if (u) return u;
348
+ }
349
+ }
350
+ const byEmail = await cfg.adapters.user.getUserByEmail(profile.email);
351
+ if (byEmail) {
352
+ if (!cfg.allowDangerousEmailAccountLinking || !profile.emailVerified) {
353
+ throw new AccountConflictError(
354
+ `an account with email ${profile.email} already exists; enable allowDangerousEmailAccountLinking or sign in with password first`
355
+ );
356
+ }
357
+ if (account) {
358
+ const linked = await account.linkAccount({
359
+ userId: byEmail.id,
360
+ provider: provider.id,
361
+ providerAccountId: profile.providerAccountId,
362
+ email: profile.email,
363
+ accessToken: rawTokens.access_token ?? null,
364
+ refreshToken: rawTokens.refresh_token ?? null,
365
+ idToken: rawTokens.id_token ?? null,
366
+ tokenType: rawTokens.token_type ?? null,
367
+ scope: rawTokens.scope ?? null,
368
+ expiresAt: typeof rawTokens.expires_in === "number" ? new Date(Date.now() + rawTokens.expires_in * 1e3) : null
369
+ });
370
+ await emit(cfg, {
371
+ type: "account.linked",
372
+ userId: byEmail.id,
373
+ data: { provider: provider.id, accountId: linked.id, auto: true }
374
+ });
375
+ }
376
+ return byEmail;
377
+ }
378
+ const user = await cfg.adapters.user.createUser({
379
+ email: profile.email,
380
+ name: profile.name ?? null,
381
+ image: profile.image ?? null,
382
+ emailVerified: profile.emailVerified ? /* @__PURE__ */ new Date() : null
383
+ });
384
+ if (account) {
385
+ const linked = await account.linkAccount({
386
+ userId: user.id,
387
+ provider: provider.id,
388
+ providerAccountId: profile.providerAccountId,
389
+ email: profile.email,
390
+ accessToken: rawTokens.access_token ?? null,
391
+ refreshToken: rawTokens.refresh_token ?? null,
392
+ idToken: rawTokens.id_token ?? null,
393
+ tokenType: rawTokens.token_type ?? null,
394
+ scope: rawTokens.scope ?? null,
395
+ expiresAt: typeof rawTokens.expires_in === "number" ? new Date(Date.now() + rawTokens.expires_in * 1e3) : null
396
+ });
397
+ await emit(cfg, {
398
+ type: "account.linked",
399
+ userId: user.id,
400
+ data: { provider: provider.id, accountId: linked.id, onCreate: true }
401
+ });
402
+ }
403
+ return user;
404
+ }
405
+ async function callback(cfg, providerId, input) {
406
+ const p = findProvider(cfg, providerId);
407
+ try {
408
+ const { tokens: rawTokens, profile } = await exchangeAndProfile(p, input);
409
+ const user = await resolveUser(cfg, p, profile, rawTokens);
410
+ const tokens = await issueSession(cfg, {
411
+ userId: user.id,
412
+ ip: input.ip ?? null,
413
+ userAgent: input.userAgent ?? null
414
+ });
415
+ await emit(cfg, {
416
+ type: "sso.callback_ok",
417
+ userId: user.id,
418
+ sessionId: tokens.sessionId,
419
+ data: { provider: providerId }
420
+ });
421
+ return { user, tokens };
422
+ } catch (e) {
423
+ await emit(cfg, {
424
+ type: "sso.callback_failed",
425
+ data: { provider: providerId, error: e.message }
426
+ });
427
+ throw e;
428
+ }
429
+ }
430
+
431
+ // src/sso/providers/google.ts
432
+ function GoogleProvider(opts) {
433
+ return {
434
+ kind: "oidc",
435
+ id: opts.id ?? "google",
436
+ name: "Google",
437
+ clientId: opts.clientId,
438
+ clientSecret: opts.clientSecret,
439
+ redirectUri: opts.redirectUri,
440
+ scopes: opts.scopes ?? ["openid", "email", "profile"],
441
+ issuer: "https://accounts.google.com",
442
+ authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
443
+ tokenUrl: "https://oauth2.googleapis.com/token",
444
+ userinfoUrl: "https://openidconnect.googleapis.com/v1/userinfo"
445
+ };
446
+ }
447
+
448
+ // src/sso/providers/github.ts
449
+ function GithubProvider(opts) {
450
+ return {
451
+ kind: "oauth2",
452
+ id: opts.id ?? "github",
453
+ name: "GitHub",
454
+ clientId: opts.clientId,
455
+ clientSecret: opts.clientSecret,
456
+ redirectUri: opts.redirectUri,
457
+ scopes: opts.scopes ?? ["read:user", "user:email"],
458
+ authorizationUrl: "https://github.com/login/oauth/authorize",
459
+ tokenUrl: "https://github.com/login/oauth/access_token",
460
+ userinfoUrl: "https://api.github.com/user",
461
+ profile: (raw) => {
462
+ const p = raw ?? {};
463
+ return {
464
+ providerAccountId: String(p.id ?? p.login ?? ""),
465
+ email: p.email ?? "",
466
+ name: p.name ?? p.login ?? null,
467
+ image: p.avatar_url ?? null
468
+ };
469
+ }
470
+ };
471
+ }
472
+
473
+ export { GithubProvider, GoogleProvider, authorize, base64url, buildAuthorizeUrl, callback, exchangeCode, fetchUserInfo, findProvider, generateNonce, generatePkcePair, generateState };
474
+ //# sourceMappingURL=index.js.map
475
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/errors/index.ts","../../src/sso/client.ts","../../src/events/emitter.ts","../../src/sso/authorize.ts","../../src/jwt/index.ts","../../src/cookies/csrf.ts","../../src/plugins/runner-ref.ts","../../src/session/hash.ts","../../src/session/issue.ts","../../src/sso/callback.ts","../../src/sso/providers/google.ts","../../src/sso/providers/github.ts"],"names":["email"],"mappings":";;;AAAO,IAAM,aAAA,GAAN,cAA4B,KAAA,CAAM;AAAA,EAC9B,IAAA;AAAA,EACA,MAAA;AAAA,EACT,WAAA,CAAY,IAAA,EAAc,OAAA,EAAiB,MAAA,GAAS,GAAA,EAAK;AACvD,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AACF,CAAA;AACO,IAAM,iBAAA,GAAN,cAAgC,aAAA,CAAc;AAAA,EACnD,WAAA,CAAY,UAAU,eAAA,EAAiB;AAAE,IAAA,KAAA,CAAM,eAAA,EAAiB,SAAS,GAAG,CAAA;AAAA,EAAG;AACjF,CAAA;AAOO,IAAM,aAAA,GAAN,cAA4B,aAAA,CAAc;AAAA,EAC/C,WAAA,CAAY,UAAU,gBAAA,EAAkB;AAAE,IAAA,KAAA,CAAM,gBAAA,EAAkB,SAAS,GAAG,CAAA;AAAA,EAAG;AACnF,CAAA;AAOO,IAAM,oBAAA,GAAN,cAAmC,aAAA,CAAc;AAAA,EACtD,WAAA,CAAY,UAAU,kBAAA,EAAoB;AAAE,IAAA,KAAA,CAAM,kBAAA,EAAoB,SAAS,GAAG,CAAA;AAAA,EAAG;AACvF,CAAA;;;ACZO,SAAS,kBAAkB,CAAA,EAA4B;AAC5D,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,CAAA,CAAE,aAAa,CAAA;AACnC,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,eAAA,EAAiB,MAAM,CAAA;AAC5C,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,WAAA,EAAa,CAAA,CAAE,QAAQ,CAAA;AAC5C,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,cAAA,EAAgB,CAAA,CAAE,WAAW,CAAA;AAClD,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,OAAA,EAAA,CAAU,CAAA,CAAE,MAAA,IAAU,CAAC,QAAA,EAAU,OAAA,EAAS,SAAS,CAAA,EAAG,IAAA,CAAK,GAAG,CAAC,CAAA;AACpF,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,OAAA,EAAS,CAAA,CAAE,KAAK,CAAA;AACrC,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,gBAAA,EAAkB,CAAA,CAAE,aAAa,CAAA;AACtD,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,uBAAA,EAAyB,MAAM,CAAA;AACpD,EAAA,IAAI,EAAE,KAAA,EAAO,GAAA,CAAI,aAAa,GAAA,CAAI,OAAA,EAAS,EAAE,KAAK,CAAA;AAClD,EAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,CAAA,IAAK,OAAO,OAAA,CAAQ,CAAA,CAAE,KAAA,IAAS,EAAE,CAAA,EAAG,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,GAAG,CAAC,CAAA;AAC7E,EAAA,OAAO,IAAI,QAAA,EAAS;AACtB;AAEA,eAAsB,gBAAA,GAAqE;AACzF,EAAA,MAAM,QAAQ,MAAA,CAAO,eAAA,CAAgB,IAAI,UAAA,CAAW,EAAE,CAAC,CAAA;AACvD,EAAA,MAAM,QAAA,GAAW,UAAU,KAAK,CAAA;AAChC,EAAA,MAAM,IAAA,GAAO,MAAM,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,QAAQ,CAAC,CAAA;AACrF,EAAA,OAAO,EAAE,UAAU,SAAA,EAAW,SAAA,CAAU,IAAI,UAAA,CAAW,IAAI,CAAC,CAAA,EAAE;AAChE;AAEO,SAAS,aAAA,GAAwB;AACtC,EAAA,OAAO,UAAU,MAAA,CAAO,eAAA,CAAgB,IAAI,UAAA,CAAW,EAAE,CAAC,CAAC,CAAA;AAC7D;AAEO,SAAS,aAAA,GAAwB;AACtC,EAAA,OAAO,UAAU,MAAA,CAAO,eAAA,CAAgB,IAAI,UAAA,CAAW,EAAE,CAAC,CAAC,CAAA;AAC7D;AAWA,eAAsB,aAAa,CAAA,EAAyD;AAC1F,EAAA,MAAM,IAAA,GAAO,IAAI,eAAA,CAAgB;AAAA,IAC/B,UAAA,EAAY,oBAAA;AAAA,IACZ,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,cAAc,CAAA,CAAE,WAAA;AAAA,IAChB,WAAW,CAAA,CAAE,QAAA;AAAA,IACb,eAAe,CAAA,CAAE,YAAA;AAAA,IACjB,eAAe,CAAA,CAAE;AAAA,GAClB,CAAA;AACD,EAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,CAAA,CAAE,QAAA,EAAU;AAAA,IAClC,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS,EAAE,cAAA,EAAgB,mCAAA,EAAqC,QAAQ,kBAAA,EAAmB;AAAA,IAC3F;AAAA,GACD,CAAA;AACD,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,aAAA,CAAc,CAAA,uBAAA,EAA0B,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAC3E,EAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AACzB;AAEA,eAAsB,aAAA,CACpB,aACA,WAAA,EACkC;AAClC,EAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,WAAA,EAAa;AAAA,IACnC,SAAS,EAAE,aAAA,EAAe,UAAU,WAAW,CAAA,CAAA,EAAI,QAAQ,kBAAA;AAAmB,GAC/E,CAAA;AACD,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,aAAA,CAAc,CAAA,iBAAA,EAAoB,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACrE,EAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AACzB;AAEO,SAAS,UAAU,KAAA,EAA2B;AACnD,EAAA,IAAI,CAAA,GAAI,EAAA;AAAI,EAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,CAAA,IAAK,MAAA,CAAO,aAAa,CAAC,CAAA;AAC7D,EAAA,OAAO,IAAA,CAAK,CAAC,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC1E;;;AC9EA,IAAM,WAAA,uBAAkB,OAAA,EAAkC;AAE1D,SAAS,OAAO,GAAA,EAA+B;AAC7C,EAAA,IAAI,GAAA,GAAM,WAAA,CAAY,GAAA,CAAI,GAAG,CAAA;AAC7B,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,GAAA,GAAM,EAAE,wBAAQ,IAAI,GAAA,IAAO,QAAA,kBAAU,IAAI,KAAI,EAAE;AAC/C,IAAA,WAAA,CAAY,GAAA,CAAI,KAAK,GAAG,CAAA;AAAA,EAC1B;AACA,EAAA,OAAO,GAAA;AACT;AAmCA,eAAsB,IAAA,CAAK,KAAqB,KAAA,EAAqC;AACnF,EAAA,MAAM,gBAA+B,EAAE,EAAA,sBAAQ,IAAA,EAAK,EAAG,GAAG,KAAA,EAAM;AAChE,EAAA,MAAM,GAAA,CAAI,QAAA,CAAS,QAAA,CAAS,MAAA,CAAO,aAAa,CAAA;AAEhD,EAAA,MAAM,GAAA,GAAM,OAAO,GAAG,CAAA;AACtB,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,cAAc,IAAI,CAAA;AAC/C,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAe;AAC3B,IAAA,OAAA,CAAQ,OAAA,GACL,IAAA,CAAK,MAAM,EAAE,aAAa,CAAC,CAAA,CAC3B,KAAA,CAAM,MAAM;AAAA,IAAyC,CAAC,CAAA;AAAA,EAC3D,CAAA;AACA,EAAA,IAAI,KAAA,EAAO,KAAA,MAAW,CAAA,IAAK,KAAA,OAAY,CAAC,CAAA;AACxC,EAAA,KAAA,MAAW,CAAA,IAAK,GAAA,CAAI,QAAA,EAAU,IAAA,CAAK,CAAC,CAAA;AAEpC,EAAA,IAAI,IAAI,OAAA,EAAS;AACf,IAAA,OAAA,CAAQ,OAAA,EAAQ,CACb,IAAA,CAAK,MAAM,GAAA,CAAI,UAAU,aAAa,CAAC,CAAA,CACvC,KAAA,CAAM,MAAM;AAAA,IAAyC,CAAC,CAAA;AAAA,EAC3D;AACF;;;ACpEO,SAAS,YAAA,CAAa,KAAqB,EAAA,EAA4B;AAC5E,EAAA,MAAM,CAAA,GAAI,IAAI,SAAA,EAAW,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAChD,EAAA,IAAI,CAAC,CAAA,EAAG,MAAM,IAAI,aAAA,CAAc,CAAA,kBAAA,EAAqB,EAAE,CAAA,CAAE,CAAA;AACzD,EAAA,OAAO,CAAA;AACT;AAOA,eAAsB,SAAA,CACpB,KACA,UAAA,EAC+E;AAC/E,EAAA,MAAM,CAAA,GAAI,YAAA,CAAa,GAAA,EAAK,UAAU,CAAA;AACtC,EAAA,MAAM,QAAQ,aAAA,EAAc;AAC5B,EAAA,MAAM,EAAE,QAAA,EAAU,SAAA,EAAU,GAAI,MAAM,gBAAA,EAAiB;AACvD,EAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,IAAA,KAAS,MAAA,GAAS,eAAc,GAAI,MAAA;AAEpD,EAAA,MAAM,MAAM,iBAAA,CAAkB;AAAA,IAC5B,eAAe,CAAA,CAAE,gBAAA;AAAA,IACjB,UAAU,CAAA,CAAE,QAAA;AAAA,IACZ,aAAa,CAAA,CAAE,WAAA;AAAA,IACf,QAAQ,CAAA,CAAE,MAAA;AAAA,IACV,KAAA;AAAA,IACA,aAAA,EAAe,SAAA;AAAA,IACf;AAAA,GACD,CAAA;AAED,EAAA,MAAM,IAAA,CAAK,GAAA,EAAK,EAAE,IAAA,EAAM,eAAA,EAAiB,MAAM,EAAE,QAAA,EAAU,UAAA,EAAW,EAAG,CAAA;AACzE,EAAA,OAAO,EAAE,GAAA,EAAK,KAAA,EAAO,YAAA,EAAc,UAAU,KAAA,EAAM;AACrD;AClCA,SAAS,MAAM,MAAA,EAAyC;AACtD,EAAA,OAAO,OAAO,WAAW,QAAA,GAAW,IAAI,aAAY,CAAE,MAAA,CAAO,MAAM,CAAA,GAAI,MAAA;AACzE;AAUA,eAAsB,IAAA,CACpB,OAAA,EACA,MAAA,EACA,IAAA,GAAoB,EAAC,EACJ;AACjB,EAAA,MAAM,GAAA,GAAM,IAAI,OAAA,CAAQ,OAAO,CAAA,CAAE,kBAAA,CAAmB,EAAE,GAAA,EAAK,OAAA,EAAS,CAAA,CAAE,WAAA,EAAY;AAClF,EAAA,IAAI,IAAA,CAAK,MAAA,EAAQ,GAAA,CAAI,SAAA,CAAU,KAAK,MAAM,CAAA;AAC1C,EAAA,IAAI,IAAA,CAAK,QAAA,EAAU,GAAA,CAAI,WAAA,CAAY,KAAK,QAAQ,CAAA;AAChD,EAAA,IAAI,IAAA,CAAK,OAAA,EAAS,GAAA,CAAI,UAAA,CAAW,KAAK,OAAO,CAAA;AAC7C,EAAA,IAAI,IAAA,CAAK,GAAA,EAAK,GAAA,CAAI,MAAA,CAAO,KAAK,GAAG,CAAA;AACjC,EAAA,IAAI,KAAK,SAAA,KAAc,MAAA,EAAW,GAAA,CAAI,iBAAA,CAAkB,KAAK,SAAS,CAAA;AACtE,EAAA,OAAO,GAAA,CAAI,IAAA,CAAK,KAAA,CAAM,MAAM,CAAC,CAAA;AAC/B;AAcO,SAAS,OAA0C,KAAA,EAAkB;AAC1E,EAAA,IAAI;AACF,IAAA,OAAO,UAAU,KAAK,CAAA;AAAA,EACxB,SAAS,CAAA,EAAG;AACV,IAAA,MAAM,IAAI,iBAAA,CAAmB,CAAA,CAAY,OAAO,CAAA;AAAA,EAClD;AACF;;;ACvCA,IAAM,WAAA,GAAc,kEAAA;AAEb,SAAS,iBAAA,GAA4B;AAC1C,EAAA,MAAM,QAAQ,MAAA,CAAO,eAAA,CAAgB,IAAI,UAAA,CAAW,EAAE,CAAC,CAAA;AACvD,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,GAAA,IAAO,WAAA,CAAY,IAAI,EAAE,CAAA;AAChD,EAAA,OAAO,GAAA;AACT;;;ACLA,IAAM,OAAA,uBAAc,OAAA,EAAoC;AAMxD,IAAM,IAAA,GAAmB;AAAA,EACvB,MAAM,iBAAA,GAAoB;AAAA,EAAC,CAAA;AAAA,EAC3B,MAAM,gBAAA,GAAmB;AAAA,EAAC,CAAA;AAAA,EAC1B,MAAM,eAAA,GAAkB;AAAA,EAAC,CAAA;AAAA,EACzB,MAAM,kBAAA,GAAqB;AAAE,IAAA,OAAO,IAAA;AAAA,EAAM,CAAA;AAAA,EAC1C,MAAM,cAAA,GAAiB;AAAA,EAAC,CAAA;AAAA,EACxB,MAAM,eAAA,GAAkB;AAAA,EAAC,CAAA;AAAA,EACzB,MAAM,gBAAA,GAAmB;AAAA,EAAC,CAAA;AAAA,EAC1B,MAAM,eAAA,GAAkB;AAAA,EAAC,CAAA;AAAA,EACzB,MAAM,uBAAA,GAA0B;AAAA,EAAC,CAAA;AAAA,EACjC,MAAM,sBAAA,GAAyB;AAAA,EAAC,CAAA;AAAA,EAChC,MAAM,sBAAA,GAAyB;AAAA,EAAC,CAAA;AAAA,EAChC,MAAM,qBAAA,GAAwB;AAAA,EAAC,CAAA;AAAA,EAC/B,MAAM,kBAAA,GAAqB;AAAA,EAAC,CAAA;AAAA,EAC5B,MAAM,kBAAA,GAAqB;AAAA,EAAC,CAAA;AAAA,EAC5B,MAAM,eAAA,GAAkB;AAAA,EAAC,CAAA;AAAA,EACzB,MAAM,gBAAA,GAAmB;AAAA,EAAC,CAAA;AAAA,EAC1B,MAAM,gBAAA,GAAmB;AAAA,EAAC;AAC5B,CAAA;AAEO,SAAS,cAAc,GAAA,EAAiC;AAC7D,EAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,IAAK,IAAA;AAC7B;;;ACrCA,eAAsB,aAAa,KAAA,EAAgC;AACjE,EAAA,MAAM,GAAA,GAAM,MAAM,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAC,CAAA;AACjF,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,GAAG,CAAA;AAChC,EAAA,IAAI,CAAA,GAAI,EAAA;AACR,EAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,CAAA,IAAK,MAAA,CAAO,aAAa,CAAC,CAAA;AACjD,EAAA,OAAO,IAAA,CAAK,CAAC,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC1E;;;ACAA,IAAM,cAAA,GAAiB,GAAA;AACvB,IAAM,eAAA,GAAkB,MAAA;AAcxB,eAAsB,YAAA,CAAa,KAAqB,KAAA,EAA0C;AAChG,EAAA,MAAM,SAAA,GAAY,GAAA,CAAI,MAAA,EAAQ,SAAA,IAAa,cAAA;AAC3C,EAAA,MAAM,UAAA,GAAa,GAAA,CAAI,MAAA,EAAQ,UAAA,IAAc,eAAA;AAC7C,EAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AAExC,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,QAAA,IAAY,MAAA,CAAO,UAAA,EAAW;AACrD,EAAA,MAAM,SAAA,GAAY,OAAO,UAAA,EAAW;AACpC,EAAA,MAAM,KAAA,GAAQ,OAAO,UAAA,EAAW;AAEhC,EAAA,MAAM,CAAC,WAAA,EAAa,YAAY,CAAA,GAAI,MAAM,QAAQ,GAAA,CAAI;AAAA,IACpD,IAAA;AAAA,MACE,EAAE,KAAK,SAAA,EAAW,GAAA,EAAK,MAAM,MAAA,EAAQ,GAAA,EAAK,QAAA,EAAU,GAAA,EAAK,KAAA,EAAM;AAAA,MAC/D,IAAI,OAAA,CAAQ,SAAA;AAAA,MACZ,EAAE,SAAA,EAAW,CAAA,EAAG,SAAS,CAAA,CAAA,CAAA;AAAI,KAC/B;AAAA,IACA,IAAA;AAAA,MACE,EAAE,GAAA,EAAK,SAAA,EAAW,GAAA,EAAK,KAAA,CAAM,MAAA,EAAQ,GAAA,EAAK,QAAA,EAAU,GAAA,EAAK,SAAA,EAAW,GAAA,EAAK,KAAA,EAAM;AAAA,MAC/E,IAAI,OAAA,CAAQ,SAAA;AAAA,MACZ,EAAE,SAAA,EAAW,CAAA,EAAG,UAAU,CAAA,CAAA,CAAA,EAAK,KAAK,KAAA;AAAM;AAC5C,GACD,CAAA;AACD,EAAA,MAAM,gBAAA,GAAmB,MAAM,YAAA,CAAa,YAAY,CAAA;AAExD,EAAA,MAAM,GAAA,CAAI,QAAA,CAAS,OAAA,CAAQ,aAAA,CAAc;AAAA,IACvC,EAAA,EAAI,SAAA;AAAA,IACJ,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,QAAA;AAAA,IACA,gBAAA;AAAA,IACA,SAAA,EAAW,IAAI,IAAA,CAAA,CAAM,GAAA,GAAM,cAAc,GAAI,CAAA;AAAA,IAC7C,SAAA,sBAAe,IAAA,EAAK;AAAA,IACpB,SAAA,EAAW,IAAA;AAAA,IACX,EAAA,EAAI,MAAM,EAAA,IAAM,IAAA;AAAA,IAChB,SAAA,EAAW,MAAM,SAAA,IAAa;AAAA,GAC/B,CAAA;AAED,EAAA,MAAM,YAAY,iBAAA,EAAkB;AAEpC,EAAA,MAAM,KAAK,GAAA,EAAK;AAAA,IACd,IAAA,EAAM,iBAAA;AAAA,IACN,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,SAAA;AAAA,IACA,EAAA,EAAI,MAAM,EAAA,IAAM,IAAA;AAAA,IAChB,SAAA,EAAW,MAAM,SAAA,IAAa,IAAA;AAAA,IAC9B,IAAA,EAAM,EAAE,QAAA;AAAS,GAClB,CAAA;AACD,EAAA,MAAM,aAAA,CAAc,GAAG,CAAA,CAAE,eAAA,CAAgB,EAAE,QAAQ,KAAA,CAAM,MAAA,EAAQ,SAAA,EAAW,QAAA,EAAU,CAAA;AAEtF,EAAA,OAAO;AAAA,IACL,WAAA;AAAA,IACA,YAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAA;AAAA,IACA,QAAA;AAAA,IACA,eAAA,EAAA,CAAkB,MAAM,SAAA,IAAa,GAAA;AAAA,IACrC,gBAAA,EAAA,CAAmB,MAAM,UAAA,IAAc;AAAA,GACzC;AACF;;;ACrDA,eAAe,kBAAA,CACb,GACA,KAAA,EAC0E;AAC1E,EAAA,MAAM,MAAA,GAAS,MAAM,YAAA,CAAa;AAAA,IAChC,UAAU,CAAA,CAAE,QAAA;AAAA,IACZ,UAAU,CAAA,CAAE,QAAA;AAAA,IACZ,cAAc,CAAA,CAAE,YAAA;AAAA,IAChB,aAAa,CAAA,CAAE,WAAA;AAAA,IACf,MAAM,KAAA,CAAM,IAAA;AAAA,IACZ,cAAc,KAAA,CAAM;AAAA,GACrB,CAAA;AAED,EAAA,MAAM,cAAc,OAAO,MAAA,CAAO,YAAA,KAAiB,QAAA,GAAW,OAAO,YAAA,GAAe,IAAA;AACpF,EAAA,IAAI,CAAC,WAAA,EAAa,MAAM,IAAI,cAAc,6BAA6B,CAAA;AAEvE,EAAA,IAAI,CAAA,CAAE,SAAS,MAAA,EAAQ;AACrB,IAAA,MAAM,UAAU,OAAO,MAAA,CAAO,QAAA,KAAa,QAAA,GAAW,OAAO,QAAA,GAAW,IAAA;AACxE,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,MAAM,MAAA,GAAS,OAMZ,OAAO,CAAA;AACV,MAAA,IAAI,MAAA,CAAO,GAAA,IAAO,MAAA,CAAO,KAAA,EAAO;AAC9B,QAAA,OAAO;AAAA,UACL,MAAA;AAAA,UACA,OAAA,EAAS;AAAA,YACP,mBAAmB,MAAA,CAAO,GAAA;AAAA,YAC1B,KAAA,EAAO,MAAA,CAAO,KAAA,CAAM,WAAA,EAAY;AAAA,YAChC,IAAA,EAAM,OAAO,IAAA,IAAQ,IAAA;AAAA,YACrB,KAAA,EAAO,OAAO,OAAA,IAAW,IAAA;AAAA,YACzB,aAAA,EAAe,OAAO,cAAA,IAAkB;AAAA;AAC1C,SACF;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,EAAA,GAAK,MAAM,aAAA,CAAc,CAAA,CAAE,aAAa,WAAW,CAAA;AACzD,IAAA,MAAM,GAAA,GAAO,GAAG,GAAA,IAAO,EAAA;AACvB,IAAA,MAAMA,MAAAA,GAAQ,OAAO,EAAA,CAAG,KAAA,KAAU,WAAW,EAAA,CAAG,KAAA,CAAM,aAAY,GAAI,EAAA;AACtE,IAAA,IAAI,CAAC,GAAA,IAAO,CAACA,QAAO,MAAM,IAAI,cAAc,4BAA4B,CAAA;AACxE,IAAA,OAAO;AAAA,MACL,MAAA;AAAA,MACA,OAAA,EAAS;AAAA,QACP,iBAAA,EAAmB,GAAA;AAAA,QACnB,KAAA,EAAAA,MAAAA;AAAA,QACA,IAAA,EAAO,GAAG,IAAA,IAA0B,IAAA;AAAA,QACpC,KAAA,EAAQ,GAAG,OAAA,IAA6B,IAAA;AAAA,QACxC,aAAA,EAAe,OAAA,CAAQ,EAAA,CAAG,cAAc;AAAA;AAC1C,KACF;AAAA,EACF;AAGA,EAAA,MAAM,GAAA,GAAM,MAAM,aAAA,CAAc,CAAA,CAAE,aAAa,WAAW,CAAA;AAC1D,EAAA,MAAM,MAAA,GAAS,CAAA,CAAE,OAAA,CAAQ,GAAG,CAAA;AAC5B,EAAA,IAAI,CAAC,MAAA,CAAO,iBAAA,EAAmB,MAAM,IAAI,cAAc,6BAA6B,CAAA;AAEpF,EAAA,IAAI,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,WAAA,EAAY;AACrC,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,IAAI,CAAC,KAAA,IAAS,CAAA,CAAE,EAAA,KAAO,QAAA,EAAU;AAC/B,IAAA,MAAM,MAAA,GAAU,MAAM,aAAA,CAAc,oCAAA,EAAsC,WAAW,CAAA;AAKrF,IAAA,MAAM,OAAA,GAAU,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,CAAA,GAAI,MAAA;AACxE,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,KAAA,GAAQ,OAAA,CAAQ,MAAM,WAAA,EAAY;AAClC,MAAA,QAAA,GAAW,OAAA,CAAQ,QAAA;AAAA,IACrB;AAAA,EACF;AACA,EAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,cAAc,kCAAkC,CAAA;AACtE,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,OAAA,EAAS;AAAA,MACP,mBAAmB,MAAA,CAAO,iBAAA;AAAA,MAC1B,KAAA;AAAA,MACA,IAAA,EAAM,OAAO,IAAA,IAAQ,IAAA;AAAA,MACrB,KAAA,EAAO,OAAO,KAAA,IAAS,IAAA;AAAA,MACvB,aAAA,EAAe;AAAA;AACjB,GACF;AACF;AAEA,eAAe,WAAA,CACb,GAAA,EACA,QAAA,EACA,OAAA,EACA,SAAA,EACsB;AACtB,EAAA,MAAM,OAAA,GAAU,IAAI,QAAA,CAAS,OAAA;AAG7B,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAM,WAAW,MAAM,OAAA,CAAQ,qBAAqB,QAAA,CAAS,EAAA,EAAI,QAAQ,iBAAiB,CAAA;AAC1F,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,MAAM,IAAI,MAAM,GAAA,CAAI,SAAS,IAAA,CAAK,WAAA,CAAY,SAAS,MAAM,CAAA;AAC7D,MAAA,IAAI,GAAG,OAAO,CAAA;AAAA,IAChB;AAAA,EACF;AAGA,EAAA,MAAM,UAAU,MAAM,GAAA,CAAI,SAAS,IAAA,CAAK,cAAA,CAAe,QAAQ,KAAK,CAAA;AACpE,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,IAAI,CAAC,GAAA,CAAI,iCAAA,IAAqC,CAAC,QAAQ,aAAA,EAAe;AACpE,MAAA,MAAM,IAAI,oBAAA;AAAA,QACR,CAAA,sBAAA,EAAyB,QAAQ,KAAK,CAAA,wFAAA;AAAA,OACxC;AAAA,IACF;AAEA,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,WAAA,CAAY;AAAA,QACvC,QAAQ,OAAA,CAAQ,EAAA;AAAA,QAChB,UAAU,QAAA,CAAS,EAAA;AAAA,QACnB,mBAAmB,OAAA,CAAQ,iBAAA;AAAA,QAC3B,OAAO,OAAA,CAAQ,KAAA;AAAA,QACf,WAAA,EAAc,UAAU,YAAA,IAA2B,IAAA;AAAA,QACnD,YAAA,EAAe,UAAU,aAAA,IAA4B,IAAA;AAAA,QACrD,OAAA,EAAU,UAAU,QAAA,IAAuB,IAAA;AAAA,QAC3C,SAAA,EAAY,UAAU,UAAA,IAAyB,IAAA;AAAA,QAC/C,KAAA,EAAQ,UAAU,KAAA,IAAoB,IAAA;AAAA,QACtC,SAAA,EAAW,OAAO,SAAA,CAAU,UAAA,KAAe,QAAA,GACvC,IAAI,IAAA,CAAK,IAAA,CAAK,GAAA,EAAI,GAAK,SAAA,CAAU,UAAA,GAAwB,GAAI,CAAA,GAC7D;AAAA,OACL,CAAA;AACD,MAAA,MAAM,KAAK,GAAA,EAAK;AAAA,QACd,IAAA,EAAM,gBAAA;AAAA,QACN,QAAQ,OAAA,CAAQ,EAAA;AAAA,QAChB,IAAA,EAAM,EAAE,QAAA,EAAU,QAAA,CAAS,IAAI,SAAA,EAAW,MAAA,CAAO,EAAA,EAAI,IAAA,EAAM,IAAA;AAAK,OACjE,CAAA;AAAA,IACH;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAGA,EAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,QAAA,CAAS,KAAK,UAAA,CAAW;AAAA,IAC9C,OAAO,OAAA,CAAQ,KAAA;AAAA,IACf,IAAA,EAAM,QAAQ,IAAA,IAAQ,IAAA;AAAA,IACtB,KAAA,EAAO,QAAQ,KAAA,IAAS,IAAA;AAAA,IACxB,aAAA,EAAe,OAAA,CAAQ,aAAA,mBAAgB,IAAI,MAAK,GAAI;AAAA,GACrD,CAAA;AACD,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,WAAA,CAAY;AAAA,MACvC,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,UAAU,QAAA,CAAS,EAAA;AAAA,MACnB,mBAAmB,OAAA,CAAQ,iBAAA;AAAA,MAC3B,OAAO,OAAA,CAAQ,KAAA;AAAA,MACf,WAAA,EAAc,UAAU,YAAA,IAA2B,IAAA;AAAA,MACnD,YAAA,EAAe,UAAU,aAAA,IAA4B,IAAA;AAAA,MACrD,OAAA,EAAU,UAAU,QAAA,IAAuB,IAAA;AAAA,MAC3C,SAAA,EAAY,UAAU,UAAA,IAAyB,IAAA;AAAA,MAC/C,KAAA,EAAQ,UAAU,KAAA,IAAoB,IAAA;AAAA,MACtC,SAAA,EAAW,OAAO,SAAA,CAAU,UAAA,KAAe,QAAA,GACvC,IAAI,IAAA,CAAK,IAAA,CAAK,GAAA,EAAI,GAAK,SAAA,CAAU,UAAA,GAAwB,GAAI,CAAA,GAC7D;AAAA,KACL,CAAA;AACD,IAAA,MAAM,KAAK,GAAA,EAAK;AAAA,MACd,IAAA,EAAM,gBAAA;AAAA,MACN,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,IAAA,EAAM,EAAE,QAAA,EAAU,QAAA,CAAS,IAAI,SAAA,EAAW,MAAA,CAAO,EAAA,EAAI,QAAA,EAAU,IAAA;AAAK,KACrE,CAAA;AAAA,EACH;AACA,EAAA,OAAO,IAAA;AACT;AAEA,eAAsB,QAAA,CACpB,GAAA,EACA,UAAA,EACA,KAAA,EACsD;AACtD,EAAA,MAAM,CAAA,GAAI,YAAA,CAAa,GAAA,EAAK,UAAU,CAAA;AACtC,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,QAAQ,SAAA,EAAW,OAAA,KAAY,MAAM,kBAAA,CAAmB,GAAG,KAAK,CAAA;AACxE,IAAA,MAAM,OAAO,MAAM,WAAA,CAAY,GAAA,EAAK,CAAA,EAAG,SAAS,SAAS,CAAA;AAEzD,IAAA,MAAM,MAAA,GAAS,MAAM,YAAA,CAAa,GAAA,EAAK;AAAA,MACrC,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,EAAA,EAAI,MAAM,EAAA,IAAM,IAAA;AAAA,MAChB,SAAA,EAAW,MAAM,SAAA,IAAa;AAAA,KAC/B,CAAA;AACD,IAAA,MAAM,KAAK,GAAA,EAAK;AAAA,MACd,IAAA,EAAM,iBAAA;AAAA,MACN,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,WAAW,MAAA,CAAO,SAAA;AAAA,MAClB,IAAA,EAAM,EAAE,QAAA,EAAU,UAAA;AAAW,KAC9B,CAAA;AACD,IAAA,OAAO,EAAE,MAAM,MAAA,EAAO;AAAA,EACxB,SAAS,CAAA,EAAG;AACV,IAAA,MAAM,KAAK,GAAA,EAAK;AAAA,MACd,IAAA,EAAM,qBAAA;AAAA,MACN,MAAM,EAAE,QAAA,EAAU,UAAA,EAAY,KAAA,EAAQ,EAAY,OAAA;AAAQ,KAC3D,CAAA;AACD,IAAA,MAAM,CAAA;AAAA,EACR;AACF;;;ACpNO,SAAS,eAAe,IAAA,EAAyC;AACtE,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,MAAA;AAAA,IACN,EAAA,EAAI,KAAK,EAAA,IAAM,QAAA;AAAA,IACf,IAAA,EAAM,QAAA;AAAA,IACN,UAAU,IAAA,CAAK,QAAA;AAAA,IACf,cAAc,IAAA,CAAK,YAAA;AAAA,IACnB,aAAa,IAAA,CAAK,WAAA;AAAA,IAClB,QAAQ,IAAA,CAAK,MAAA,IAAU,CAAC,QAAA,EAAU,SAAS,SAAS,CAAA;AAAA,IACpD,MAAA,EAAQ,6BAAA;AAAA,IACR,gBAAA,EAAkB,8CAAA;AAAA,IAClB,QAAA,EAAU,qCAAA;AAAA,IACV,WAAA,EAAa;AAAA,GACf;AACF;;;ACdO,SAAS,eAAe,IAAA,EAA2C;AACxE,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAA;AAAA,IACN,EAAA,EAAI,KAAK,EAAA,IAAM,QAAA;AAAA,IACf,IAAA,EAAM,QAAA;AAAA,IACN,UAAU,IAAA,CAAK,QAAA;AAAA,IACf,cAAc,IAAA,CAAK,YAAA;AAAA,IACnB,aAAa,IAAA,CAAK,WAAA;AAAA,IAClB,MAAA,EAAQ,IAAA,CAAK,MAAA,IAAU,CAAC,aAAa,YAAY,CAAA;AAAA,IACjD,gBAAA,EAAkB,0CAAA;AAAA,IAClB,QAAA,EAAU,6CAAA;AAAA,IACV,WAAA,EAAa,6BAAA;AAAA,IACb,OAAA,EAAS,CAAC,GAAA,KAAQ;AAChB,MAAA,MAAM,CAAA,GAAK,OAAO,EAAC;AAOnB,MAAA,OAAO;AAAA,QACL,mBAAmB,MAAA,CAAO,CAAA,CAAE,EAAA,IAAM,CAAA,CAAE,SAAS,EAAE,CAAA;AAAA,QAC/C,KAAA,EAAO,EAAE,KAAA,IAAS,EAAA;AAAA,QAClB,IAAA,EAAM,CAAA,CAAE,IAAA,IAAQ,CAAA,CAAE,KAAA,IAAS,IAAA;AAAA,QAC3B,KAAA,EAAO,EAAE,UAAA,IAAc;AAAA,OACzB;AAAA,IACF;AAAA,GACF;AACF","file":"index.js","sourcesContent":["export class HoleauthError extends Error {\n readonly code: string;\n readonly status: number;\n constructor(code: string, message: string, status = 400) {\n super(message);\n this.name = 'HoleauthError';\n this.code = code;\n this.status = status;\n }\n}\nexport class InvalidTokenError extends HoleauthError {\n constructor(message = 'Invalid token') { super('INVALID_TOKEN', message, 401); }\n}\nexport class SessionExpiredError extends HoleauthError {\n constructor(message = 'Session expired') { super('SESSION_EXPIRED', message, 401); }\n}\nexport class AdapterError extends HoleauthError {\n constructor(message = 'Adapter error') { super('ADAPTER_ERROR', message, 500); }\n}\nexport class ProviderError extends HoleauthError {\n constructor(message = 'Provider error') { super('PROVIDER_ERROR', message, 502); }\n}\nexport class CsrfError extends HoleauthError {\n constructor(message = 'CSRF validation failed') { super('CSRF_FAILED', message, 403); }\n}\nexport class CredentialsError extends HoleauthError {\n constructor(message = 'Invalid credentials') { super('INVALID_CREDENTIALS', message, 401); }\n}\nexport class AccountConflictError extends HoleauthError {\n constructor(message = 'Account conflict') { super('ACCOUNT_CONFLICT', message, 409); }\n}\nexport class RefreshReuseError extends HoleauthError {\n constructor(message = 'Refresh token reuse detected') { super('REFRESH_REUSE', message, 401); }\n}\nexport class PendingChallengeError extends HoleauthError {\n constructor(message = 'Pending challenge required') { super('PENDING_CHALLENGE', message, 401); }\n}\nexport class RegistrationDisabledError extends HoleauthError {\n constructor(message = 'Self-registration is disabled') { super('REGISTRATION_DISABLED', message, 403); }\n}\nexport class NotSupportedError extends HoleauthError {\n constructor(message = 'Operation not supported by adapter') { super('NOT_SUPPORTED', message, 501); }\n}\n","/**\n * Generic OAuth2/OIDC PKCE client helpers.\n * High-level per-provider flows live in ./authorize.ts and ./callback.ts.\n */\nimport { ProviderError } from '../errors/index.js';\n\nexport interface AuthorizeParams {\n issuerAuthUrl: string;\n clientId: string;\n redirectUri: string;\n scopes?: string[];\n state: string;\n codeChallenge: string;\n nonce?: string;\n /** Extra params merged into the query string. */\n extra?: Record<string, string>;\n}\n\nexport function buildAuthorizeUrl(p: AuthorizeParams): string {\n const url = new URL(p.issuerAuthUrl);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', p.clientId);\n url.searchParams.set('redirect_uri', p.redirectUri);\n url.searchParams.set('scope', (p.scopes ?? ['openid', 'email', 'profile']).join(' '));\n url.searchParams.set('state', p.state);\n url.searchParams.set('code_challenge', p.codeChallenge);\n url.searchParams.set('code_challenge_method', 'S256');\n if (p.nonce) url.searchParams.set('nonce', p.nonce);\n for (const [k, v] of Object.entries(p.extra ?? {})) url.searchParams.set(k, v);\n return url.toString();\n}\n\nexport async function generatePkcePair(): Promise<{ verifier: string; challenge: string }> {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n const verifier = base64url(bytes);\n const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return { verifier, challenge: base64url(new Uint8Array(hash)) };\n}\n\nexport function generateState(): string {\n return base64url(crypto.getRandomValues(new Uint8Array(16)));\n}\n\nexport function generateNonce(): string {\n return base64url(crypto.getRandomValues(new Uint8Array(16)));\n}\n\nexport interface TokenExchangeInput {\n tokenUrl: string;\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n code: string;\n codeVerifier: string;\n}\n\nexport async function exchangeCode(i: TokenExchangeInput): Promise<Record<string, unknown>> {\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code: i.code,\n redirect_uri: i.redirectUri,\n client_id: i.clientId,\n client_secret: i.clientSecret,\n code_verifier: i.codeVerifier,\n });\n const res = await fetch(i.tokenUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },\n body,\n });\n if (!res.ok) throw new ProviderError(`Token exchange failed: ${res.status}`);\n return (await res.json()) as Record<string, unknown>;\n}\n\nexport async function fetchUserInfo(\n userinfoUrl: string,\n accessToken: string,\n): Promise<Record<string, unknown>> {\n const res = await fetch(userinfoUrl, {\n headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' },\n });\n if (!res.ok) throw new ProviderError(`Userinfo failed: ${res.status}`);\n return (await res.json()) as Record<string, unknown>;\n}\n\nexport function base64url(bytes: Uint8Array): string {\n let s = ''; for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n","import type { HoleauthConfig } from '../types/index.js';\nimport type { HoleauthEvent } from './types.js';\n\ntype Handler = (e: HoleauthEvent) => void | Promise<void>;\n\ninterface EventBus {\n byType: Map<string, Set<Handler>>;\n wildcard: Set<Handler>;\n}\n\nconst busByConfig = new WeakMap<HoleauthConfig, EventBus>();\n\nfunction getBus(cfg: HoleauthConfig): EventBus {\n let bus = busByConfig.get(cfg);\n if (!bus) {\n bus = { byType: new Map(), wildcard: new Set() };\n busByConfig.set(cfg, bus);\n }\n return bus;\n}\n\n/** Subscribe to an event type. Use '*' to match all events. Returns an unsubscribe fn. */\nexport function subscribe(cfg: HoleauthConfig, type: string, handler: Handler): () => void {\n const bus = getBus(cfg);\n if (type === '*') {\n bus.wildcard.add(handler);\n return () => bus.wildcard.delete(handler);\n }\n let set = bus.byType.get(type);\n if (!set) {\n set = new Set();\n bus.byType.set(type, set);\n }\n set.add(handler);\n return () => set!.delete(handler);\n}\n\nexport function unsubscribe(cfg: HoleauthConfig, type: string, handler: Handler): void {\n const bus = getBus(cfg);\n if (type === '*') {\n bus.wildcard.delete(handler);\n return;\n }\n bus.byType.get(type)?.delete(handler);\n}\n\n/**\n * emit() persists the event via the mandatory AuditLogAdapter and\n * additionally fans out to all subscribers (typed + wildcard) plus the\n * legacy `cfg.onEvent` hook — all fire-and-forget so business flows are\n * never blocked by observer failures.\n *\n * Callers MUST await emit(): audit persistence is a hard requirement.\n */\nexport async function emit(cfg: HoleauthConfig, event: HoleauthEvent): Promise<void> {\n const withTimestamp: HoleauthEvent = { at: new Date(), ...event };\n await cfg.adapters.auditLog.record(withTimestamp);\n\n const bus = getBus(cfg);\n const typed = bus.byType.get(withTimestamp.type);\n const fire = (h: Handler) => {\n Promise.resolve()\n .then(() => h(withTimestamp))\n .catch(() => { /* observer errors do not propagate */ });\n };\n if (typed) for (const h of typed) fire(h);\n for (const h of bus.wildcard) fire(h);\n\n if (cfg.onEvent) {\n Promise.resolve()\n .then(() => cfg.onEvent?.(withTimestamp))\n .catch(() => { /* observer errors do not propagate */ });\n }\n}\n","import type { HoleauthConfig, ProviderConfig } from '../types/index.js';\nimport { ProviderError } from '../errors/index.js';\nimport { buildAuthorizeUrl, generatePkcePair, generateState, generateNonce } from './client.js';\nimport { emit } from '../events/emitter.js';\n\nexport function findProvider(cfg: HoleauthConfig, id: string): ProviderConfig {\n const p = cfg.providers?.find((x) => x.id === id);\n if (!p) throw new ProviderError(`unknown provider: ${id}`);\n return p;\n}\n\n/**\n * Begin the SSO hop. Returns a URL to redirect the user to, plus state +\n * PKCE verifier the caller must persist (typically in httpOnly cookies)\n * to validate the callback.\n */\nexport async function authorize(\n cfg: HoleauthConfig,\n providerId: string,\n): Promise<{ url: string; state: string; codeVerifier: string; nonce?: string }> {\n const p = findProvider(cfg, providerId);\n const state = generateState();\n const { verifier, challenge } = await generatePkcePair();\n const nonce = p.kind === 'oidc' ? generateNonce() : undefined;\n\n const url = buildAuthorizeUrl({\n issuerAuthUrl: p.authorizationUrl,\n clientId: p.clientId,\n redirectUri: p.redirectUri,\n scopes: p.scopes,\n state,\n codeChallenge: challenge,\n nonce,\n });\n\n await emit(cfg, { type: 'sso.authorize', data: { provider: providerId } });\n return { url, state, codeVerifier: verifier, nonce };\n}\n","import { SignJWT, jwtVerify, decodeJwt, type JWTPayload } from 'jose';\nimport { InvalidTokenError } from '../errors/index.js';\n\nfunction toKey(secret: string | Uint8Array): Uint8Array {\n return typeof secret === 'string' ? new TextEncoder().encode(secret) : secret;\n}\n\nexport interface SignOptions {\n issuer?: string;\n audience?: string;\n subject?: string;\n expiresIn?: string | number; // e.g. '15m' or seconds\n jti?: string;\n}\n\nexport async function sign(\n payload: JWTPayload,\n secret: string | Uint8Array,\n opts: SignOptions = {},\n): Promise<string> {\n const jwt = new SignJWT(payload).setProtectedHeader({ alg: 'HS256' }).setIssuedAt();\n if (opts.issuer) jwt.setIssuer(opts.issuer);\n if (opts.audience) jwt.setAudience(opts.audience);\n if (opts.subject) jwt.setSubject(opts.subject);\n if (opts.jti) jwt.setJti(opts.jti);\n if (opts.expiresIn !== undefined) jwt.setExpirationTime(opts.expiresIn);\n return jwt.sign(toKey(secret));\n}\n\nexport async function verify<T extends JWTPayload = JWTPayload>(\n token: string,\n secret: string | Uint8Array,\n): Promise<T> {\n try {\n const { payload } = await jwtVerify(token, toKey(secret));\n return payload as T;\n } catch (e) {\n throw new InvalidTokenError((e as Error).message);\n }\n}\n\nexport function decode<T extends JWTPayload = JWTPayload>(token: string): T {\n try {\n return decodeJwt(token) as T;\n } catch (e) {\n throw new InvalidTokenError((e as Error).message);\n }\n}\n","/**\n * Double-submit CSRF protection.\n * The cookie holeauth.csrf is readable by JS (httpOnly:false). The client\n * echoes its value in header `x-csrf-token`; the server compares the two.\n * Because cross-origin JS cannot read the cookie, an attacker cannot mint\n * a matching header, defeating the cross-site POST scenario.\n */\n\nconst b64urlChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';\n\nexport function generateCsrfToken(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let out = '';\n for (const b of bytes) out += b64urlChars[b % 64];\n return out;\n}\n\n/** Constant-time compare. */\nexport function verifyCsrf(cookieValue: string | undefined, headerValue: string | undefined): boolean {\n if (!cookieValue || !headerValue) return false;\n if (cookieValue.length !== headerValue.length) return false;\n let diff = 0;\n for (let i = 0; i < cookieValue.length; i++) {\n diff |= cookieValue.charCodeAt(i) ^ headerValue.charCodeAt(i);\n }\n return diff === 0;\n}\n\nexport const CSRF_HEADER = 'x-csrf-token';\n","/**\n * Per-config hook runner attachment. Stored via WeakMap so the runner is\n * reachable from low-level helpers (session/issue, session/rotate, …)\n * without threading it through every signature.\n *\n * `defineHoleauth()` attaches the runner once at instance creation.\n */\nimport type { HoleauthConfig } from '../types/index.js';\nimport type { HookRunner } from './registry.js';\n\nconst runners = new WeakMap<HoleauthConfig, HookRunner>();\n\nexport function attachHookRunner(cfg: HoleauthConfig, runner: HookRunner): void {\n runners.set(cfg, runner);\n}\n\nconst NOOP: HookRunner = {\n async runRegisterBefore() {},\n async runRegisterAfter() {},\n async runSignInBefore() {},\n async runSignInChallenge() { return null; },\n async runSignInAfter() {},\n async runSignOutAfter() {},\n async runRefreshBefore() {},\n async runRefreshAfter() {},\n async runPasswordChangeBefore() {},\n async runPasswordChangeAfter() {},\n async runPasswordResetBefore() {},\n async runPasswordResetAfter() {},\n async runUserUpdateAfter() {},\n async runUserDeleteAfter() {},\n async runSessionIssue() {},\n async runSessionRotate() {},\n async runSessionRevoke() {},\n};\n\nexport function getHookRunner(cfg: HoleauthConfig): HookRunner {\n return runners.get(cfg) ?? NOOP;\n}\n","/** SHA-256 → base64url. Works on Node 20+ and all Edge runtimes. */\nexport async function sha256b64url(input: string): Promise<string> {\n const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));\n const bytes = new Uint8Array(buf);\n let s = '';\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n","import type { HoleauthConfig, IssuedTokens } from '../types/index.js';\nimport { sign } from '../jwt/index.js';\nimport { generateCsrfToken } from '../cookies/csrf.js';\nimport { emit } from '../events/emitter.js';\nimport { getHookRunner } from '../plugins/runner-ref.js';\nimport { sha256b64url } from './hash.js';\n\nconst ACCESS_DEFAULT = 900; // 15m\nconst REFRESH_DEFAULT = 2592000; // 30d\n\nexport interface IssueInput {\n userId: string;\n /** Omit to start a fresh family (e.g. on a real login). */\n familyId?: string;\n ip?: string | null;\n userAgent?: string | null;\n}\n\n/**\n * Mint a brand new session row + JWT pair + CSRF token.\n * Used by: fresh login, passkey login, SSO callback, 2FA verify.\n */\nexport async function issueSession(cfg: HoleauthConfig, input: IssueInput): Promise<IssuedTokens> {\n const accessTtl = cfg.tokens?.accessTtl ?? ACCESS_DEFAULT;\n const refreshTtl = cfg.tokens?.refreshTtl ?? REFRESH_DEFAULT;\n const now = Math.floor(Date.now() / 1000);\n\n const familyId = input.familyId ?? crypto.randomUUID();\n const sessionId = crypto.randomUUID();\n const nonce = crypto.randomUUID();\n\n const [accessToken, refreshToken] = await Promise.all([\n sign(\n { sid: sessionId, sub: input.userId, fam: familyId, nce: nonce },\n cfg.secrets.jwtSecret,\n { expiresIn: `${accessTtl}s` },\n ),\n sign(\n { sid: sessionId, sub: input.userId, fam: familyId, typ: 'refresh', nce: nonce },\n cfg.secrets.jwtSecret,\n { expiresIn: `${refreshTtl}s`, jti: nonce },\n ),\n ]);\n const refreshTokenHash = await sha256b64url(refreshToken);\n\n await cfg.adapters.session.createSession({\n id: sessionId,\n userId: input.userId,\n familyId,\n refreshTokenHash,\n expiresAt: new Date((now + refreshTtl) * 1000),\n createdAt: new Date(),\n revokedAt: null,\n ip: input.ip ?? null,\n userAgent: input.userAgent ?? null,\n });\n\n const csrfToken = generateCsrfToken();\n\n await emit(cfg, {\n type: 'session.created',\n userId: input.userId,\n sessionId,\n ip: input.ip ?? null,\n userAgent: input.userAgent ?? null,\n data: { familyId },\n });\n await getHookRunner(cfg).runSessionIssue({ userId: input.userId, sessionId, familyId });\n\n return {\n accessToken,\n refreshToken,\n csrfToken,\n sessionId,\n familyId,\n accessExpiresAt: (now + accessTtl) * 1000,\n refreshExpiresAt: (now + refreshTtl) * 1000,\n };\n}\n","import type { HoleauthConfig, IssuedTokens, ProviderConfig } from '../types/index.js';\nimport type { AdapterUser } from '../adapters/index.js';\nimport { findProvider } from './authorize.js';\nimport { exchangeCode, fetchUserInfo } from './client.js';\nimport { decode } from '../jwt/index.js';\nimport { issueSession } from '../session/issue.js';\nimport { AccountConflictError, ProviderError } from '../errors/index.js';\nimport { emit } from '../events/emitter.js';\n\nexport interface CallbackInput {\n code: string;\n state: string;\n codeVerifier: string;\n ip?: string;\n userAgent?: string;\n}\n\ninterface NormalisedProfile {\n providerAccountId: string;\n email: string;\n name?: string | null;\n image?: string | null;\n emailVerified: boolean;\n}\n\nasync function exchangeAndProfile(\n p: ProviderConfig,\n input: CallbackInput,\n): Promise<{ tokens: Record<string, unknown>; profile: NormalisedProfile }> {\n const tokens = await exchangeCode({\n tokenUrl: p.tokenUrl,\n clientId: p.clientId,\n clientSecret: p.clientSecret,\n redirectUri: p.redirectUri,\n code: input.code,\n codeVerifier: input.codeVerifier,\n });\n\n const accessToken = typeof tokens.access_token === 'string' ? tokens.access_token : null;\n if (!accessToken) throw new ProviderError('no access_token in response');\n\n if (p.kind === 'oidc') {\n const idToken = typeof tokens.id_token === 'string' ? tokens.id_token : null;\n if (idToken) {\n const claims = decode<{\n sub?: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n picture?: string;\n }>(idToken);\n if (claims.sub && claims.email) {\n return {\n tokens,\n profile: {\n providerAccountId: claims.sub,\n email: claims.email.toLowerCase(),\n name: claims.name ?? null,\n image: claims.picture ?? null,\n emailVerified: claims.email_verified ?? false,\n },\n };\n }\n }\n // Fall back to userinfo\n const ui = await fetchUserInfo(p.userinfoUrl, accessToken);\n const sub = (ui.sub ?? '') as string;\n const email = typeof ui.email === 'string' ? ui.email.toLowerCase() : '';\n if (!sub || !email) throw new ProviderError('userinfo missing sub/email');\n return {\n tokens,\n profile: {\n providerAccountId: sub,\n email,\n name: (ui.name as string | null) ?? null,\n image: (ui.picture as string | null) ?? null,\n emailVerified: Boolean(ui.email_verified),\n },\n };\n }\n\n // oauth2\n const raw = await fetchUserInfo(p.userinfoUrl, accessToken);\n const mapped = p.profile(raw);\n if (!mapped.providerAccountId) throw new ProviderError('provider profile missing id');\n // GitHub may return null email — fall back to /user/emails.\n let email = mapped.email.toLowerCase();\n let verified = false;\n if (!email && p.id === 'github') {\n const emails = (await fetchUserInfo('https://api.github.com/user/emails', accessToken)) as unknown as Array<{\n email: string;\n primary: boolean;\n verified: boolean;\n }>;\n const primary = Array.isArray(emails) ? emails.find((e) => e.primary) : undefined;\n if (primary) {\n email = primary.email.toLowerCase();\n verified = primary.verified;\n }\n }\n if (!email) throw new ProviderError('provider did not return an email');\n return {\n tokens,\n profile: {\n providerAccountId: mapped.providerAccountId,\n email,\n name: mapped.name ?? null,\n image: mapped.image ?? null,\n emailVerified: verified,\n },\n };\n}\n\nasync function resolveUser(\n cfg: HoleauthConfig,\n provider: ProviderConfig,\n profile: NormalisedProfile,\n rawTokens: Record<string, unknown>,\n): Promise<AdapterUser> {\n const account = cfg.adapters.account;\n\n // 1. Try existing link\n if (account) {\n const existing = await account.getAccountByProvider(provider.id, profile.providerAccountId);\n if (existing) {\n const u = await cfg.adapters.user.getUserById(existing.userId);\n if (u) return u;\n }\n }\n\n // 2. Try user by email (potential linking)\n const byEmail = await cfg.adapters.user.getUserByEmail(profile.email);\n if (byEmail) {\n if (!cfg.allowDangerousEmailAccountLinking || !profile.emailVerified) {\n throw new AccountConflictError(\n `an account with email ${profile.email} already exists; enable allowDangerousEmailAccountLinking or sign in with password first`,\n );\n }\n // Auto-link\n if (account) {\n const linked = await account.linkAccount({\n userId: byEmail.id,\n provider: provider.id,\n providerAccountId: profile.providerAccountId,\n email: profile.email,\n accessToken: (rawTokens.access_token as string) ?? null,\n refreshToken: (rawTokens.refresh_token as string) ?? null,\n idToken: (rawTokens.id_token as string) ?? null,\n tokenType: (rawTokens.token_type as string) ?? null,\n scope: (rawTokens.scope as string) ?? null,\n expiresAt: typeof rawTokens.expires_in === 'number'\n ? new Date(Date.now() + (rawTokens.expires_in as number) * 1000)\n : null,\n });\n await emit(cfg, {\n type: 'account.linked',\n userId: byEmail.id,\n data: { provider: provider.id, accountId: linked.id, auto: true },\n });\n }\n return byEmail;\n }\n\n // 3. Create user + (maybe) link\n const user = await cfg.adapters.user.createUser({\n email: profile.email,\n name: profile.name ?? null,\n image: profile.image ?? null,\n emailVerified: profile.emailVerified ? new Date() : null,\n });\n if (account) {\n const linked = await account.linkAccount({\n userId: user.id,\n provider: provider.id,\n providerAccountId: profile.providerAccountId,\n email: profile.email,\n accessToken: (rawTokens.access_token as string) ?? null,\n refreshToken: (rawTokens.refresh_token as string) ?? null,\n idToken: (rawTokens.id_token as string) ?? null,\n tokenType: (rawTokens.token_type as string) ?? null,\n scope: (rawTokens.scope as string) ?? null,\n expiresAt: typeof rawTokens.expires_in === 'number'\n ? new Date(Date.now() + (rawTokens.expires_in as number) * 1000)\n : null,\n });\n await emit(cfg, {\n type: 'account.linked',\n userId: user.id,\n data: { provider: provider.id, accountId: linked.id, onCreate: true },\n });\n }\n return user;\n}\n\nexport async function callback(\n cfg: HoleauthConfig,\n providerId: string,\n input: CallbackInput,\n): Promise<{ user: AdapterUser; tokens: IssuedTokens }> {\n const p = findProvider(cfg, providerId);\n try {\n const { tokens: rawTokens, profile } = await exchangeAndProfile(p, input);\n const user = await resolveUser(cfg, p, profile, rawTokens);\n\n const tokens = await issueSession(cfg, {\n userId: user.id,\n ip: input.ip ?? null,\n userAgent: input.userAgent ?? null,\n });\n await emit(cfg, {\n type: 'sso.callback_ok',\n userId: user.id,\n sessionId: tokens.sessionId,\n data: { provider: providerId },\n });\n return { user, tokens };\n } catch (e) {\n await emit(cfg, {\n type: 'sso.callback_failed',\n data: { provider: providerId, error: (e as Error).message },\n });\n throw e;\n }\n}\n","import type { OIDCProviderConfig } from '../../types/index.js';\n\nexport interface GoogleOptions {\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n id?: string;\n scopes?: string[];\n}\n\n/** Google OpenID Connect provider. */\nexport function GoogleProvider(opts: GoogleOptions): OIDCProviderConfig {\n return {\n kind: 'oidc',\n id: opts.id ?? 'google',\n name: 'Google',\n clientId: opts.clientId,\n clientSecret: opts.clientSecret,\n redirectUri: opts.redirectUri,\n scopes: opts.scopes ?? ['openid', 'email', 'profile'],\n issuer: 'https://accounts.google.com',\n authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',\n tokenUrl: 'https://oauth2.googleapis.com/token',\n userinfoUrl: 'https://openidconnect.googleapis.com/v1/userinfo',\n };\n}\n","import type { OAuth2ProviderConfig } from '../../types/index.js';\n\nexport interface GithubOptions {\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n id?: string;\n scopes?: string[];\n}\n\n/** GitHub OAuth2 provider. Uses REST userinfo (no OIDC). */\nexport function GithubProvider(opts: GithubOptions): OAuth2ProviderConfig {\n return {\n kind: 'oauth2',\n id: opts.id ?? 'github',\n name: 'GitHub',\n clientId: opts.clientId,\n clientSecret: opts.clientSecret,\n redirectUri: opts.redirectUri,\n scopes: opts.scopes ?? ['read:user', 'user:email'],\n authorizationUrl: 'https://github.com/login/oauth/authorize',\n tokenUrl: 'https://github.com/login/oauth/access_token',\n userinfoUrl: 'https://api.github.com/user',\n profile: (raw) => {\n const p = (raw ?? {}) as {\n id?: number | string;\n login?: string;\n email?: string | null;\n name?: string | null;\n avatar_url?: string | null;\n };\n return {\n providerAccountId: String(p.id ?? p.login ?? ''),\n email: p.email ?? '',\n name: p.name ?? p.login ?? null,\n image: p.avatar_url ?? null,\n };\n },\n };\n}\n"]}