@mesob/auth-hono 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -327,6 +327,232 @@ var createDatabase = (connectionString) => {
327
327
  import { OpenAPIHono as OpenAPIHono17 } from "@hono/zod-openapi";
328
328
  import { getCookie as getCookie3 } from "hono/cookie";
329
329
 
330
+ // src/lib/cookie.ts
331
+ import { deleteCookie as honoDel, setCookie as honoSet } from "hono/cookie";
332
+ var isProduction = process.env.NODE_ENV === "production";
333
+ var getSessionKeyNamespace = (config) => {
334
+ const p = config.prefix?.trim();
335
+ return p && p.length > 0 ? p : "msb";
336
+ };
337
+ var getSessionCookieName = (config) => {
338
+ const prefix = config.prefix?.trim() || "";
339
+ const baseName = "session_token";
340
+ if (prefix) {
341
+ return `${prefix}_${baseName}`;
342
+ }
343
+ return isProduction ? "__Host-session_token" : baseName;
344
+ };
345
+ var setSessionCookie = (c, token, config, options) => {
346
+ const cookieName = getSessionCookieName(config);
347
+ const cookieOptions = {
348
+ httpOnly: true,
349
+ secure: isProduction,
350
+ sameSite: "Lax",
351
+ path: options.path || "/",
352
+ expires: options.expires,
353
+ ...isProduction && { domain: void 0 }
354
+ // __Host- requires no domain
355
+ };
356
+ honoSet(c, cookieName, token, cookieOptions);
357
+ };
358
+ var deleteSessionCookie = (c, config) => {
359
+ const cookieName = getSessionCookieName(config);
360
+ honoDel(c, cookieName, {
361
+ httpOnly: true,
362
+ secure: isProduction,
363
+ sameSite: "Lax",
364
+ path: "/",
365
+ expires: /* @__PURE__ */ new Date(0)
366
+ });
367
+ };
368
+
369
+ // src/lib/crypto.ts
370
+ import { scrypt } from "@noble/hashes/scrypt.js";
371
+ import { randomBytes } from "@noble/hashes/utils.js";
372
+ var encoder = new TextEncoder();
373
+ var randomHex = (bytes) => {
374
+ const arr = randomBytes(bytes);
375
+ return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join(
376
+ ""
377
+ );
378
+ };
379
+ var toHex = (buffer) => {
380
+ return Array.from(
381
+ buffer,
382
+ (b) => b.toString(16).padStart(2, "0")
383
+ ).join("");
384
+ };
385
+ var hexToBytes = (hex) => {
386
+ const bytes = new Uint8Array(hex.length / 2);
387
+ for (let i = 0; i < hex.length; i += 2) {
388
+ bytes[i / 2] = Number.parseInt(hex.slice(i, i + 2), 16);
389
+ }
390
+ return bytes;
391
+ };
392
+ var SCRYPT_KEYLEN = 64;
393
+ var SCRYPT_COST = 16384;
394
+ var SCRYPT_BLOCK_SIZE = 8;
395
+ var SCRYPT_PARALLELISM = 1;
396
+ var hashPassword = async (password) => {
397
+ const salt = randomBytes(16);
398
+ const saltHex = toHex(salt);
399
+ const passwordBytes = encoder.encode(password);
400
+ const derivedKey = await Promise.resolve(
401
+ scrypt(passwordBytes, salt, {
402
+ N: SCRYPT_COST,
403
+ r: SCRYPT_BLOCK_SIZE,
404
+ p: SCRYPT_PARALLELISM,
405
+ dkLen: SCRYPT_KEYLEN
406
+ })
407
+ );
408
+ return `${saltHex}:${toHex(derivedKey)}`;
409
+ };
410
+ var verifyPassword = async (password, hashed) => {
411
+ if (!hashed) {
412
+ return false;
413
+ }
414
+ const [saltHex, keyHex] = hashed.split(":");
415
+ if (!(saltHex && keyHex)) {
416
+ return false;
417
+ }
418
+ const salt = hexToBytes(saltHex);
419
+ const passwordBytes = encoder.encode(password);
420
+ const derivedKey = await Promise.resolve(
421
+ scrypt(passwordBytes, salt, {
422
+ N: SCRYPT_COST,
423
+ r: SCRYPT_BLOCK_SIZE,
424
+ p: SCRYPT_PARALLELISM,
425
+ dkLen: SCRYPT_KEYLEN
426
+ })
427
+ );
428
+ const derived = toHex(derivedKey);
429
+ if (derived.length !== keyHex.length) {
430
+ return false;
431
+ }
432
+ let result = 0;
433
+ for (let i = 0; i < derived.length; i++) {
434
+ result |= derived.charCodeAt(i) ^ keyHex.charCodeAt(i);
435
+ }
436
+ return result === 0;
437
+ };
438
+ var hashToken = async (token, secret) => {
439
+ if (!secret || secret.length === 0) {
440
+ throw new Error(
441
+ "AUTH_SECRET is required and must be a non-empty string. Please set AUTH_SECRET environment variable."
442
+ );
443
+ }
444
+ const key = await crypto.subtle.importKey(
445
+ "raw",
446
+ encoder.encode(secret),
447
+ { name: "HMAC", hash: "SHA-256" },
448
+ false,
449
+ ["sign"]
450
+ );
451
+ const signature = await crypto.subtle.sign(
452
+ "HMAC",
453
+ key,
454
+ encoder.encode(token)
455
+ );
456
+ return toHex(new Uint8Array(signature));
457
+ };
458
+ var generateToken = (bytes = 48) => randomHex(bytes);
459
+
460
+ // src/lib/error-handler.ts
461
+ import { logger } from "@mesob/common";
462
+ import { HTTPException } from "hono/http-exception";
463
+ var isDatabaseError = (error) => {
464
+ if (typeof error !== "object" || error === null) {
465
+ return false;
466
+ }
467
+ if ("code" in error || "query" in error || "detail" in error) {
468
+ return true;
469
+ }
470
+ if (error instanceof Error) {
471
+ const message = error.message.toLowerCase();
472
+ return message.includes("failed query") || message.includes("relation") || message.includes("column") || message.includes("syntax error") || message.includes("duplicate key") || message.includes("foreign key") || message.includes("null value");
473
+ }
474
+ return false;
475
+ };
476
+ var sanitizeDatabaseError = (error) => {
477
+ const code = error.code;
478
+ if (code === "23505") {
479
+ return "Resource already exists";
480
+ }
481
+ if (code === "23503") {
482
+ return "Referenced resource not found";
483
+ }
484
+ if (code === "23502") {
485
+ return "Required field is missing";
486
+ }
487
+ if (code === "42P01") {
488
+ return "Resource not found";
489
+ }
490
+ if (code === "42703") {
491
+ return "Invalid request";
492
+ }
493
+ if (code === "23514") {
494
+ return "Validation failed";
495
+ }
496
+ return "An error occurred while processing your request";
497
+ };
498
+ var isDatabaseErrorMessage = (message) => {
499
+ const lowerMessage = message.toLowerCase();
500
+ return lowerMessage.includes("failed query") || lowerMessage.includes("select") || lowerMessage.includes("insert") || lowerMessage.includes("update") || lowerMessage.includes("delete") || lowerMessage.includes("from") || lowerMessage.includes("where") || lowerMessage.includes("limit") || lowerMessage.includes("params:") || lowerMessage.includes("query") || message.includes('"iam".') || message.includes('"tenants"') || message.includes('"users"') || message.includes('"sessions"') || message.includes('"accounts"') || lowerMessage.includes("relation") || lowerMessage.includes("column") || lowerMessage.includes("syntax error") || lowerMessage.includes("database") || lowerMessage.includes("postgres") || lowerMessage.includes("sql");
501
+ };
502
+ var handleError = (error, c) => {
503
+ logger.error("API Error:", {
504
+ error,
505
+ path: c.req.path,
506
+ method: c.req.method,
507
+ url: c.req.url
508
+ });
509
+ if (error instanceof HTTPException) {
510
+ const message = isDatabaseErrorMessage(error.message) ? "An error occurred while processing your request" : error.message;
511
+ return c.json({ error: message }, error.status);
512
+ }
513
+ if (isDatabaseError(error)) {
514
+ const userMessage = sanitizeDatabaseError(error);
515
+ logger.error("Database error details:", {
516
+ code: error.code,
517
+ message: error.message,
518
+ detail: error.detail,
519
+ query: error.query,
520
+ parameters: error.parameters
521
+ });
522
+ return c.json({ error: userMessage }, 500);
523
+ }
524
+ if (error instanceof Error) {
525
+ const message = error.message;
526
+ const lowerMessage = message.toLowerCase();
527
+ const isDatabaseError2 = lowerMessage.includes("failed query") || lowerMessage.includes("select") || lowerMessage.includes("insert") || lowerMessage.includes("update") || lowerMessage.includes("delete") || lowerMessage.includes("from") || lowerMessage.includes("where") || lowerMessage.includes("limit") || lowerMessage.includes("params:") || lowerMessage.includes("query") || message.includes('"iam".') || message.includes('"tenants"') || message.includes('"users"') || message.includes('"sessions"') || message.includes('"accounts"') || lowerMessage.includes("relation") || lowerMessage.includes("column") || lowerMessage.includes("syntax error") || lowerMessage.includes("duplicate key") || lowerMessage.includes("foreign key") || lowerMessage.includes("null value") || lowerMessage.includes("database") || lowerMessage.includes("postgres") || lowerMessage.includes("sql");
528
+ if (isDatabaseError2) {
529
+ logger.error("SQL/database error detected:", {
530
+ message: error.message,
531
+ stack: error.stack,
532
+ name: error.name
533
+ });
534
+ return c.json(
535
+ { error: "An error occurred while processing your request" },
536
+ 500
537
+ );
538
+ }
539
+ logger.error("Error details:", {
540
+ message: error.message,
541
+ stack: error.stack,
542
+ name: error.name
543
+ });
544
+ return c.json(
545
+ { error: "An error occurred while processing your request" },
546
+ 500
547
+ );
548
+ }
549
+ logger.error("Unknown error:", error);
550
+ return c.json(
551
+ { error: "An error occurred while processing your request" },
552
+ 500
553
+ );
554
+ };
555
+
330
556
  // src/db/orm/session.ts
331
557
  import { and, eq, gt } from "drizzle-orm";
332
558
  var fetchSessionByToken = async ({
@@ -384,6 +610,19 @@ var deleteSession = async ({
384
610
  )
385
611
  );
386
612
  };
613
+ var listHashedTokensForUser = async ({
614
+ database,
615
+ userId,
616
+ tenantId
617
+ }) => {
618
+ const rows = await database.select({ token: sessionsInIam.token }).from(sessionsInIam).where(
619
+ and(
620
+ eq(sessionsInIam.userId, userId),
621
+ eq(sessionsInIam.tenantId, tenantId)
622
+ )
623
+ );
624
+ return rows.map((r) => r.token);
625
+ };
387
626
 
388
627
  // src/db/orm/tenant.ts
389
628
  import { and as and2, eq as eq2, sql as sql2 } from "drizzle-orm";
@@ -492,226 +731,335 @@ var fetchUserWithRoles = async ({
492
731
  return userResult || null;
493
732
  };
494
733
 
495
- // src/lib/cookie.ts
496
- import { deleteCookie as honoDel, setCookie as honoSet } from "hono/cookie";
497
- var isProduction = process.env.NODE_ENV === "production";
498
- var getSessionCookieName = (config) => {
499
- const prefix = config.cookie?.prefix || "";
500
- const baseName = "session_token";
501
- if (prefix) {
502
- return `${prefix}_${baseName}`;
734
+ // src/lib/session-cache.ts
735
+ var sessionCacheKey = (config, hashedToken) => `${getSessionKeyNamespace(config)}:sc:v1:${hashedToken}`;
736
+ async function readSessionCache(config, hashedToken) {
737
+ const sc = config.sessionCache;
738
+ if (!sc?.enabled) {
739
+ return null;
503
740
  }
504
- return isProduction ? "__Host-session_token" : baseName;
505
- };
506
- var setSessionCookie = (c, token, config, options) => {
507
- const cookieName = getSessionCookieName(config);
508
- const cookieOptions = {
509
- httpOnly: true,
510
- secure: isProduction,
511
- sameSite: "Lax",
512
- path: options.path || "/",
513
- expires: options.expires,
514
- ...isProduction && { domain: void 0 }
515
- // __Host- requires no domain
516
- };
517
- honoSet(c, cookieName, token, cookieOptions);
518
- };
519
- var deleteSessionCookie = (c, config) => {
520
- const cookieName = getSessionCookieName(config);
521
- honoDel(c, cookieName, {
522
- httpOnly: true,
523
- secure: isProduction,
524
- sameSite: "Lax",
525
- path: "/",
526
- expires: /* @__PURE__ */ new Date(0)
741
+ const raw = await sc.kv.get(sessionCacheKey(config, hashedToken));
742
+ if (!raw) {
743
+ return null;
744
+ }
745
+ try {
746
+ const parsed = JSON.parse(raw);
747
+ if (!parsed?.session?.expiresAt || new Date(parsed.session.expiresAt) <= /* @__PURE__ */ new Date()) {
748
+ await sc.kv.delete(sessionCacheKey(config, hashedToken));
749
+ return null;
750
+ }
751
+ return parsed;
752
+ } catch {
753
+ await sc.kv.delete(sessionCacheKey(config, hashedToken));
754
+ return null;
755
+ }
756
+ }
757
+ async function writeSessionCache(config, hashedToken, payload) {
758
+ const sc = config.sessionCache;
759
+ if (!sc?.enabled) {
760
+ return;
761
+ }
762
+ const ttl = sc.ttlSeconds ?? Math.max(
763
+ 60,
764
+ Math.ceil(
765
+ (new Date(payload.session.expiresAt).getTime() - Date.now()) / 1e3
766
+ )
767
+ );
768
+ await sc.kv.put(
769
+ sessionCacheKey(config, hashedToken),
770
+ JSON.stringify(payload),
771
+ {
772
+ expirationTtl: Math.min(Math.max(ttl, 60), 2147483647)
773
+ }
774
+ );
775
+ }
776
+ async function deleteSessionCacheKeys(config, hashedTokens) {
777
+ const sc = config.sessionCache;
778
+ if (!sc?.enabled || hashedTokens.length === 0) {
779
+ return;
780
+ }
781
+ await Promise.all(
782
+ hashedTokens.map((t) => sc.kv.delete(sessionCacheKey(config, t)))
783
+ );
784
+ }
785
+ async function invalidateSessionCacheForHashedToken(config, hashedToken) {
786
+ await deleteSessionCacheKeys(config, [hashedToken]);
787
+ }
788
+ async function invalidateSessionCacheForUser(config, database, userId, tenantId) {
789
+ const tokens = await listHashedTokensForUser({
790
+ database,
791
+ userId,
792
+ tenantId
527
793
  });
528
- };
794
+ await deleteSessionCacheKeys(config, tokens);
795
+ }
529
796
 
530
- // src/lib/crypto.ts
531
- import { scrypt } from "@noble/hashes/scrypt.js";
532
- import { randomBytes } from "@noble/hashes/utils.js";
533
- var encoder = new TextEncoder();
534
- var randomHex = (bytes) => {
535
- const arr = randomBytes(bytes);
536
- return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join(
537
- ""
538
- );
539
- };
540
- var toHex = (buffer) => {
541
- return Array.from(
542
- buffer,
543
- (b) => b.toString(16).padStart(2, "0")
544
- ).join("");
545
- };
546
- var hexToBytes = (hex) => {
547
- const bytes = new Uint8Array(hex.length / 2);
548
- for (let i = 0; i < hex.length; i += 2) {
549
- bytes[i / 2] = Number.parseInt(hex.slice(i, i + 2), 16);
797
+ // src/lib/load-session-pair.ts
798
+ async function loadSessionPair(database, config, hashedToken) {
799
+ const cached = await readSessionCache(config, hashedToken);
800
+ if (cached) {
801
+ return { session: cached.session, user: cached.user };
802
+ }
803
+ const session = await fetchSessionByToken({
804
+ database,
805
+ hashedToken,
806
+ tenantId: config.tenant?.tenantId || "tenant"
807
+ });
808
+ if (!session) {
809
+ return null;
810
+ }
811
+ const user = await fetchUserWithRoles({
812
+ database,
813
+ userId: session.userId,
814
+ tenantId: session.tenantId
815
+ });
816
+ if (!user) {
817
+ await deleteSession({
818
+ database,
819
+ sessionId: session.id,
820
+ tenantId: session.tenantId
821
+ });
822
+ return null;
823
+ }
824
+ await writeSessionCache(config, hashedToken, { session, user });
825
+ return { session, user };
826
+ }
827
+
828
+ // src/lib/normalize-rate-limit-ip.ts
829
+ function parseIpv4Dotted(s) {
830
+ const p = s.split(".");
831
+ if (p.length !== 4) {
832
+ return null;
833
+ }
834
+ const b = new Uint8Array(4);
835
+ for (let i = 0; i < 4; i++) {
836
+ if (!/^\d{1,3}$/.test(p[i])) {
837
+ return null;
838
+ }
839
+ const n = Number(p[i]);
840
+ if (!Number.isInteger(n) || n < 0 || n > 255) {
841
+ return null;
842
+ }
843
+ b[i] = n;
844
+ }
845
+ return b;
846
+ }
847
+ function ipv4Key(bytes) {
848
+ return `4:${bytes[0]}.${bytes[1]}.${bytes[2]}.${bytes[3]}`;
849
+ }
850
+ function maskIpv6InPlace(bytes, prefixBits) {
851
+ if (prefixBits >= 128) {
852
+ return;
853
+ }
854
+ const whole = Math.floor(prefixBits / 8);
855
+ const rem = prefixBits % 8;
856
+ if (rem === 0) {
857
+ for (let i = whole; i < 16; i++) {
858
+ bytes[i] = 0;
859
+ }
860
+ } else {
861
+ for (let i = whole + 1; i < 16; i++) {
862
+ bytes[i] = 0;
863
+ }
864
+ const keep = 255 << 8 - rem & 255;
865
+ bytes[whole] &= keep;
550
866
  }
551
- return bytes;
552
- };
553
- var SCRYPT_KEYLEN = 64;
554
- var SCRYPT_COST = 16384;
555
- var SCRYPT_BLOCK_SIZE = 8;
556
- var SCRYPT_PARALLELISM = 1;
557
- var hashPassword = async (password) => {
558
- const salt = randomBytes(16);
559
- const saltHex = toHex(salt);
560
- const passwordBytes = encoder.encode(password);
561
- const derivedKey = await Promise.resolve(
562
- scrypt(passwordBytes, salt, {
563
- N: SCRYPT_COST,
564
- r: SCRYPT_BLOCK_SIZE,
565
- p: SCRYPT_PARALLELISM,
566
- dkLen: SCRYPT_KEYLEN
567
- })
568
- );
569
- return `${saltHex}:${toHex(derivedKey)}`;
570
- };
571
- var verifyPassword = async (password, hashed) => {
572
- if (!hashed) {
573
- return false;
867
+ }
868
+ function isIpv4MappedIpv6(bytes) {
869
+ for (let i = 0; i < 10; i++) {
870
+ if (bytes[i] !== 0) {
871
+ return false;
872
+ }
574
873
  }
575
- const [saltHex, keyHex] = hashed.split(":");
576
- if (!(saltHex && keyHex)) {
577
- return false;
874
+ return bytes[10] === 255 && bytes[11] === 255;
875
+ }
876
+ function parseIpv6HextetsToBytes(s) {
877
+ const buf = new Uint8Array(16);
878
+ if (s === "") {
879
+ return buf;
578
880
  }
579
- const salt = hexToBytes(saltHex);
580
- const passwordBytes = encoder.encode(password);
581
- const derivedKey = await Promise.resolve(
582
- scrypt(passwordBytes, salt, {
583
- N: SCRYPT_COST,
584
- r: SCRYPT_BLOCK_SIZE,
585
- p: SCRYPT_PARALLELISM,
586
- dkLen: SCRYPT_KEYLEN
587
- })
588
- );
589
- const derived = toHex(derivedKey);
590
- if (derived.length !== keyHex.length) {
591
- return false;
881
+ const double = s.includes("::");
882
+ if (double && s.split("::").length > 2) {
883
+ return null;
592
884
  }
593
- let result = 0;
594
- for (let i = 0; i < derived.length; i++) {
595
- result |= derived.charCodeAt(i) ^ keyHex.charCodeAt(i);
885
+ const [leftRaw, rightRaw] = double ? s.split("::", 2) : [s, ""];
886
+ const left = leftRaw ? leftRaw.split(":").filter(Boolean) : [];
887
+ const right = rightRaw ? rightRaw.split(":").filter(Boolean) : [];
888
+ const partsLen = left.length + right.length;
889
+ if (double) {
890
+ if (partsLen > 8) {
891
+ return null;
892
+ }
893
+ } else if (partsLen !== 8) {
894
+ return null;
596
895
  }
597
- return result === 0;
598
- };
599
- var hashToken = async (token, secret) => {
600
- if (!secret || secret.length === 0) {
601
- throw new Error(
602
- "AUTH_SECRET is required and must be a non-empty string. Please set AUTH_SECRET environment variable."
603
- );
896
+ const pad = double ? 8 - partsLen : 0;
897
+ const all = [...left, ...Array(pad).fill("0"), ...right];
898
+ if (all.length !== 8) {
899
+ return null;
604
900
  }
605
- const key = await crypto.subtle.importKey(
606
- "raw",
607
- encoder.encode(secret),
608
- { name: "HMAC", hash: "SHA-256" },
609
- false,
610
- ["sign"]
611
- );
612
- const signature = await crypto.subtle.sign(
613
- "HMAC",
614
- key,
615
- encoder.encode(token)
616
- );
617
- return toHex(new Uint8Array(signature));
618
- };
619
- var generateToken = (bytes = 48) => randomHex(bytes);
620
-
621
- // src/lib/error-handler.ts
622
- import { logger } from "@mesob/common";
623
- import { HTTPException } from "hono/http-exception";
624
- var isDatabaseError = (error) => {
625
- if (typeof error !== "object" || error === null) {
626
- return false;
901
+ for (let i = 0; i < 8; i++) {
902
+ const h = all[i];
903
+ if (!h || h.includes(".")) {
904
+ return null;
905
+ }
906
+ const v = Number.parseInt(h, 16);
907
+ if (v > 65535 || Number.isNaN(v)) {
908
+ return null;
909
+ }
910
+ buf[i * 2] = v >> 8;
911
+ buf[i * 2 + 1] = v & 255;
627
912
  }
628
- if ("code" in error || "query" in error || "detail" in error) {
629
- return true;
913
+ return buf;
914
+ }
915
+ function parseIpv6ToBytes(address) {
916
+ let a = address.trim().toLowerCase();
917
+ if (a.startsWith("[") && a.endsWith("]")) {
918
+ a = a.slice(1, -1);
630
919
  }
631
- if (error instanceof Error) {
632
- const message = error.message.toLowerCase();
633
- return message.includes("failed query") || message.includes("relation") || message.includes("column") || message.includes("syntax error") || message.includes("duplicate key") || message.includes("foreign key") || message.includes("null value");
920
+ const zi = a.indexOf("%");
921
+ if (zi >= 0) {
922
+ a = a.slice(0, zi);
634
923
  }
635
- return false;
636
- };
637
- var sanitizeDatabaseError = (error) => {
638
- const code = error.code;
639
- if (code === "23505") {
640
- return "Resource already exists";
924
+ if (a === "") {
925
+ return null;
641
926
  }
642
- if (code === "23503") {
643
- return "Referenced resource not found";
927
+ if (!a.includes(":")) {
928
+ const v4 = parseIpv4Dotted(a);
929
+ return v4 ? v4 : null;
930
+ }
931
+ const lastColon = a.lastIndexOf(":");
932
+ if (lastColon > 0) {
933
+ const maybeV4 = a.slice(lastColon + 1);
934
+ if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(maybeV4)) {
935
+ const v4b = parseIpv4Dotted(maybeV4);
936
+ if (!v4b) {
937
+ return null;
938
+ }
939
+ const prefix = a.slice(0, lastColon);
940
+ const w1 = v4b[0] << 8 | v4b[1];
941
+ const w2 = v4b[2] << 8 | v4b[3];
942
+ const tailHex = `${w1.toString(16)}:${w2.toString(16)}`;
943
+ let merged;
944
+ if (!prefix) {
945
+ merged = tailHex;
946
+ } else if (prefix.endsWith(":")) {
947
+ merged = `${prefix}${tailHex}`;
948
+ } else {
949
+ merged = `${prefix}:${tailHex}`;
950
+ }
951
+ return parseIpv6HextetsToBytes(merged);
952
+ }
644
953
  }
645
- if (code === "23502") {
646
- return "Required field is missing";
954
+ return parseIpv6HextetsToBytes(a);
955
+ }
956
+ function bytesToHex(bytes) {
957
+ let o = "";
958
+ for (let i = 0; i < bytes.length; i++) {
959
+ o += bytes[i].toString(16).padStart(2, "0");
647
960
  }
648
- if (code === "42P01") {
649
- return "Resource not found";
961
+ return o;
962
+ }
963
+ function rateLimitClientKey(raw, ipv6SubnetBits) {
964
+ const s = raw.trim();
965
+ if (!s || s === "unknown") {
966
+ return "unknown";
650
967
  }
651
- if (code === "42703") {
652
- return "Invalid request";
968
+ const prefixBits = Number.isFinite(ipv6SubnetBits) && ipv6SubnetBits >= 1 && ipv6SubnetBits <= 128 ? Math.floor(ipv6SubnetBits) : 64;
969
+ if (!s.includes(":")) {
970
+ const v4 = parseIpv4Dotted(s);
971
+ return v4 ? ipv4Key(v4) : `raw:${s}`;
653
972
  }
654
- if (code === "23514") {
655
- return "Validation failed";
973
+ const bytes = parseIpv6ToBytes(s);
974
+ if (!bytes) {
975
+ return `raw:${s}`;
656
976
  }
657
- return "An error occurred while processing your request";
658
- };
659
- var isDatabaseErrorMessage = (message) => {
660
- const lowerMessage = message.toLowerCase();
661
- return lowerMessage.includes("failed query") || lowerMessage.includes("select") || lowerMessage.includes("insert") || lowerMessage.includes("update") || lowerMessage.includes("delete") || lowerMessage.includes("from") || lowerMessage.includes("where") || lowerMessage.includes("limit") || lowerMessage.includes("params:") || lowerMessage.includes("query") || message.includes('"iam".') || message.includes('"tenants"') || message.includes('"users"') || message.includes('"sessions"') || message.includes('"accounts"') || lowerMessage.includes("relation") || lowerMessage.includes("column") || lowerMessage.includes("syntax error") || lowerMessage.includes("database") || lowerMessage.includes("postgres") || lowerMessage.includes("sql");
662
- };
663
- var handleError = (error, c) => {
664
- logger.error("API Error:", {
665
- error,
666
- path: c.req.path,
667
- method: c.req.method,
668
- url: c.req.url
669
- });
670
- if (error instanceof HTTPException) {
671
- const message = isDatabaseErrorMessage(error.message) ? "An error occurred while processing your request" : error.message;
672
- return c.json({ error: message }, error.status);
977
+ if (isIpv4MappedIpv6(bytes)) {
978
+ return ipv4Key(bytes.subarray(12, 16));
673
979
  }
674
- if (isDatabaseError(error)) {
675
- const userMessage = sanitizeDatabaseError(error);
676
- logger.error("Database error details:", {
677
- code: error.code,
678
- message: error.message,
679
- detail: error.detail,
680
- query: error.query,
681
- parameters: error.parameters
682
- });
683
- return c.json({ error: userMessage }, 500);
980
+ maskIpv6InPlace(bytes, prefixBits);
981
+ return `6:${bytesToHex(bytes)}`;
982
+ }
983
+
984
+ // src/lib/auth-rate-limit.ts
985
+ var AUTH_RATE_LIMIT_POST_PATHS = /* @__PURE__ */ new Set([
986
+ "/check-account",
987
+ "/sign-in",
988
+ "/sign-up",
989
+ "/password/forgot",
990
+ "/password/reset",
991
+ "/password/set",
992
+ "/email/verification/request",
993
+ "/email/verification/confirm",
994
+ "/phone/verification/request",
995
+ "/phone/verification/confirm"
996
+ ]);
997
+ var DEFAULT_MESSAGE = "Too many requests. Please wait before trying again.";
998
+ var RL_PREFIX = "msb:rl:v1:";
999
+ function getClientIp(c, headerName) {
1000
+ const primary = c.req.header(headerName)?.trim();
1001
+ if (primary) {
1002
+ return primary;
1003
+ }
1004
+ const forwarded = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
1005
+ if (forwarded) {
1006
+ return forwarded;
1007
+ }
1008
+ return c.req.header("x-real-ip")?.trim() || "unknown";
1009
+ }
1010
+ function rateLimitKey(path, windowStart, ip) {
1011
+ const bucket = path.replaceAll(/[^a-z0-9/-]/gi, "_");
1012
+ return `${RL_PREFIX}${bucket}:${ip}:${windowStart}`;
1013
+ }
1014
+ async function checkAuthRateLimit(c, config) {
1015
+ const rl = config.rateLimit;
1016
+ if (!rl?.enabled) {
1017
+ return { limited: false };
1018
+ }
1019
+ if (c.req.method !== "POST") {
1020
+ return { limited: false };
1021
+ }
1022
+ const path = c.req.path;
1023
+ if (!AUTH_RATE_LIMIT_POST_PATHS.has(path)) {
1024
+ return { limited: false };
1025
+ }
1026
+ const rawIp = getClientIp(c, rl.ipHeader ?? "cf-connecting-ip");
1027
+ const ipKey = rateLimitClientKey(rawIp, rl.ipv6Subnet ?? 64);
1028
+ const now = Math.floor(Date.now() / 1e3);
1029
+ const windowStart = Math.floor(now / rl.window) * rl.window;
1030
+ const key = rateLimitKey(path, windowStart, ipKey);
1031
+ const msg = rl.message ?? DEFAULT_MESSAGE;
1032
+ const raw = await rl.kv.get(key);
1033
+ let count = 0;
1034
+ if (raw) {
1035
+ try {
1036
+ const parsed = JSON.parse(raw);
1037
+ count = typeof parsed.n === "number" ? parsed.n : 0;
1038
+ } catch {
1039
+ count = 0;
1040
+ }
684
1041
  }
685
- if (error instanceof Error) {
686
- const message = error.message;
687
- const lowerMessage = message.toLowerCase();
688
- const isDatabaseError2 = lowerMessage.includes("failed query") || lowerMessage.includes("select") || lowerMessage.includes("insert") || lowerMessage.includes("update") || lowerMessage.includes("delete") || lowerMessage.includes("from") || lowerMessage.includes("where") || lowerMessage.includes("limit") || lowerMessage.includes("params:") || lowerMessage.includes("query") || message.includes('"iam".') || message.includes('"tenants"') || message.includes('"users"') || message.includes('"sessions"') || message.includes('"accounts"') || lowerMessage.includes("relation") || lowerMessage.includes("column") || lowerMessage.includes("syntax error") || lowerMessage.includes("duplicate key") || lowerMessage.includes("foreign key") || lowerMessage.includes("null value") || lowerMessage.includes("database") || lowerMessage.includes("postgres") || lowerMessage.includes("sql");
689
- if (isDatabaseError2) {
690
- logger.error("SQL/database error detected:", {
691
- message: error.message,
692
- stack: error.stack,
693
- name: error.name
694
- });
1042
+ if (count >= rl.max) {
1043
+ return { limited: true, message: msg };
1044
+ }
1045
+ await rl.kv.put(key, JSON.stringify({ n: count + 1 }), {
1046
+ expirationTtl: Math.min(rl.window * 2, 2147483647)
1047
+ });
1048
+ return { limited: false };
1049
+ }
1050
+
1051
+ // src/middlewares/auth-rate-limit-middleware.ts
1052
+ var createAuthRateLimitMiddleware = (config) => {
1053
+ return async (c, next) => {
1054
+ const result = await checkAuthRateLimit(c, config);
1055
+ if (result.limited) {
695
1056
  return c.json(
696
- { error: "An error occurred while processing your request" },
697
- 500
1057
+ { error: result.message, code: "RATE_LIMITED" },
1058
+ 429
698
1059
  );
699
1060
  }
700
- logger.error("Error details:", {
701
- message: error.message,
702
- stack: error.stack,
703
- name: error.name
704
- });
705
- return c.json(
706
- { error: "An error occurred while processing your request" },
707
- 500
708
- );
709
- }
710
- logger.error("Unknown error:", error);
711
- return c.json(
712
- { error: "An error occurred while processing your request" },
713
- 500
714
- );
1061
+ await next();
1062
+ };
715
1063
  };
716
1064
 
717
1065
  // src/routes/index.ts
@@ -932,7 +1280,10 @@ var normalizeAuthUser = (user) => ({
932
1280
  phone: user.phone,
933
1281
  image: user.image,
934
1282
  emailVerified: user.emailVerified,
935
- phoneVerified: user.phoneVerified
1283
+ phoneVerified: user.phoneVerified,
1284
+ roles: user.roles ?? null,
1285
+ roleCodes: user.roleCodes ?? null,
1286
+ permissions: user.permissions ?? null
936
1287
  });
937
1288
  var normalizeAuthSession = (session) => session ? {
938
1289
  id: session.id,
@@ -1831,6 +2182,7 @@ var signOutHandler = async (c) => {
1831
2182
  )
1832
2183
  );
1833
2184
  }
2185
+ await invalidateSessionCacheForHashedToken(config, hashedToken);
1834
2186
  deleteSessionCookie(c, config);
1835
2187
  return c.json({ message: "Signed out" }, 200);
1836
2188
  };
@@ -2617,6 +2969,12 @@ var emailVerificationConfirmHandler = async (c) => {
2617
2969
  if (result.status === "error") {
2618
2970
  return c.json({ error: result.error }, 400);
2619
2971
  }
2972
+ await invalidateSessionCacheForUser(
2973
+ config,
2974
+ database,
2975
+ result.user.id,
2976
+ resolvedTenantId
2977
+ );
2620
2978
  setSessionCookie(c, result.sessionToken, config, {
2621
2979
  expires: new Date(result.expiresAt)
2622
2980
  });
@@ -2872,6 +3230,12 @@ var changePasswordHandler = async (c) => {
2872
3230
  eq18(accountsInIam.provider, "credentials")
2873
3231
  )
2874
3232
  );
3233
+ await invalidateSessionCacheForUser(
3234
+ config,
3235
+ database,
3236
+ userId,
3237
+ resolvedTenantId
3238
+ );
2875
3239
  const currentSessionToken = getCookie2(c, getSessionCookieName(config));
2876
3240
  if (currentSessionToken) {
2877
3241
  const hashedToken = await hashToken(currentSessionToken, config.secret);
@@ -3152,6 +3516,12 @@ var resetPasswordHandler = async (c) => {
3152
3516
  eq20(accountsInIam.provider, "credentials")
3153
3517
  )
3154
3518
  );
3519
+ await invalidateSessionCacheForUser(
3520
+ config,
3521
+ database,
3522
+ verification.userId,
3523
+ resolvedTenantId
3524
+ );
3155
3525
  await database.delete(sessionsInIam).where(
3156
3526
  and20(
3157
3527
  eq20(sessionsInIam.tenantId, resolvedTenantId),
@@ -3991,6 +4361,12 @@ var phoneVerificationConfirmHandler = async (c) => {
3991
4361
  if (result.status === "error") {
3992
4362
  return c.json({ error: result.error }, 400);
3993
4363
  }
4364
+ await invalidateSessionCacheForUser(
4365
+ config,
4366
+ database,
4367
+ result.user.id,
4368
+ resolvedTenantId
4369
+ );
3994
4370
  if (!result.session) {
3995
4371
  return c.json(
3996
4372
  {
@@ -4397,6 +4773,12 @@ var updateProfileHandler = async (c) => {
4397
4773
  if (!updatedUser) {
4398
4774
  return c.json({ error: "User not found" }, 404);
4399
4775
  }
4776
+ await invalidateSessionCacheForUser(
4777
+ config,
4778
+ database,
4779
+ userId,
4780
+ resolvedTenantId
4781
+ );
4400
4782
  return c.json({ user: normalizeUser(updatedUser) }, 200);
4401
4783
  };
4402
4784
 
@@ -4417,6 +4799,12 @@ var updateEmailHandler = async (c) => {
4417
4799
  return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
4418
4800
  }
4419
4801
  const resolvedTenantId = ensureTenantId(config, tenantId);
4802
+ await invalidateSessionCacheForUser(
4803
+ config,
4804
+ database,
4805
+ userId,
4806
+ resolvedTenantId
4807
+ );
4420
4808
  if (user.email && session?.id) {
4421
4809
  await database.delete(sessionsInIam).where(
4422
4810
  and28(
@@ -4482,6 +4870,12 @@ var updatePhoneHandler = async (c) => {
4482
4870
  return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
4483
4871
  }
4484
4872
  const resolvedTenantId = ensureTenantId(config, tenantId);
4873
+ await invalidateSessionCacheForUser(
4874
+ config,
4875
+ database,
4876
+ userId,
4877
+ resolvedTenantId
4878
+ );
4485
4879
  const phoneValidator = createPhoneField(config);
4486
4880
  if (!phoneValidator.validate(body.phone)) {
4487
4881
  return c.json({ error: "Invalid phone number format" }, 400);
@@ -6147,12 +6541,14 @@ var listSessionsHandler = async (c) => {
6147
6541
  import { and as and45, eq as eq46 } from "drizzle-orm";
6148
6542
  var revokeAllSessionsHandler = async (c) => {
6149
6543
  const { userId } = c.req.valid("param");
6544
+ const config = c.get("config");
6150
6545
  const database = c.get("database");
6151
6546
  const tenantId = c.get("tenantId");
6152
6547
  const [user] = await database.select().from(usersInIam).where(and45(eq46(usersInIam.id, userId), eq46(usersInIam.tenantId, tenantId))).limit(1);
6153
6548
  if (!user) {
6154
6549
  return c.json({ error: "User not found" }, 404);
6155
6550
  }
6551
+ await invalidateSessionCacheForUser(config, database, userId, tenantId);
6156
6552
  await database.delete(sessionsInIam).where(
6157
6553
  and45(
6158
6554
  eq46(sessionsInIam.tenantId, tenantId),
@@ -6172,6 +6568,8 @@ var revokeSessionHandler = async (c) => {
6172
6568
  if (!existing) {
6173
6569
  return c.json({ error: "Session not found" }, 404);
6174
6570
  }
6571
+ const config = c.get("config");
6572
+ await deleteSessionCacheKeys(config, [existing.token]);
6175
6573
  await database.delete(sessionsInIam).where(and46(eq47(sessionsInIam.id, id), eq47(sessionsInIam.tenantId, tenantId)));
6176
6574
  return c.json({ message: "Session revoked" }, 200);
6177
6575
  };
@@ -6775,6 +7173,12 @@ var assignUserRoleHandler = async (c) => {
6775
7173
  userId: body.userId,
6776
7174
  roleId: body.roleId
6777
7175
  }).returning();
7176
+ await invalidateSessionCacheForUser(
7177
+ config,
7178
+ database,
7179
+ body.userId,
7180
+ resolvedTenantId
7181
+ );
6778
7182
  return c.json({ userRole }, 201);
6779
7183
  } catch (error) {
6780
7184
  if (error && typeof error === "object" && "code" in error && error.code === "23505") {
@@ -6805,6 +7209,7 @@ var listUserRolesHandler = async (c) => {
6805
7209
  import { and as and49, eq as eq54 } from "drizzle-orm";
6806
7210
  var revokeUserRoleHandler = async (c) => {
6807
7211
  const { id } = c.req.valid("param");
7212
+ const config = c.get("config");
6808
7213
  const database = c.get("database");
6809
7214
  const tenantId = c.get("tenantId");
6810
7215
  const [existing] = await database.select().from(userRolesInIam).where(
@@ -6816,6 +7221,12 @@ var revokeUserRoleHandler = async (c) => {
6816
7221
  await database.delete(userRolesInIam).where(
6817
7222
  and49(eq54(userRolesInIam.id, id), eq54(userRolesInIam.tenantId, tenantId))
6818
7223
  );
7224
+ await invalidateSessionCacheForUser(
7225
+ config,
7226
+ database,
7227
+ existing.userId,
7228
+ tenantId
7229
+ );
6819
7230
  return c.json({ message: "Role revoked from user" }, 200);
6820
7231
  };
6821
7232
 
@@ -6947,6 +7358,7 @@ import { and as and50, eq as eq55, sql as sql25 } from "drizzle-orm";
6947
7358
  var banUserHandler = async (c) => {
6948
7359
  const { id } = c.req.valid("param");
6949
7360
  const body = c.req.valid("json");
7361
+ const config = c.get("config");
6950
7362
  const database = c.get("database");
6951
7363
  const tenantId = c.get("tenantId");
6952
7364
  const [existing] = await database.select().from(usersInIam).where(and50(eq55(usersInIam.id, id), eq55(usersInIam.tenantId, tenantId))).limit(1);
@@ -6965,6 +7377,7 @@ var banUserHandler = async (c) => {
6965
7377
  if (!userWithRoles) {
6966
7378
  return c.json({ error: "User not found" }, 404);
6967
7379
  }
7380
+ await invalidateSessionCacheForUser(config, database, id, tenantId);
6968
7381
  return c.json({ user: normalizeUser(userWithRoles) }, 200);
6969
7382
  };
6970
7383
 
@@ -7548,6 +7961,7 @@ import { and as and56, eq as eq61, inArray as inArray7, sql as sql28 } from "dri
7548
7961
  var updateUserHandler = async (c) => {
7549
7962
  const { id } = c.req.valid("param");
7550
7963
  const body = c.req.valid("json");
7964
+ const config = c.get("config");
7551
7965
  const database = c.get("database");
7552
7966
  const tenantId = c.get("tenantId");
7553
7967
  const [existing] = await database.select().from(usersInIam).where(and56(eq61(usersInIam.id, id), eq61(usersInIam.tenantId, tenantId))).limit(1);
@@ -7647,6 +8061,7 @@ var updateUserHandler = async (c) => {
7647
8061
  userId: id,
7648
8062
  tenantId
7649
8063
  });
8064
+ await invalidateSessionCacheForUser(config, database, id, tenantId);
7650
8065
  return c.json(
7651
8066
  {
7652
8067
  user: normalizeUser(userWithRoles ?? updated)
@@ -8225,26 +8640,14 @@ var loadSession = async ({
8225
8640
  }) => {
8226
8641
  try {
8227
8642
  const hashedToken = await hashToken(sessionToken, config.secret);
8228
- const session = await fetchSessionByToken({
8229
- database,
8230
- hashedToken,
8231
- tenantId: config.tenant?.tenantId || "tenant"
8232
- });
8233
- if (!session) {
8234
- return;
8235
- }
8236
- const user = await fetchUserWithRoles({
8237
- database,
8238
- userId: session.userId,
8239
- tenantId: session.tenantId
8240
- });
8241
- if (user) {
8643
+ const pair = await loadSessionPair(database, config, hashedToken);
8644
+ if (pair) {
8242
8645
  setAuthContext({
8243
8646
  c,
8244
8647
  config,
8245
8648
  database,
8246
- user,
8247
- session
8649
+ user: pair.user,
8650
+ session: pair.session
8248
8651
  });
8249
8652
  }
8250
8653
  } catch {
@@ -8282,6 +8685,9 @@ var createAuthRoutes = ({
8282
8685
  app.onError((error, c) => {
8283
8686
  return handleError(error, c);
8284
8687
  });
8688
+ if (config.rateLimit?.enabled) {
8689
+ app.use("*", createAuthRateLimitMiddleware(config));
8690
+ }
8285
8691
  app.use(
8286
8692
  "*",
8287
8693
  createAuthMiddleware({
@@ -8504,12 +8910,9 @@ var createGetSession = (database, config) => {
8504
8910
  }
8505
8911
  try {
8506
8912
  const hashedToken = await hashToken(sessionToken, config.secret);
8507
- const session = await fetchSessionByToken({
8508
- database,
8509
- hashedToken,
8510
- tenantId: config.tenant?.tenantId || "tenant"
8511
- });
8512
- if (!session) {
8913
+ const pair = await loadSessionPair(database, config, hashedToken);
8914
+ if (!pair) {
8915
+ await invalidateSessionCacheForHashedToken(config, hashedToken);
8513
8916
  deleteSessionCookie(c, config);
8514
8917
  return {
8515
8918
  session: null,
@@ -8518,25 +8921,7 @@ var createGetSession = (database, config) => {
8518
8921
  status: "invalid_session"
8519
8922
  };
8520
8923
  }
8521
- const user = await fetchUserWithRoles({
8522
- database,
8523
- userId: session.userId,
8524
- tenantId: session.tenantId
8525
- });
8526
- if (!user) {
8527
- await deleteSession({
8528
- database,
8529
- sessionId: session.id,
8530
- tenantId: session.tenantId
8531
- });
8532
- deleteSessionCookie(c, config);
8533
- return {
8534
- session: null,
8535
- user: null,
8536
- sessionToken: null,
8537
- status: "user_not_found"
8538
- };
8539
- }
8924
+ let { session, user } = pair;
8540
8925
  const rememberMe = session.meta?.rememberMe !== false;
8541
8926
  const updateAge = getSessionUpdateAge({
8542
8927
  sessionConfig: config.session,
@@ -8556,12 +8941,8 @@ var createGetSession = (database, config) => {
8556
8941
  setSessionCookie(c, sessionToken, config, {
8557
8942
  expires: new Date(newExpiresAt)
8558
8943
  });
8559
- return {
8560
- session: { ...session, expiresAt: newExpiresAt },
8561
- user,
8562
- sessionToken,
8563
- status: "valid"
8564
- };
8944
+ session = { ...session, expiresAt: newExpiresAt };
8945
+ await writeSessionCache(config, hashedToken, { session, user });
8565
8946
  }
8566
8947
  return { session, user, sessionToken, status: "valid" };
8567
8948
  } catch {
@@ -8597,9 +8978,7 @@ var defaultAuthConfig = {
8597
8978
  docs: {
8598
8979
  enabled: true
8599
8980
  },
8600
- cookie: {
8601
- prefix: "msb"
8602
- },
8981
+ prefix: "msb",
8603
8982
  session: {
8604
8983
  expiresIn: "7d",
8605
8984
  rememberMeExpiresIn: "30d",
@@ -8622,7 +9001,9 @@ var defaultAuthConfig = {
8622
9001
  security: {
8623
9002
  maxLoginAttempts: 5,
8624
9003
  lockoutDuration: "15m"
8625
- }
9004
+ },
9005
+ sessionCache: { enabled: false },
9006
+ rateLimit: { enabled: false }
8626
9007
  };
8627
9008
 
8628
9009
  // src/lib/cleanup.ts
@@ -8655,6 +9036,21 @@ var createMesobAuth = (authConfig) => {
8655
9036
  "AUTH_SECRET is required and must be a non-empty string. Please set AUTH_SECRET environment variable."
8656
9037
  );
8657
9038
  }
9039
+ if (config.sessionCache?.enabled && !config.sessionCache.kv) {
9040
+ throw new Error("sessionCache.kv is required when sessionCache.enabled");
9041
+ }
9042
+ if (config.rateLimit?.enabled) {
9043
+ if (!config.rateLimit.kv) {
9044
+ throw new Error("rateLimit.kv is required when rateLimit.enabled");
9045
+ }
9046
+ if (config.rateLimit.window < 1 || config.rateLimit.max < 1) {
9047
+ throw new Error("rateLimit.window and rateLimit.max must be >= 1");
9048
+ }
9049
+ const ipv6s = config.rateLimit.ipv6Subnet;
9050
+ if (ipv6s !== void 0 && (!Number.isFinite(ipv6s) || ipv6s < 1 || ipv6s > 128)) {
9051
+ throw new Error("rateLimit.ipv6Subnet must be between 1 and 128");
9052
+ }
9053
+ }
8658
9054
  const database = createDatabase(config.connectionString);
8659
9055
  const routesApp = createAuthRoutes({ config, database });
8660
9056
  const basePath = config.basePath || "";
@@ -8683,14 +9079,23 @@ var createMesobAuth = (authConfig) => {
8683
9079
  };
8684
9080
  };
8685
9081
  export {
9082
+ AUTH_RATE_LIMIT_POST_PATHS,
8686
9083
  cleanupExpiredData,
8687
9084
  cleanupExpiredSessions,
8688
9085
  cleanupExpiredVerifications,
9086
+ createAuthRateLimitMiddleware,
8689
9087
  createDatabase,
8690
9088
  createMesobAuth,
8691
9089
  createSessionMiddleware,
8692
9090
  createTenantMiddleware,
9091
+ deleteSessionCacheKeys,
9092
+ getSessionCookieName,
9093
+ getSessionKeyNamespace,
8693
9094
  hasPermission,
8694
- hasPermissionThrow
9095
+ hasPermissionThrow,
9096
+ invalidateSessionCacheForHashedToken,
9097
+ invalidateSessionCacheForUser,
9098
+ rateLimitClientKey,
9099
+ sessionCacheKey
8695
9100
  };
8696
9101
  //# sourceMappingURL=index.js.map