astro-sessionkit 0.1.20 → 0.1.22

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.
@@ -8,10 +8,12 @@ export interface ResolvedConfig {
8
8
  runWithContext?: <T>(context: SessionContext, fn: () => T) => T | Promise<T>;
9
9
  getContextStore?: () => SessionContext | undefined;
10
10
  setContextStore?: (context: SessionContext) => void;
11
+ context?: any;
11
12
  globalProtect: boolean;
12
13
  exclude: string[];
13
14
  debug: boolean;
14
15
  }
16
+ export declare function resetConfig(): void;
15
17
  export declare function setConfig(userConfig: SessionKitConfig): void;
16
18
  export declare function getConfig(): ResolvedConfig;
17
19
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,gBAAgB,EAAE,WAAW,EAAE,cAAc,EAAW,cAAc,EAAC,MAAM,SAAS,CAAC;AAOpG,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,GAAG;QAC3C,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;KAChC,CAAC;IACF,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7E,eAAe,CAAC,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IACnD,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IACpD,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAClB;AAoBD,wBAAgB,SAAS,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAiG5D;AAKD,wBAAgB,SAAS,IAAI,cAAc,CAE1C"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,gBAAgB,EAAE,WAAW,EAAE,cAAc,EAAW,cAAc,EAAC,MAAM,SAAS,CAAC;AAOpG,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,GAAG;QAC3C,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;KAChC,CAAC;IACF,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7E,eAAe,CAAC,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IACnD,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IACpD,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAClB;AA0BD,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAKD,wBAAgB,SAAS,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAkG5D;AAKD,wBAAgB,SAAS,IAAI,cAAc,CAE1C"}
@@ -1,6 +1,8 @@
1
1
  import { isValidRedirectPath, isValidPattern } from './validation.js';
2
2
  import { warn } from './logger.js';
3
3
 
4
+ const CONFIG_KEY = Symbol.for('astro-sessionkit.config');
5
+ const globalStorage = globalThis;
4
6
  const DEFAULT_CONFIG = {
5
7
  loginPath: "/login",
6
8
  protect: [],
@@ -13,7 +15,9 @@ const DEFAULT_CONFIG = {
13
15
  exclude: [],
14
16
  debug: false,
15
17
  };
16
- let config = { ...DEFAULT_CONFIG };
18
+ if (!globalStorage[CONFIG_KEY]) {
19
+ globalStorage[CONFIG_KEY] = Object.freeze({ ...DEFAULT_CONFIG });
20
+ }
17
21
  function setConfig(userConfig) {
18
22
  const newConfig = { ...DEFAULT_CONFIG };
19
23
  if (userConfig.loginPath !== undefined) {
@@ -61,6 +65,7 @@ function setConfig(userConfig) {
61
65
  newConfig.runWithContext = userConfig.runWithContext;
62
66
  newConfig.getContextStore = userConfig.getContextStore;
63
67
  newConfig.setContextStore = userConfig.setContextStore;
68
+ newConfig.context = userConfig.context;
64
69
  if (userConfig.globalProtect !== undefined) {
65
70
  newConfig.globalProtect = userConfig.globalProtect;
66
71
  }
@@ -79,10 +84,18 @@ function setConfig(userConfig) {
79
84
  if (userConfig.debug !== undefined) {
80
85
  newConfig.debug = userConfig.debug;
81
86
  }
82
- config = Object.freeze(newConfig);
87
+ globalStorage[CONFIG_KEY] = Object.freeze(newConfig);
83
88
  }
84
89
  function getConfig() {
85
- return config;
90
+ return globalStorage[CONFIG_KEY] || DEFAULT_CONFIG;
91
+ }
92
+ try {
93
+ const injectedConfig = typeof __SESSIONKIT_CONFIG__ !== 'undefined' ? __SESSIONKIT_CONFIG__ : undefined;
94
+ if (injectedConfig) {
95
+ setConfig(injectedConfig);
96
+ }
97
+ }
98
+ catch (e) {
86
99
  }
87
100
 
88
101
  export { getConfig, setConfig };
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sources":["../../src/core/config.ts"],"sourcesContent":["// ============================================================================\n// Configuration Store\n// ============================================================================\n\nimport type {SessionKitConfig, AccessHooks, ProtectionRule, Session, SessionContext} from \"./types\";\nimport {isValidPattern, isValidRedirectPath} from \"./validation\";\nimport * as logger from \"./logger\";\n\n/**\n * Internal config with defaults applied\n */\nexport interface ResolvedConfig {\n loginPath: string;\n protect: ProtectionRule[];\n access: Required<Omit<AccessHooks, \"check\">> & {\n check?: AccessHooks[\"check\"];\n };\n runWithContext?: <T>(context: SessionContext, fn: () => T) => T | Promise<T>;\n getContextStore?: () => SessionContext | undefined;\n setContextStore?: (context: SessionContext) => void;\n globalProtect: boolean;\n exclude: string[];\n debug: boolean;\n}\n\nconst DEFAULT_CONFIG: ResolvedConfig = {\n loginPath: \"/login\",\n protect: [],\n access: {\n getRole: (session: Session | null) => session?.role ?? null,\n getPermissions: (session: Session | null) => session?.permissions ?? [],\n check: undefined,\n },\n globalProtect: false,\n exclude: [],\n debug: false,\n};\n\nlet config: ResolvedConfig = { ...DEFAULT_CONFIG };\n\n/**\n * Set configuration (called by integration)\n */\nexport function setConfig(userConfig: SessionKitConfig): void {\n // Start with default config\n const newConfig: ResolvedConfig = { ...DEFAULT_CONFIG };\n\n // Validate and set loginPath\n if (userConfig.loginPath !== undefined) {\n if (!isValidRedirectPath(userConfig.loginPath)) {\n throw new Error(\n `[SessionKit] Invalid loginPath: \"${userConfig.loginPath}\". Must start with / and be less than 500 characters.`\n );\n }\n newConfig.loginPath = userConfig.loginPath;\n }\n\n // Validate protection rules\n if (userConfig.protect) {\n for (const rule of userConfig.protect) {\n // Validate pattern\n if (!isValidPattern(rule.pattern)) {\n throw new Error(\n `[SessionKit] Invalid pattern: \"${rule.pattern}\". ` +\n `Patterns must start with / and be less than 1000 characters.`\n );\n }\n\n // Validate redirectTo if present\n if (rule.redirectTo && !isValidRedirectPath(rule.redirectTo)) {\n throw new Error(\n `[SessionKit] Invalid redirectTo: \"${rule.redirectTo}\". ` +\n `Must start with / and be less than 500 characters.`\n );\n }\n }\n newConfig.protect = [...userConfig.protect];\n }\n\n // Validate context store getter/setter pair\n if ((userConfig.getContextStore && !userConfig.setContextStore) || (!userConfig.getContextStore && userConfig.setContextStore)) {\n throw new Error(\n '[SessionKit] Both getContextStore and setContextStore must be provided together if using custom context storage.'\n );\n }\n\n // Set access hooks\n if (userConfig.access) {\n newConfig.access = {\n getRole: userConfig.access.getRole ?? DEFAULT_CONFIG.access.getRole,\n getPermissions: userConfig.access.getPermissions ?? DEFAULT_CONFIG.access.getPermissions,\n check: userConfig.access.check,\n };\n\n // Migration/Safety: If user misplaced globalProtect/exclude/debug in access object,\n // we honor them but warn about it.\n const anyAccess = userConfig.access as any;\n if (anyAccess.globalProtect !== undefined && userConfig.globalProtect === undefined) {\n newConfig.globalProtect = anyAccess.globalProtect;\n logger.warn('Deprecation: globalProtect should be at the top level of configuration, not inside \"access\".');\n }\n if (anyAccess.exclude !== undefined && userConfig.exclude === undefined) {\n newConfig.exclude = anyAccess.exclude;\n logger.warn('Deprecation: exclude should be at the top level of configuration, not inside \"access\".');\n }\n if (anyAccess.debug !== undefined && userConfig.debug === undefined) {\n newConfig.debug = anyAccess.debug;\n logger.warn('Deprecation: debug should be at the top level of configuration, not inside \"access\".');\n }\n }\n\n // Set context hooks\n newConfig.runWithContext = userConfig.runWithContext;\n newConfig.getContextStore = userConfig.getContextStore;\n newConfig.setContextStore = userConfig.setContextStore;\n \n if (userConfig.globalProtect !== undefined) {\n newConfig.globalProtect = userConfig.globalProtect;\n }\n\n if (userConfig.exclude !== undefined) {\n for (const pattern of userConfig.exclude) {\n if (!isValidPattern(pattern)) {\n throw new Error(\n `[SessionKit] Invalid exclude pattern: \"${pattern}\". ` +\n `Patterns must start with / and be less than 1000 characters.`\n );\n }\n }\n newConfig.exclude = [...userConfig.exclude];\n } else if (newConfig.exclude.length === 0) {\n newConfig.exclude = [...DEFAULT_CONFIG.exclude];\n }\n\n if (userConfig.debug !== undefined) {\n newConfig.debug = userConfig.debug;\n }\n\n // Atomic update\n config = Object.freeze(newConfig);\n}\n\n/**\n * Get current configuration\n */\nexport function getConfig(): ResolvedConfig {\n return config;\n}\n"],"names":["logger.warn"],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sources":["../../src/core/config.ts"],"sourcesContent":["// ============================================================================\n// Configuration Store\n// ============================================================================\n\nimport type {SessionKitConfig, AccessHooks, ProtectionRule, Session, SessionContext} from \"./types\";\nimport {isValidPattern, isValidRedirectPath} from \"./validation\";\nimport * as logger from \"./logger\";\n\n/**\n * Internal config with defaults applied\n */\nexport interface ResolvedConfig {\n loginPath: string;\n protect: ProtectionRule[];\n access: Required<Omit<AccessHooks, \"check\">> & {\n check?: AccessHooks[\"check\"];\n };\n runWithContext?: <T>(context: SessionContext, fn: () => T) => T | Promise<T>;\n getContextStore?: () => SessionContext | undefined;\n setContextStore?: (context: SessionContext) => void;\n context?: any;\n globalProtect: boolean;\n exclude: string[];\n debug: boolean;\n}\n\nconst CONFIG_KEY = Symbol.for('astro-sessionkit.config');\nconst globalStorage = globalThis as any;\n\nconst DEFAULT_CONFIG: ResolvedConfig = {\n loginPath: \"/login\",\n protect: [],\n access: {\n getRole: (session: Session | null) => session?.role ?? null,\n getPermissions: (session: Session | null) => session?.permissions ?? [],\n check: undefined,\n },\n globalProtect: false,\n exclude: [],\n debug: false,\n};\n\n// Initialize global storage if not present\nif (!globalStorage[CONFIG_KEY]) {\n globalStorage[CONFIG_KEY] = Object.freeze({ ...DEFAULT_CONFIG });\n}\n\n/**\n * Reset configuration to defaults (mainly for testing)\n */\nexport function resetConfig(): void {\n globalStorage[CONFIG_KEY] = Object.freeze({ ...DEFAULT_CONFIG });\n}\n\n/**\n * Set configuration (called by integration)\n */\nexport function setConfig(userConfig: SessionKitConfig): void {\n // Start with default config\n const newConfig: ResolvedConfig = { ...DEFAULT_CONFIG };\n\n // Validate and set loginPath\n if (userConfig.loginPath !== undefined) {\n if (!isValidRedirectPath(userConfig.loginPath)) {\n throw new Error(\n `[SessionKit] Invalid loginPath: \"${userConfig.loginPath}\". Must start with / and be less than 500 characters.`\n );\n }\n newConfig.loginPath = userConfig.loginPath;\n }\n\n // Validate protection rules\n if (userConfig.protect) {\n for (const rule of userConfig.protect) {\n // Validate pattern\n if (!isValidPattern(rule.pattern)) {\n throw new Error(\n `[SessionKit] Invalid pattern: \"${rule.pattern}\". ` +\n `Patterns must start with / and be less than 1000 characters.`\n );\n }\n\n // Validate redirectTo if present\n if (rule.redirectTo && !isValidRedirectPath(rule.redirectTo)) {\n throw new Error(\n `[SessionKit] Invalid redirectTo: \"${rule.redirectTo}\". ` +\n `Must start with / and be less than 500 characters.`\n );\n }\n }\n newConfig.protect = [...userConfig.protect];\n }\n\n // Validate context store getter/setter pair\n if ((userConfig.getContextStore && !userConfig.setContextStore) || (!userConfig.getContextStore && userConfig.setContextStore)) {\n throw new Error(\n '[SessionKit] Both getContextStore and setContextStore must be provided together if using custom context storage.'\n );\n }\n\n // Set access hooks\n if (userConfig.access) {\n newConfig.access = {\n getRole: userConfig.access.getRole ?? DEFAULT_CONFIG.access.getRole,\n getPermissions: userConfig.access.getPermissions ?? DEFAULT_CONFIG.access.getPermissions,\n check: userConfig.access.check,\n };\n\n // Migration/Safety: If user misplaced globalProtect/exclude/debug in access object,\n // we honor them but warn about it.\n const anyAccess = userConfig.access as any;\n if (anyAccess.globalProtect !== undefined && userConfig.globalProtect === undefined) {\n newConfig.globalProtect = anyAccess.globalProtect;\n logger.warn('Deprecation: globalProtect should be at the top level of configuration, not inside \"access\".');\n }\n if (anyAccess.exclude !== undefined && userConfig.exclude === undefined) {\n newConfig.exclude = anyAccess.exclude;\n logger.warn('Deprecation: exclude should be at the top level of configuration, not inside \"access\".');\n }\n if (anyAccess.debug !== undefined && userConfig.debug === undefined) {\n newConfig.debug = anyAccess.debug;\n logger.warn('Deprecation: debug should be at the top level of configuration, not inside \"access\".');\n }\n }\n\n // Set context hooks\n newConfig.runWithContext = userConfig.runWithContext;\n newConfig.getContextStore = userConfig.getContextStore;\n newConfig.setContextStore = userConfig.setContextStore;\n newConfig.context = userConfig.context;\n\n if (userConfig.globalProtect !== undefined) {\n newConfig.globalProtect = userConfig.globalProtect;\n }\n\n if (userConfig.exclude !== undefined) {\n for (const pattern of userConfig.exclude) {\n if (!isValidPattern(pattern)) {\n throw new Error(\n `[SessionKit] Invalid exclude pattern: \"${pattern}\". ` +\n `Patterns must start with / and be less than 1000 characters.`\n );\n }\n }\n newConfig.exclude = [...userConfig.exclude];\n } else if (newConfig.exclude.length === 0) {\n newConfig.exclude = [...DEFAULT_CONFIG.exclude];\n }\n\n if (userConfig.debug !== undefined) {\n newConfig.debug = userConfig.debug;\n }\n\n // Atomic update\n globalStorage[CONFIG_KEY] = Object.freeze(newConfig);\n}\n\n/**\n * Get current configuration\n */\nexport function getConfig(): ResolvedConfig {\n return globalStorage[CONFIG_KEY] || DEFAULT_CONFIG;\n}\n\n// Handle injected configuration from Astro integration\ntry {\n // @ts-ignore\n const injectedConfig = typeof __SESSIONKIT_CONFIG__ !== 'undefined' ? __SESSIONKIT_CONFIG__ : undefined;\n if (injectedConfig) {\n setConfig(injectedConfig);\n }\n} catch (e) {\n // Ignore errors in environments where __SESSIONKIT_CONFIG__ might be restricted\n}\n"],"names":["logger.warn"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import type { SessionContext } from "./types";
2
2
  export declare function runWithContext<T>(context: SessionContext, fn: () => T): T;
3
- export declare function getContextStore(): SessionContext | undefined;
3
+ export declare function getContextStore(): SessionContext;
4
+ export declare function hasContext(): boolean;
4
5
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/core/context.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAQ9C,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,cAAc,EACvB,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAEH;AAKD,wBAAgB,eAAe,IAAI,cAAc,GAAG,SAAS,CAM5D"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/core/context.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAQ9C,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,cAAc,EACvB,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAEH;AAKD,wBAAgB,eAAe,IAAI,cAAc,CAchD;AAKD,wBAAgB,UAAU,IAAI,OAAO,CAUpC"}
@@ -6,11 +6,16 @@ function runWithContext(context, fn) {
6
6
  return als.run(context, fn);
7
7
  }
8
8
  function getContextStore() {
9
- const customGetter = getConfig().getContextStore;
10
- if (customGetter) {
11
- return customGetter();
9
+ const config = getConfig();
10
+ const getStore = config.getContextStore;
11
+ const context = config.context || als;
12
+ const store = getStore
13
+ ? getStore()
14
+ : context.getStore();
15
+ if (!store) {
16
+ return undefined;
12
17
  }
13
- return als.getStore();
18
+ return store;
14
19
  }
15
20
 
16
21
  export { getContextStore, runWithContext };
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sources":["../../src/core/context.ts"],"sourcesContent":["// ============================================================================\n// Session Context (AsyncLocalStorage)\n// ============================================================================\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { SessionContext } from \"./types\";\nimport { getConfig } from \"./config\";\n\nconst als = new AsyncLocalStorage<SessionContext>();\n\n/**\n * Run a function with session context available\n */\nexport function runWithContext<T>(\n context: SessionContext,\n fn: () => T\n): T {\n return als.run(context, fn);\n}\n\n/**\n * Get the current session context (only available inside middleware chain)\n */\nexport function getContextStore(): SessionContext | undefined {\n const customGetter = getConfig().getContextStore;\n if (customGetter) {\n return customGetter();\n }\n return als.getStore();\n}\n"],"names":[],"mappings":";;;AAQA,MAAM,GAAG,GAAG,IAAI,iBAAiB,EAAkB;AAK7C,SAAU,cAAc,CAC5B,OAAuB,EACvB,EAAW,EAAA;IAEX,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;AAC7B;SAKgB,eAAe,GAAA;AAC7B,IAAA,MAAM,YAAY,GAAG,SAAS,EAAE,CAAC,eAAe;IAChD,IAAI,YAAY,EAAE;QAChB,OAAO,YAAY,EAAE;IACvB;AACA,IAAA,OAAO,GAAG,CAAC,QAAQ,EAAE;AACvB;;;;"}
1
+ {"version":3,"file":"context.js","sources":["../../src/core/context.ts"],"sourcesContent":["// ============================================================================\n// Session Context (AsyncLocalStorage)\n// ============================================================================\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { SessionContext } from \"./types\";\nimport { getConfig } from \"./config\";\n\nconst als = new AsyncLocalStorage<SessionContext>();\n\n/**\n * Run a function with session context available\n */\nexport function runWithContext<T>(\n context: SessionContext,\n fn: () => T\n): T {\n return als.run(context, fn);\n}\n\n/**\n * Get current Astro context (from middleware binding or explicit)\n */\nexport function getContextStore(): SessionContext {\n const config = getConfig();\n const getStore = config.getContextStore;\n const context = (config as any).context || als;\n\n const store = getStore\n ? getStore()\n : (context as AsyncLocalStorage<SessionContext>).getStore();\n\n if (!store) {\n return undefined as any;\n }\n\n return store;\n}\n\n/**\n * Check if context is available\n */\nexport function hasContext(): boolean {\n const config = getConfig();\n const getStore = config.getContextStore;\n const context = (config as any).context || als;\n\n const store = getStore\n ? getStore()\n : (context as AsyncLocalStorage<SessionContext>).getStore();\n\n return !!store;\n}"],"names":[],"mappings":";;;AAQA,MAAM,GAAG,GAAG,IAAI,iBAAiB,EAAkB;AAK7C,SAAU,cAAc,CAC5B,OAAuB,EACvB,EAAW,EAAA;IAEX,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;AAC7B;SAKgB,eAAe,GAAA;AAC7B,IAAA,MAAM,MAAM,GAAG,SAAS,EAAE;AAC1B,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,eAAe;AACvC,IAAA,MAAM,OAAO,GAAI,MAAc,CAAC,OAAO,IAAI,GAAG;IAE9C,MAAM,KAAK,GAAG;UACR,QAAQ;AACV,UAAG,OAA6C,CAAC,QAAQ,EAAE;IAE/D,IAAI,CAAC,KAAK,EAAE;AACV,QAAA,OAAO,SAAgB;IACzB;AAEA,IAAA,OAAO,KAAK;AACd;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"guardMiddleware.d.ts","sourceRoot":"","sources":["../../src/core/guardMiddleware.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAa,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAsEzD,wBAAgB,qBAAqB,IAAI,iBAAiB,CAoEzD"}
1
+ {"version":3,"file":"guardMiddleware.d.ts","sourceRoot":"","sources":["../../src/core/guardMiddleware.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAsE7C,wBAAgB,qBAAqB,IAAI,iBAAiB,CAgGzD"}
@@ -47,7 +47,6 @@ async function checkRule(rule, session) {
47
47
  }
48
48
  function createGuardMiddleware() {
49
49
  return async (context, next) => {
50
- const { protect, loginPath, globalProtect, exclude } = getConfig();
51
50
  let pathname;
52
51
  try {
53
52
  pathname = new URL(context.request.url).pathname;
@@ -55,42 +54,69 @@ function createGuardMiddleware() {
55
54
  catch {
56
55
  pathname = "/";
57
56
  }
58
- debug(`[Guard] Pathname: ${pathname}, GlobalProtect: ${globalProtect}, Rules: ${protect.length}`);
57
+ const config = getConfig();
58
+ const { protect, loginPath, globalProtect, exclude, debug: debug$1 } = config;
59
+ if (debug$1) {
60
+ debug(`[Guard] Pathname: ${pathname}, GlobalProtect: ${globalProtect}, Rules: ${protect.length}`);
61
+ }
59
62
  if (protect.length === 0 && !globalProtect) {
60
- debug(`[Guard] Skipping ${pathname} because no rules are configured and globalProtect is false`);
63
+ if (debug$1) {
64
+ debug(`[Guard] Skipping ${pathname} because no rules are configured and globalProtect is false`);
65
+ }
61
66
  return next();
62
67
  }
63
68
  const sessionContext = getContextStore();
64
69
  const session = sessionContext?.session ?? null;
70
+ if (debug$1) {
71
+ debug(`[Guard] Session retrieved from store: ${session ? 'exists' : 'null'}`);
72
+ }
65
73
  const rule = protect.find((r) => matchesPattern(r.pattern, pathname));
66
- if (rule) {
74
+ if (rule && debug$1) {
67
75
  debug(`[Guard] Found matching rule for ${pathname}:`, rule);
68
76
  }
69
77
  if (!rule) {
70
78
  if (globalProtect) {
71
79
  if (exclude.some((pattern) => matchesPattern(pattern, pathname))) {
72
- debug(`[GlobalProtect] Skipping ${pathname} because it matches an exclude pattern`);
80
+ if (debug$1) {
81
+ debug(`[GlobalProtect] Skipping ${pathname} because it matches an exclude pattern`);
82
+ }
73
83
  return next();
74
84
  }
75
85
  if (pathname === loginPath) {
76
- debug(`[GlobalProtect] Skipping ${pathname} because it is the loginPath`);
86
+ if (session && isValidSessionStructure(session)) {
87
+ if (debug$1) {
88
+ debug(`[GlobalProtect] Redirecting ${pathname} to / because session is already present`);
89
+ }
90
+ return context.redirect('/');
91
+ }
92
+ if (debug$1) {
93
+ debug(`[GlobalProtect] Skipping ${pathname} because it is the loginPath`);
94
+ }
77
95
  return next();
78
96
  }
79
97
  if (!session || !isValidSessionStructure(session)) {
80
- debug(`[GlobalProtect] Redirecting to ${loginPath} because session is ${session ? 'invalid' : 'missing'}`);
98
+ if (debug$1) {
99
+ debug(`[GlobalProtect] Redirecting to ${loginPath} because session is ${session ? 'invalid' : 'missing'}`);
100
+ }
81
101
  return context.redirect(loginPath);
82
102
  }
83
103
  }
84
- debug(`[GlobalProtect] Allowing ${pathname} because session is valid or globalProtect is false`);
104
+ if (debug$1) {
105
+ debug(`[GlobalProtect] Allowing ${pathname} because session is valid or globalProtect is false`);
106
+ }
85
107
  return next();
86
108
  }
87
109
  const allowed = await checkRule(rule, session);
88
110
  if (!allowed) {
89
111
  const redirectTo = rule.redirectTo ?? loginPath;
90
- debug(`[Guard] Redirecting to ${redirectTo} because access was denied by rule:`, rule);
112
+ if (debug$1) {
113
+ debug(`[Guard] Redirecting to ${redirectTo} because access was denied by rule:`, rule);
114
+ }
91
115
  return context.redirect(redirectTo);
92
116
  }
93
- debug(`[Guard] Allowing ${pathname} because access was granted by rule:`, rule);
117
+ if (debug$1) {
118
+ debug(`[Guard] Allowing ${pathname} because access was granted by rule:`, rule);
119
+ }
94
120
  return next();
95
121
  };
96
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"guardMiddleware.js","sources":["../../src/core/guardMiddleware.ts"],"sourcesContent":["// ============================================================================\n// Route Guard Middleware - Enforces protection rules\n// ============================================================================\n\nimport type {APIContext, MiddlewareHandler} from \"astro\";\nimport { getContextStore } from \"./context\";\nimport { getConfig } from \"./config\";\nimport { matchesPattern } from \"./matcher\";\nimport type { ProtectionRule, Session } from \"./types\";\nimport { isValidSessionStructure } from \"./validation\";\nimport * as logger from \"./logger\";\n\n/**\n * Check if session satisfies a protection rule\n */\nasync function checkRule(rule: ProtectionRule, session: Session | null): Promise<boolean> {\n const { access } = getConfig();\n\n // Custom check overrides everything\n if (access.check) {\n try {\n return await access.check(rule, session);\n } catch (error) {\n logger.error('Error in custom access check hook:', error);\n return false;\n }\n }\n\n // Custom allow function\n if (\"allow\" in rule) {\n try {\n return await rule.allow(session);\n } catch (error) {\n logger.error('Error in custom rule allow function:', error);\n return false;\n }\n }\n\n // Must be authenticated and have a valid session structure for all other checks\n if (!session || !isValidSessionStructure(session)) {\n return false;\n }\n\n // Single role check\n if (\"role\" in rule) {\n const userRole = access.getRole(session);\n return userRole === rule.role;\n }\n\n // Multiple roles check (user must have ONE of these)\n if (\"roles\" in rule) {\n const userRole = access.getRole(session);\n return userRole !== null && rule.roles.includes(userRole);\n }\n\n // Single permission check\n if (\"permission\" in rule) {\n const userPermissions = access.getPermissions(session);\n return userPermissions.includes(rule.permission);\n }\n\n // Multiple permissions check (user must have ALL of these)\n if (\"permissions\" in rule) {\n const userPermissions = access.getPermissions(session);\n return rule.permissions.every((p) => userPermissions.includes(p));\n }\n\n // No specific rule matched - allow by default\n return true;\n}\n\n/**\n * Create route guard middleware\n */\nexport function createGuardMiddleware(): MiddlewareHandler {\n return async (context : APIContext, next) => {\n const { protect, loginPath, globalProtect, exclude } = getConfig();\n \n let pathname: string;\n try {\n pathname = new URL(context.request.url).pathname;\n } catch {\n // Fallback if URL is invalid (unlikely in Astro)\n pathname = \"/\";\n }\n\n logger.debug(`[Guard] Pathname: ${pathname}, GlobalProtect: ${globalProtect}, Rules: ${protect.length}`);\n\n // No rules configured and no global protect - skip\n if (protect.length === 0 && !globalProtect) {\n logger.debug(`[Guard] Skipping ${pathname} because no rules are configured and globalProtect is false`);\n return next();\n }\n\n const sessionContext = getContextStore();\n const session = sessionContext?.session ?? null;\n\n // Find matching rule\n const rule = protect.find((r) => matchesPattern(r.pattern, pathname));\n \n if (rule) {\n logger.debug(`[Guard] Found matching rule for ${pathname}:`, rule);\n }\n\n // No matching rule - check global protection\n if (!rule) {\n if (globalProtect) {\n // Skip if path is in exclude list\n if (exclude.some((pattern) => matchesPattern(pattern, pathname))) {\n logger.debug(`[GlobalProtect] Skipping ${pathname} because it matches an exclude pattern`);\n return next();\n }\n \n // Skip if it's the login page itself (to avoid redirect loops)\n if (pathname === loginPath) {\n logger.debug(`[GlobalProtect] Skipping ${pathname} because it is the loginPath`);\n return next();\n }\n\n // Require valid session\n if (!session || !isValidSessionStructure(session)) {\n logger.debug(`[GlobalProtect] Redirecting to ${loginPath} because session is ${session ? 'invalid' : 'missing'}`);\n return context.redirect(loginPath);\n }\n }\n \n logger.debug(`[GlobalProtect] Allowing ${pathname} because session is valid or globalProtect is false`);\n return next();\n }\n\n // Check if access is allowed\n const allowed = await checkRule(rule, session);\n\n if (!allowed) {\n const redirectTo = rule.redirectTo ?? loginPath;\n logger.debug(`[Guard] Redirecting to ${redirectTo} because access was denied by rule:`, rule);\n return context.redirect(redirectTo);\n }\n\n logger.debug(`[Guard] Allowing ${pathname} because access was granted by rule:`, rule);\n return next();\n };\n}\n"],"names":["error","logger.error","logger.debug"],"mappings":"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"}
1
+ {"version":3,"file":"guardMiddleware.js","sources":["../../src/core/guardMiddleware.ts"],"sourcesContent":["// ============================================================================\n// Route Guard Middleware - Enforces protection rules\n// ============================================================================\n\nimport type {MiddlewareHandler} from \"astro\";\nimport { getContextStore } from \"./context\";\nimport { getConfig } from \"./config\";\nimport { matchesPattern } from \"./matcher\";\nimport type { ProtectionRule, Session } from \"./types\";\nimport { isValidSessionStructure } from \"./validation\";\nimport * as logger from \"./logger\";\n\n/**\n * Check if session satisfies a protection rule\n */\nasync function checkRule(rule: ProtectionRule, session: Session | null): Promise<boolean> {\n const { access } = getConfig();\n\n // Custom check overrides everything\n if (access.check) {\n try {\n return await access.check(rule, session);\n } catch (error) {\n logger.error('Error in custom access check hook:', error);\n return false;\n }\n }\n\n // Custom allow function\n if (\"allow\" in rule) {\n try {\n return await rule.allow(session);\n } catch (error) {\n logger.error('Error in custom rule allow function:', error);\n return false;\n }\n }\n\n // Must be authenticated and have a valid session structure for all other checks\n if (!session || !isValidSessionStructure(session)) {\n return false;\n }\n\n // Single role check\n if (\"role\" in rule) {\n const userRole = access.getRole(session);\n return userRole === rule.role;\n }\n\n // Multiple roles check (user must have ONE of these)\n if (\"roles\" in rule) {\n const userRole = access.getRole(session);\n return userRole !== null && rule.roles.includes(userRole);\n }\n\n // Single permission check\n if (\"permission\" in rule) {\n const userPermissions = access.getPermissions(session);\n return userPermissions.includes(rule.permission);\n }\n\n // Multiple permissions check (user must have ALL of these)\n if (\"permissions\" in rule) {\n const userPermissions = access.getPermissions(session);\n return rule.permissions.every((p) => userPermissions.includes(p));\n }\n\n // No specific rule matched - allow by default\n return true;\n}\n\n/**\n * Create route guard middleware\n */\nexport function createGuardMiddleware(): MiddlewareHandler {\n return async (context, next) => {\n let pathname: string;\n try {\n pathname = new URL(context.request.url).pathname;\n } catch {\n pathname = \"/\";\n }\n\n const config = getConfig();\n const {protect, loginPath, globalProtect, exclude, debug} = config;\n\n if (debug) {\n logger.debug(`[Guard] Pathname: ${pathname}, GlobalProtect: ${globalProtect}, Rules: ${protect.length}`);\n }\n\n // No rules configured and no global protect - skip\n if (protect.length === 0 && !globalProtect) {\n if (debug) {\n logger.debug(`[Guard] Skipping ${pathname} because no rules are configured and globalProtect is false`);\n }\n return next();\n }\n\n const sessionContext = getContextStore();\n const session = sessionContext?.session ?? null;\n\n if (debug) {\n logger.debug(`[Guard] Session retrieved from store: ${session ? 'exists' : 'null'}`);\n }\n\n // Find matching rule\n const rule = protect.find((r) => matchesPattern(r.pattern, pathname));\n\n if (rule && debug) {\n logger.debug(`[Guard] Found matching rule for ${pathname}:`, rule);\n }\n\n // No matching rule - check global protection\n if (!rule) {\n if (globalProtect) {\n // Skip if path is in exclude list\n if (exclude.some((pattern) => matchesPattern(pattern, pathname))) {\n if (debug) {\n logger.debug(`[GlobalProtect] Skipping ${pathname} because it matches an exclude pattern`);\n }\n return next();\n }\n\n // Skip if it's the login page itself (to avoid redirect loops)\n if (pathname === loginPath) {\n // NEW: If session is already present, redirect to home (/)\n if (session && isValidSessionStructure(session)) {\n if (debug) {\n logger.debug(`[GlobalProtect] Redirecting ${pathname} to / because session is already present`);\n }\n return context.redirect('/');\n }\n\n if (debug) {\n logger.debug(`[GlobalProtect] Skipping ${pathname} because it is the loginPath`);\n }\n return next();\n }\n\n // Require valid session\n if (!session || !isValidSessionStructure(session)) {\n if (debug) {\n logger.debug(`[GlobalProtect] Redirecting to ${loginPath} because session is ${session ? 'invalid' : 'missing'}`);\n }\n return context.redirect(loginPath);\n }\n }\n\n if (debug) {\n logger.debug(`[GlobalProtect] Allowing ${pathname} because session is valid or globalProtect is false`);\n }\n return next();\n }\n\n // Check if access is allowed\n const allowed = await checkRule(rule, session);\n\n if (!allowed) {\n const redirectTo = rule.redirectTo ?? loginPath;\n if (debug) {\n logger.debug(`[Guard] Redirecting to ${redirectTo} because access was denied by rule:`, rule);\n }\n return context.redirect(redirectTo);\n }\n\n if (debug) {\n logger.debug(`[Guard] Allowing ${pathname} because access was granted by rule:`, rule);\n }\n return next();\n };\n}\n"],"names":["error","logger.error","debug","logger.debug"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../../src/core/matcher.ts"],"names":[],"mappings":"AA0DA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAErE"}
1
+ {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../../src/core/matcher.ts"],"names":[],"mappings":"AAiEA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAErE"}
@@ -1,7 +1,11 @@
1
1
  function escapeRegex(str) {
2
2
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
3
  }
4
+ const regexCache = new Map();
4
5
  function globToRegex(pattern) {
6
+ const cached = regexCache.get(pattern);
7
+ if (cached)
8
+ return cached;
5
9
  let regex = "";
6
10
  let i = 0;
7
11
  while (i < pattern.length) {
@@ -40,7 +44,9 @@ function globToRegex(pattern) {
40
44
  regex += escapeRegex(char);
41
45
  i += 1;
42
46
  }
43
- return new RegExp(`^${regex}$`);
47
+ const result = new RegExp(`^${regex}$`);
48
+ regexCache.set(pattern, result);
49
+ return result;
44
50
  }
45
51
  function matchesPattern(pattern, path) {
46
52
  return globToRegex(pattern).test(path);
@@ -1 +1 @@
1
- {"version":3,"file":"matcher.js","sources":["../../src/core/matcher.ts"],"sourcesContent":["// ============================================================================\n// Route Pattern Matching\n// ============================================================================\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let regex = \"\";\n let i = 0;\n\n while (i < pattern.length) {\n const char = pattern[i];\n const next = pattern[i + 1];\n\n // Handle **\n if (char === \"*\" && next === \"*\") {\n const isAtEnd = i + 2 === pattern.length;\n const prevIsSlash = i > 0 && pattern[i - 1] === \"/\";\n\n if (prevIsSlash) {\n // Handle \"/**\"\n if (isAtEnd) {\n // \"/**\" at end matches everything from that point\n if (regex.endsWith(\"/\")) regex = regex.slice(0, -1);\n regex += \"(?:/.*)?\";\n } else if (pattern[i + 2] === \"/\") {\n // \"/**/\" matches zero or more segments\n if (regex.endsWith(\"/\")) regex = regex.slice(0, -1);\n regex += \"(?:/.*)?\";\n i += 1; // skip one extra for the trailing slash\n } else {\n regex += \".*\";\n }\n } else {\n regex += \".*\";\n }\n\n i += 2;\n continue;\n }\n\n // Handle *\n if (char === \"*\") {\n // one or more segments (to maintain backward compatibility with previous tests)\n regex += \"[^/]+(?:/[^/]+)*\";\n i += 1;\n continue;\n }\n\n regex += escapeRegex(char as string);\n i += 1;\n }\n\n return new RegExp(`^${regex}$`);\n}\n\nexport function matchesPattern(pattern: string, path: string): boolean {\n return globToRegex(pattern).test(path);\n}\n"],"names":[],"mappings":"AAIA,SAAS,WAAW,CAAC,GAAW,EAAA;IAC9B,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC;AACnD;AAEA,SAAS,WAAW,CAAC,OAAe,EAAA;IAClC,IAAI,KAAK,GAAG,EAAE;IACd,IAAI,CAAC,GAAG,CAAC;AAET,IAAA,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE;AACzB,QAAA,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC;QACvB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;QAG3B,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE;YAChC,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM;AACxC,YAAA,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG;YAEnD,IAAI,WAAW,EAAE;gBAEf,IAAI,OAAO,EAAE;AAEX,oBAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;wBAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;oBACnD,KAAK,IAAI,UAAU;gBACrB;qBAAO,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE;AAEjC,oBAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;wBAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;oBACnD,KAAK,IAAI,UAAU;oBACnB,CAAC,IAAI,CAAC;gBACR;qBAAO;oBACL,KAAK,IAAI,IAAI;gBACf;YACF;iBAAO;gBACL,KAAK,IAAI,IAAI;YACf;YAEA,CAAC,IAAI,CAAC;YACN;QACF;AAGA,QAAA,IAAI,IAAI,KAAK,GAAG,EAAE;YAEhB,KAAK,IAAI,kBAAkB;YAC3B,CAAC,IAAI,CAAC;YACN;QACF;AAEA,QAAA,KAAK,IAAI,WAAW,CAAC,IAAc,CAAC;QACpC,CAAC,IAAI,CAAC;IACR;AAEA,IAAA,OAAO,IAAI,MAAM,CAAC,IAAI,KAAK,CAAA,CAAA,CAAG,CAAC;AACjC;AAEM,SAAU,cAAc,CAAC,OAAe,EAAE,IAAY,EAAA;IAC1D,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;AACxC;;;;"}
1
+ {"version":3,"file":"matcher.js","sources":["../../src/core/matcher.ts"],"sourcesContent":["// ============================================================================\n// Route Pattern Matching\n// ============================================================================\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nconst regexCache = new Map<string, RegExp>();\n\nfunction globToRegex(pattern: string): RegExp {\n const cached = regexCache.get(pattern);\n if (cached) return cached;\n\n let regex = \"\";\n let i = 0;\n\n while (i < pattern.length) {\n const char = pattern[i];\n const next = pattern[i + 1];\n\n // Handle **\n if (char === \"*\" && next === \"*\") {\n const isAtEnd = i + 2 === pattern.length;\n const prevIsSlash = i > 0 && pattern[i - 1] === \"/\";\n\n if (prevIsSlash) {\n // Handle \"/**\"\n if (isAtEnd) {\n // \"/**\" at end matches everything from that point\n if (regex.endsWith(\"/\")) regex = regex.slice(0, -1);\n regex += \"(?:/.*)?\";\n } else if (pattern[i + 2] === \"/\") {\n // \"/**/\" matches zero or more segments\n if (regex.endsWith(\"/\")) regex = regex.slice(0, -1);\n regex += \"(?:/.*)?\";\n i += 1; // skip one extra for the trailing slash\n } else {\n regex += \".*\";\n }\n } else {\n regex += \".*\";\n }\n\n i += 2;\n continue;\n }\n\n // Handle *\n if (char === \"*\") {\n // one or more segments (to maintain backward compatibility with previous tests)\n regex += \"[^/]+(?:/[^/]+)*\";\n i += 1;\n continue;\n }\n\n regex += escapeRegex(char as string);\n i += 1;\n }\n\n const result = new RegExp(`^${regex}$`);\n regexCache.set(pattern, result);\n return result;\n}\n\nexport function matchesPattern(pattern: string, path: string): boolean {\n return globToRegex(pattern).test(path);\n}\n"],"names":[],"mappings":"AAIA,SAAS,WAAW,CAAC,GAAW,EAAA;IAC9B,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC;AACnD;AAEA,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB;AAE5C,SAAS,WAAW,CAAC,OAAe,EAAA;IAClC,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC;AACtC,IAAA,IAAI,MAAM;AAAE,QAAA,OAAO,MAAM;IAEzB,IAAI,KAAK,GAAG,EAAE;IACd,IAAI,CAAC,GAAG,CAAC;AAET,IAAA,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE;AACzB,QAAA,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC;QACvB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;QAG3B,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE;YAChC,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM;AACxC,YAAA,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG;YAEnD,IAAI,WAAW,EAAE;gBAEf,IAAI,OAAO,EAAE;AAEX,oBAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;wBAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;oBACnD,KAAK,IAAI,UAAU;gBACrB;qBAAO,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE;AAEjC,oBAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;wBAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;oBACnD,KAAK,IAAI,UAAU;oBACnB,CAAC,IAAI,CAAC;gBACR;qBAAO;oBACL,KAAK,IAAI,IAAI;gBACf;YACF;iBAAO;gBACL,KAAK,IAAI,IAAI;YACf;YAEA,CAAC,IAAI,CAAC;YACN;QACF;AAGA,QAAA,IAAI,IAAI,KAAK,GAAG,EAAE;YAEhB,KAAK,IAAI,kBAAkB;YAC3B,CAAC,IAAI,CAAC;YACN;QACF;AAEA,QAAA,KAAK,IAAI,WAAW,CAAC,IAAc,CAAC;QACpC,CAAC,IAAI,CAAC;IACR;IAEA,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,CAAG,CAAC;AACvC,IAAA,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC;AAC/B,IAAA,OAAO,MAAM;AACf;AAEM,SAAU,cAAc,CAAC,OAAe,EAAE,IAAY,EAAA;IAC1D,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;AACxC;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"sessionMiddleware.d.ts","sourceRoot":"","sources":["../../src/core/sessionMiddleware.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAkB7C,eAAO,MAAM,iBAAiB,EAAE,iBA0C/B,CAAC"}
1
+ {"version":3,"file":"sessionMiddleware.d.ts","sourceRoot":"","sources":["../../src/core/sessionMiddleware.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAuB7C,eAAO,MAAM,iBAAiB,EAAE,iBAwE/B,CAAC"}
@@ -1,11 +1,14 @@
1
1
  import { runWithContext } from './context.js';
2
2
  import { isValidSessionStructure } from './validation.js';
3
3
  import { getConfig } from './config.js';
4
- import { warn, error } from './logger.js';
4
+ import { warn, debug, error } from './logger.js';
5
5
 
6
6
  const SESSION_KEY = "__session__";
7
+ const LOGGED_KEY = Symbol.for('astro-sessionkit.middleware.logged');
7
8
  const sessionMiddleware = async (context, next) => {
8
- const rawSession = context.session?.get(SESSION_KEY) ?? null;
9
+ const config = getConfig();
10
+ const { runWithContext: runWithContext$1, getContextStore, setContextStore, context: externalContext, debug: debug$1 } = config;
11
+ const rawSession = await context.session?.get(SESSION_KEY) ?? null;
9
12
  let session = null;
10
13
  if (rawSession) {
11
14
  if (isValidSessionStructure(rawSession)) {
@@ -18,22 +21,47 @@ const sessionMiddleware = async (context, next) => {
18
21
  session = null;
19
22
  }
20
23
  }
21
- const config = getConfig();
22
- if (config.getContextStore && !config.runWithContext) {
23
- const store = config.getContextStore();
24
+ const globalStorage = globalThis;
25
+ if (!globalStorage[LOGGED_KEY]) {
26
+ let contextStrategy = 'default';
27
+ if (runWithContext$1) {
28
+ contextStrategy = 'custom (runWithContext)';
29
+ }
30
+ else if (getContextStore) {
31
+ contextStrategy = 'custom (getter/setter)';
32
+ }
33
+ else if (externalContext) {
34
+ contextStrategy = 'custom (external AsyncLocalStorage)';
35
+ }
36
+ debug(`[SessionKit] Middleware initialized (context: ${contextStrategy})`);
37
+ globalStorage[LOGGED_KEY] = true;
38
+ }
39
+ const runLogic = () => next();
40
+ const sessionContext = { session, astroContext: context };
41
+ if (getContextStore && !runWithContext$1) {
42
+ const store = getContextStore();
43
+ if (debug$1) {
44
+ debug('[SessionMiddleware] Custom getContextStore returned:', !!store);
45
+ }
24
46
  if (store) {
25
47
  store.session = session;
26
48
  }
27
- else if (config.setContextStore) {
28
- config.setContextStore({ session });
49
+ else if (setContextStore) {
50
+ if (debug$1) {
51
+ debug('[SessionMiddleware] Calling custom setContextStore');
52
+ }
53
+ setContextStore(sessionContext);
29
54
  }
30
55
  else {
31
56
  error('getContextStore returned undefined, cannot set session');
32
57
  }
33
- return next();
58
+ return runLogic();
59
+ }
60
+ if (debug$1) {
61
+ debug('[SessionMiddleware] Using' + (runWithContext$1 ? ' custom ' : ' default ') + 'runner');
34
62
  }
35
- const runner = config.runWithContext ?? runWithContext;
36
- return runner({ session }, () => next());
63
+ const runner = runWithContext$1 ?? runWithContext;
64
+ return runner(sessionContext, runLogic);
37
65
  };
38
66
 
39
67
  export { sessionMiddleware };
@@ -1 +1 @@
1
- {"version":3,"file":"sessionMiddleware.js","sources":["../../src/core/sessionMiddleware.ts"],"sourcesContent":["// ============================================================================\n// Session Middleware - Loads session into AsyncLocalStorage\n// ============================================================================\n\nimport type {MiddlewareHandler} from \"astro\";\nimport {runWithContext as defaultRunWithContext} from \"./context\";\nimport {isValidSessionStructure} from \"./validation\";\nimport type {Session} from \"./types\";\nimport {getConfig} from \"./config\";\nimport * as logger from \"./logger\";\n\n/**\n * Session key used to store session in context.session\n */\nconst SESSION_KEY = \"__session__\";\n\n/**\n * Main session middleware\n *\n * Reads session from context.session.get('__session__') and makes it available\n * throughout the request via AsyncLocalStorage\n */\nexport const sessionMiddleware: MiddlewareHandler = async (context, next) => {\n // Get session from context.session store\n const rawSession = context.session?.get<Session>(SESSION_KEY) ?? null;\n\n // Validate session structure if present\n let session: Session | null = null;\n\n if (rawSession) {\n if (isValidSessionStructure(rawSession)) {\n session = rawSession;\n } else {\n // Invalid session structure - log warning and treat as unauthenticated\n logger.warn(\n 'Invalid session structure detected. Session will be ignored. ' +\n 'Ensure context.session.set(\"__session__\", ...) has the correct structure. ' +\n 'Received: ' + JSON.stringify(rawSession)\n );\n session = null;\n }\n }\n\n // Run the rest of the request chain with session context\n const config = getConfig();\n\n // If getContextStore is provided, but runWithContext is NOT,\n // we assume the user is managing the context at a superior level\n // and we should NOT wrap the call in our default runner.\n if (config.getContextStore && !config.runWithContext) {\n // Initialize context store if setter is provided\n const store = config.getContextStore();\n if (store) {\n store.session = session;\n } else if (config.setContextStore) {\n config.setContextStore({session});\n } else {\n logger.error('getContextStore returned undefined, cannot set session');\n }\n return next();\n }\n\n const runner = config.runWithContext ?? defaultRunWithContext;\n return runner({session}, () => next());\n};"],"names":["logger.warn","logger.error","defaultRunWithContext"],"mappings":";;;;;AAcA,MAAM,WAAW,GAAG,aAAa;AAQ1B,MAAM,iBAAiB,GAAsB,OAAO,OAAO,EAAE,IAAI,KAAI;AAExE,IAAA,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,CAAU,WAAW,CAAC,IAAI,IAAI;IAGrE,IAAI,OAAO,GAAmB,IAAI;IAElC,IAAI,UAAU,EAAE;AACZ,QAAA,IAAI,uBAAuB,CAAC,UAAU,CAAC,EAAE;YACrC,OAAO,GAAG,UAAU;QACxB;aAAO;YAEHA,IAAW,CACP,+DAA+D;gBAC/D,4EAA4E;gBAC5E,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAC5C;YACD,OAAO,GAAG,IAAI;QAClB;IACJ;AAGA,IAAA,MAAM,MAAM,GAAG,SAAS,EAAE;IAK1B,IAAI,MAAM,CAAC,eAAe,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE;AAElD,QAAA,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,EAAE;QACtC,IAAI,KAAK,EAAE;AACP,YAAA,KAAK,CAAC,OAAO,GAAG,OAAO;QAC3B;AAAO,aAAA,IAAI,MAAM,CAAC,eAAe,EAAE;AAC/B,YAAA,MAAM,CAAC,eAAe,CAAC,EAAC,OAAO,EAAC,CAAC;QACrC;aAAO;AACH,YAAAC,KAAY,CAAC,wDAAwD,CAAC;QAC1E;QACA,OAAO,IAAI,EAAE;IACjB;AAEA,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,IAAIC,cAAqB;AAC7D,IAAA,OAAO,MAAM,CAAC,EAAC,OAAO,EAAC,EAAE,MAAM,IAAI,EAAE,CAAC;AAC1C;;;;"}
1
+ {"version":3,"file":"sessionMiddleware.js","sources":["../../src/core/sessionMiddleware.ts"],"sourcesContent":["// ============================================================================\n// Session Middleware - Loads session into AsyncLocalStorage\n// ============================================================================\n\nimport type {MiddlewareHandler} from \"astro\";\nimport {runWithContext as defaultRunWithContext} from \"./context\";\nimport {isValidSessionStructure} from \"./validation\";\nimport type {Session, SessionContext, SessionKitContext} from \"./types\";\nimport {getConfig} from \"./config\";\nimport * as logger from \"./logger\";\n\n/**\n * Session key used to store session in context.session\n */\nconst SESSION_KEY = \"__session__\";\n\n/**\n * Redundant logging prevention key\n */\nconst LOGGED_KEY = Symbol.for('astro-sessionkit.middleware.logged');\n\n/**\n * Main session middleware\n *\n * Reads session from context.session.get('__session__') and makes it available\n * throughout the request via AsyncLocalStorage\n */\nexport const sessionMiddleware: MiddlewareHandler = async (context, next) => {\n const config = getConfig();\n const {runWithContext, getContextStore, setContextStore, context: externalContext, debug} = config;\n\n // Get session from context.session store\n const rawSession = await context.session?.get<Session>(SESSION_KEY) ?? null;\n\n // Validate session structure if present\n let session: Session | null = null;\n\n if (rawSession) {\n if (isValidSessionStructure(rawSession)) {\n session = rawSession;\n } else {\n // Invalid session structure - log warning and treat as unauthenticated\n logger.warn(\n 'Invalid session structure detected. Session will be ignored. ' +\n 'Ensure context.session.set(\"__session__\", ...) has the correct structure. ' +\n 'Received: ' + JSON.stringify(rawSession)\n );\n session = null;\n }\n }\n\n // Run the rest of the request chain with session context\n const globalStorage = globalThis as any;\n if (!globalStorage[LOGGED_KEY]) {\n let contextStrategy = 'default';\n\n if (runWithContext) {\n contextStrategy = 'custom (runWithContext)';\n } else if (getContextStore) {\n contextStrategy = 'custom (getter/setter)';\n } else if (externalContext) {\n contextStrategy = 'custom (external AsyncLocalStorage)';\n }\n\n logger.debug(`[SessionKit] Middleware initialized (context: ${contextStrategy})`);\n globalStorage[LOGGED_KEY] = true;\n }\n\n const runLogic = () => next();\n const sessionContext: SessionContext = { session, astroContext: context as SessionKitContext };\n\n // If getContextStore is provided, but runWithContext is NOT,\n // we assume the user is managing the context at a superior level\n // and we should NOT wrap the call in our default runner.\n if (getContextStore && !runWithContext) {\n // Initialize context store if setter is provided\n const store = getContextStore();\n if (debug) {\n logger.debug('[SessionMiddleware] Custom getContextStore returned:', !!store);\n }\n if (store) {\n store.session = session;\n } else if (setContextStore) {\n if (debug) {\n logger.debug('[SessionMiddleware] Calling custom setContextStore');\n }\n setContextStore(sessionContext);\n } else {\n logger.error('getContextStore returned undefined, cannot set session');\n }\n return runLogic();\n }\n\n if (debug) {\n logger.debug('[SessionMiddleware] Using' + (runWithContext ? ' custom ' : ' default ') + 'runner');\n }\n\n const runner = runWithContext ?? defaultRunWithContext;\n return runner(sessionContext, runLogic);\n};"],"names":["runWithContext","debug","logger.warn","logger.debug","logger.error","defaultRunWithContext"],"mappings":";;;;;AAcA,MAAM,WAAW,GAAG,aAAa;AAKjC,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,oCAAoC,CAAC;AAQ5D,MAAM,iBAAiB,GAAsB,OAAO,OAAO,EAAE,IAAI,KAAI;AACxE,IAAA,MAAM,MAAM,GAAG,SAAS,EAAE;AAC1B,IAAA,MAAM,kBAACA,gBAAc,EAAE,eAAe,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,SAAEC,OAAK,EAAC,GAAG,MAAM;AAGlG,IAAA,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,GAAG,CAAU,WAAW,CAAC,IAAI,IAAI;IAG3E,IAAI,OAAO,GAAmB,IAAI;IAElC,IAAI,UAAU,EAAE;AACZ,QAAA,IAAI,uBAAuB,CAAC,UAAU,CAAC,EAAE;YACrC,OAAO,GAAG,UAAU;QACxB;aAAO;YAEHC,IAAW,CACP,+DAA+D;gBAC/D,4EAA4E;gBAC5E,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAC5C;YACD,OAAO,GAAG,IAAI;QAClB;IACJ;IAGA,MAAM,aAAa,GAAG,UAAiB;AACvC,IAAA,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE;QAC5B,IAAI,eAAe,GAAG,SAAS;QAE/B,IAAIF,gBAAc,EAAE;YAChB,eAAe,GAAG,yBAAyB;QAC/C;aAAO,IAAI,eAAe,EAAE;YACxB,eAAe,GAAG,wBAAwB;QAC9C;aAAO,IAAI,eAAe,EAAE;YACxB,eAAe,GAAG,qCAAqC;QAC3D;AAEA,QAAAG,KAAY,CAAC,iDAAiD,eAAe,CAAA,CAAA,CAAG,CAAC;AACjF,QAAA,aAAa,CAAC,UAAU,CAAC,GAAG,IAAI;IACpC;AAEA,IAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE;IAC7B,MAAM,cAAc,GAAmB,EAAE,OAAO,EAAE,YAAY,EAAE,OAA4B,EAAE;AAK9F,IAAA,IAAI,eAAe,IAAI,CAACH,gBAAc,EAAE;AAEpC,QAAA,MAAM,KAAK,GAAG,eAAe,EAAE;QAC/B,IAAIC,OAAK,EAAE;YACPE,KAAY,CAAC,sDAAsD,EAAE,CAAC,CAAC,KAAK,CAAC;QACjF;QACA,IAAI,KAAK,EAAE;AACP,YAAA,KAAK,CAAC,OAAO,GAAG,OAAO;QAC3B;aAAO,IAAI,eAAe,EAAE;YACxB,IAAIF,OAAK,EAAE;AACP,gBAAAE,KAAY,CAAC,oDAAoD,CAAC;YACtE;YACA,eAAe,CAAC,cAAc,CAAC;QACnC;aAAO;AACH,YAAAC,KAAY,CAAC,wDAAwD,CAAC;QAC1E;QACA,OAAO,QAAQ,EAAE;IACrB;IAEA,IAAIH,OAAK,EAAE;AACP,QAAAE,KAAY,CAAC,2BAA2B,IAAIH,gBAAc,GAAG,UAAU,GAAG,WAAW,CAAC,GAAG,QAAQ,CAAC;IACtG;AAEA,IAAA,MAAM,MAAM,GAAGA,gBAAc,IAAIK,cAAqB;AACtD,IAAA,OAAO,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC;AAC3C;;;;"}
@@ -1,3 +1,10 @@
1
+ import type { AstroCookies, AstroSession } from "astro";
2
+ export interface SessionKitContext {
3
+ cookies: AstroCookies;
4
+ session?: AstroSession;
5
+ redirect: (path: string, status?: number) => Response;
6
+ [key: string]: any;
7
+ }
1
8
  export interface Session {
2
9
  userId: string;
3
10
  email?: string;
@@ -8,6 +15,7 @@ export interface Session {
8
15
  }
9
16
  export interface SessionContext {
10
17
  session: Session | null;
18
+ astroContext?: SessionKitContext;
11
19
  }
12
20
  interface BaseProtectionRule {
13
21
  pattern: string;
@@ -43,6 +51,7 @@ export interface SessionKitConfig {
43
51
  setContextStore?: (context: SessionContext) => void;
44
52
  globalProtect?: boolean;
45
53
  exclude?: string[];
54
+ context?: any;
46
55
  debug?: boolean;
47
56
  }
48
57
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,OAAO;IAEtB,MAAM,EAAE,MAAM,CAAC;IAGf,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,IAAI,CAAC,EAAE,MAAM,CAAC;IAGd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAGjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAGvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAKD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB;AAMD,UAAU,kBAAkB;IAE1B,OAAO,EAAE,MAAM,CAAC;IAGhB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAGD,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC7D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAGD,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAClE,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACnE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAGD,MAAM,WAAW,oBAAqB,SAAQ,kBAAkB;IAC9D,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChE;AAGD,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,mBAAmB,GACnB,wBAAwB,GACxB,yBAAyB,GACzB,oBAAoB,CAAC;AASzB,MAAM,WAAW,WAAW;IAE1B,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,MAAM,GAAG,IAAI,CAAC;IAGrD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,MAAM,EAAE,CAAC;IAGvD,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvF;AAKD,MAAM,WAAW,gBAAgB;IAE/B,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAG3B,MAAM,CAAC,EAAE,WAAW,CAAC;IAMrB,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAMhE,eAAe,CAAC,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IAMnD,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAOpD,aAAa,CAAC,EAAE,OAAO,CAAC;IAKxB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAMnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,YAAY,EAAE,YAAY,EAAC,MAAM,OAAO,CAAC;AAKtD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAC;IACtD,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAMD,MAAM,WAAW,OAAO;IAEtB,MAAM,EAAE,MAAM,CAAC;IAGf,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,IAAI,CAAC,EAAE,MAAM,CAAC;IAGd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAGjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAGvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAKD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAIxB,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAClC;AAMD,UAAU,kBAAkB;IAE1B,OAAO,EAAE,MAAM,CAAC;IAGhB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAGD,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC7D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAGD,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAClE,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACnE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAGD,MAAM,WAAW,oBAAqB,SAAQ,kBAAkB;IAC9D,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChE;AAGD,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,mBAAmB,GACnB,wBAAwB,GACxB,yBAAyB,GACzB,oBAAoB,CAAC;AASzB,MAAM,WAAW,WAAW;IAE1B,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,MAAM,GAAG,IAAI,CAAC;IAGrD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,MAAM,EAAE,CAAC;IAGvD,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvF;AAKD,MAAM,WAAW,gBAAgB;IAE/B,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAG3B,MAAM,CAAC,EAAE,WAAW,CAAC;IAMrB,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAMhE,eAAe,CAAC,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IAMnD,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAOpD,aAAa,CAAC,EAAE,OAAO,CAAC;IAKxB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAMjB,OAAO,CAAC,EAAE,GAAG,CAAC;IAMd,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"","sourcesContent":["// ============================================================================\n// Core Session Types\n// ============================================================================\n\n/**\n * The session object stored in context.locals.session\n * This is what your Astro app provides - we just read it.\n */\nexport interface Session {\n /** Unique user identifier */\n userId: string;\n\n /** User's email (optional) */\n email?: string;\n\n /** Primary role */\n role?: string;\n\n /** Additional roles for multi-role scenarios */\n roles?: string[];\n\n /** Fine-grained permissions */\n permissions?: string[];\n\n /** Any additional custom data */\n [key: string]: unknown;\n}\n\n/**\n * What we store in AsyncLocalStorage\n */\nexport interface SessionContext {\n session: Session | null;\n}\n\n// ============================================================================\n// Route Protection Rules\n// ============================================================================\n\ninterface BaseProtectionRule {\n /** Glob pattern for route matching: \"/admin/**\", \"/dashboard/*\", \"/settings\" */\n pattern: string;\n\n /** Where to redirect if access denied (defaults to global loginPath) */\n redirectTo?: string;\n}\n\n/** Protect by single role */\nexport interface RoleProtectionRule extends BaseProtectionRule {\n role: string;\n}\n\n/** Protect by multiple roles (user must have ONE of these) */\nexport interface RolesProtectionRule extends BaseProtectionRule {\n roles: string[];\n}\n\n/** Protect by single permission */\nexport interface PermissionProtectionRule extends BaseProtectionRule {\n permission: string;\n}\n\n/** Protect by multiple permissions (user must have ALL of these) */\nexport interface PermissionsProtectionRule extends BaseProtectionRule {\n permissions: string[];\n}\n\n/** Protect with custom function */\nexport interface CustomProtectionRule extends BaseProtectionRule {\n allow: (session: Session | null) => boolean | Promise<boolean>;\n}\n\n/** Union of all protection rule types */\nexport type ProtectionRule =\n | RoleProtectionRule\n | RolesProtectionRule\n | PermissionProtectionRule\n | PermissionsProtectionRule\n | CustomProtectionRule;\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Optional hooks for custom role/permission extraction\n */\nexport interface AccessHooks {\n /** Extract role from session (default: session.role) */\n getRole?: (session: Session | null) => string | null;\n\n /** Extract permissions from session (default: session.permissions ?? []) */\n getPermissions?: (session: Session | null) => string[];\n\n /** Custom access check that overrides all built-in logic */\n check?: (rule: ProtectionRule, session: Session | null) => boolean | Promise<boolean>;\n}\n\n/**\n * SessionKit configuration\n */\nexport interface SessionKitConfig {\n /** Default redirect path for protected routes (default: \"/login\") */\n loginPath?: string;\n\n /** Route protection rules */\n protect?: ProtectionRule[];\n\n /** Custom access hooks */\n access?: AccessHooks;\n\n /** \n * Custom session context runner (optional)\n * Use this to provide your own AsyncLocalStorage implementation or wrap the request\n */\n runWithContext?: <T>(context: SessionContext, fn: () => T) => T;\n\n /**\n * Custom session context getter (optional)\n * Use this to provide your own way to retrieve the session context\n */\n getContextStore?: () => SessionContext | undefined;\n\n /**\n * Custom session context setter (optional)\n * Use this to provide your own way to initialize the session context\n */\n setContextStore?: (context: SessionContext) => void;\n\n /**\n * Enable global session verification for all routes.\n * If true, any route not explicitly ignored will require an active session.\n * @default false\n */\n globalProtect?: boolean;\n\n /**\n * Routes to exclude from global protection (only if globalProtect is true)\n */\n exclude?: string[];\n\n /**\n * Enable debug logging\n * @default false\n */\n debug?: boolean;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"","sourcesContent":["// ============================================================================\n// Core Session Types\n// ============================================================================\n\nimport type {AstroCookies, AstroSession} from \"astro\";\n\n/**\n * Minimal context required by SessionKit\n */\nexport interface SessionKitContext {\n cookies: AstroCookies;\n session?: AstroSession;\n redirect: (path: string, status?: number) => Response;\n [key: string]: any;\n}\n\n/**\n * The session object stored in context.locals.session\n * This is what your Astro app provides - we just read it.\n */\nexport interface Session {\n /** Unique user identifier */\n userId: string;\n\n /** User's email (optional) */\n email?: string;\n\n /** Primary role */\n role?: string;\n\n /** Additional roles for multi-role scenarios */\n roles?: string[];\n\n /** Fine-grained permissions */\n permissions?: string[];\n\n /** Any additional custom data */\n [key: string]: unknown;\n}\n\n/**\n * What we store in AsyncLocalStorage\n */\nexport interface SessionContext {\n session: Session | null;\n /**\n * Original Astro context (cookies, session, redirect, etc.)\n */\n astroContext?: SessionKitContext;\n}\n\n// ============================================================================\n// Route Protection Rules\n// ============================================================================\n\ninterface BaseProtectionRule {\n /** Glob pattern for route matching: \"/admin/**\", \"/dashboard/*\", \"/settings\" */\n pattern: string;\n\n /** Where to redirect if access denied (defaults to global loginPath) */\n redirectTo?: string;\n}\n\n/** Protect by single role */\nexport interface RoleProtectionRule extends BaseProtectionRule {\n role: string;\n}\n\n/** Protect by multiple roles (user must have ONE of these) */\nexport interface RolesProtectionRule extends BaseProtectionRule {\n roles: string[];\n}\n\n/** Protect by single permission */\nexport interface PermissionProtectionRule extends BaseProtectionRule {\n permission: string;\n}\n\n/** Protect by multiple permissions (user must have ALL of these) */\nexport interface PermissionsProtectionRule extends BaseProtectionRule {\n permissions: string[];\n}\n\n/** Protect with custom function */\nexport interface CustomProtectionRule extends BaseProtectionRule {\n allow: (session: Session | null) => boolean | Promise<boolean>;\n}\n\n/** Union of all protection rule types */\nexport type ProtectionRule =\n | RoleProtectionRule\n | RolesProtectionRule\n | PermissionProtectionRule\n | PermissionsProtectionRule\n | CustomProtectionRule;\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Optional hooks for custom role/permission extraction\n */\nexport interface AccessHooks {\n /** Extract role from session (default: session.role) */\n getRole?: (session: Session | null) => string | null;\n\n /** Extract permissions from session (default: session.permissions ?? []) */\n getPermissions?: (session: Session | null) => string[];\n\n /** Custom access check that overrides all built-in logic */\n check?: (rule: ProtectionRule, session: Session | null) => boolean | Promise<boolean>;\n}\n\n/**\n * SessionKit configuration\n */\nexport interface SessionKitConfig {\n /** Default redirect path for protected routes (default: \"/login\") */\n loginPath?: string;\n\n /** Route protection rules */\n protect?: ProtectionRule[];\n\n /** Custom access hooks */\n access?: AccessHooks;\n\n /** \n * Custom session context runner (optional)\n * Use this to provide your own AsyncLocalStorage implementation or wrap the request\n */\n runWithContext?: <T>(context: SessionContext, fn: () => T) => T;\n\n /**\n * Custom session context getter (optional)\n * Use this to provide your own way to retrieve the session context\n */\n getContextStore?: () => SessionContext | undefined;\n\n /**\n * Custom session context setter (optional)\n * Use this to provide your own way to initialize the session context\n */\n setContextStore?: (context: SessionContext) => void;\n\n /**\n * Enable global session verification for all routes.\n * If true, any route not explicitly ignored will require an active session.\n * @default false\n */\n globalProtect?: boolean;\n\n /**\n * Routes to exclude from global protection (only if globalProtect is true)\n */\n exclude?: string[];\n\n /**\n * Optional external AsyncLocalStorage instance to use for session context.\n * If provided, SessionKit will use this instead of its internal instance.\n */\n context?: any;\n\n /**\n * Enable debug logging\n * @default false\n */\n debug?: boolean;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/core/validation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAMrC,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAoExE;AAKD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAgCvD;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAgBzD"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/core/validation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAMrC,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAiFxE;AAKD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAgCvD;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAgBzD"}
@@ -6,16 +6,22 @@ function isValidSessionStructure(input) {
6
6
  if (typeof session.userId !== 'string' || !session.userId.trim()) {
7
7
  return false;
8
8
  }
9
+ if (/[\x00-\x1F\x7F]/.test(session.userId)) {
10
+ return false;
11
+ }
9
12
  if (session.userId.length > 255) {
10
13
  return false;
11
14
  }
12
- if (session.email !== undefined) {
15
+ if (session.email !== undefined && session.email !== null) {
13
16
  if (typeof session.email !== 'string') {
14
17
  return false;
15
18
  }
16
19
  if (session.email.length > 320) {
17
20
  return false;
18
21
  }
22
+ if (session.email.length > 0 && !session.email.includes('@')) {
23
+ return false;
24
+ }
19
25
  }
20
26
  if (session.role !== undefined && session.role !== null) {
21
27
  if (typeof session.role !== 'string') {
@@ -24,6 +30,9 @@ function isValidSessionStructure(input) {
24
30
  if (session.role.length > 100) {
25
31
  return false;
26
32
  }
33
+ if (/[\x00-\x1F\x7F]/.test(session.role)) {
34
+ return false;
35
+ }
27
36
  }
28
37
  if (session.roles !== undefined && session.roles !== null) {
29
38
  if (!Array.isArray(session.roles)) {
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sources":["../../src/core/validation.ts"],"sourcesContent":["// ============================================================================\n// Security Validation Utilities\n// ============================================================================\n\nimport type {Session} from \"./types\";\n\n/**\n * Validate that session has the expected structure\n * Prevents crashes from malformed data and DoS attacks\n */\nexport function isValidSessionStructure(input: unknown): input is Session {\n // Must be an object\n if (!input || typeof input !== 'object') {\n return false;\n }\n\n const session = input as any;\n\n // Required: userId must be a non-empty string\n if (typeof session.userId !== 'string' || !session.userId.trim()) {\n return false;\n }\n\n // DoS protection: Limit userId length\n if (session.userId.length > 255) {\n return false;\n }\n\n // Optional fields validation (if present)\n if (session.email !== undefined) {\n if (typeof session.email !== 'string') {\n return false;\n }\n // Reasonable email length limit\n if (session.email.length > 320) {\n return false;\n }\n }\n\n if (session.role !== undefined && session.role !== null) {\n if (typeof session.role !== 'string') {\n return false;\n }\n // Reasonable role length limit\n if (session.role.length > 100) {\n return false;\n }\n }\n\n if (session.roles !== undefined && session.roles !== null) {\n if (!Array.isArray(session.roles)) {\n return false;\n }\n // DoS protection: Limit array size\n if (session.roles.length > 100) {\n return false;\n }\n // All items must be strings with reasonable length\n if (!session.roles.every((r: any) => typeof r === 'string' && r.length <= 100)) {\n return false;\n }\n }\n\n if (session.permissions !== undefined && session.permissions !== null) {\n if (!Array.isArray(session.permissions)) {\n return false;\n }\n // DoS protection: Limit array size\n if (session.permissions.length > 500) {\n return false;\n }\n // All items must be strings with reasonable length\n if (!session.permissions.every((p: any) => typeof p === 'string' && p.length <= 200)) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Validate route patterns to prevent ReDoS attacks\n */\nexport function isValidPattern(pattern: string): boolean {\n\n if (typeof pattern !== 'string' || pattern.length === 0) return false;\n\n // Length limit\n if (pattern.length > 1000) return false;\n\n // Must start with /\n if (!pattern.startsWith(\"/\")) return false;\n\n // ReDoS protection: reject 4+ consecutive asterisks anywhere\n if (/\\*{4,}/.test(pattern)) return false;\n\n // Additional sanity: ensure any '*' run is 1..3\n for (let i = 0; i < pattern.length; i++) {\n if (pattern[i] !== \"*\") continue;\n\n let j = i;\n while (j < pattern.length && pattern[j] === \"*\") j++;\n\n const run = j - i; // consecutive '*'\n if (run < 1 || run > 3) return false; // (4+ already blocked above)\n\n // Optional: keep your patterns segment-oriented.\n // Wildcards should not be glued to letters (e.g., \"/**abc\").\n const next = pattern[j];\n if (next !== undefined && next !== \"/\") return false;\n\n i = j - 1;\n }\n\n return true;\n}\n\n/**\n * Validate redirect path (open redirect protection)\n */\nexport function isValidRedirectPath(path: string): boolean {\n\n if (typeof path !== 'string') return false;\n\n // Reasonable length limit\n if (path.length === 0 || path.length > 500) return false;\n\n // Must be a site-relative path (exactly one leading slash)\n // Reject protocol-relative URLs like \"//example.com\"\n if (!path.startsWith(\"/\") || path.startsWith(\"//\")) return false;\n\n // Extra hardening: reject anything that looks like a URL scheme\n // (e.g. \"http://\", \"https://\", \"javascript:\", \"data:\", etc.)\n return !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(path);\n\n\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validation.js","sources":["../../src/core/validation.ts"],"sourcesContent":["// ============================================================================\n// Security Validation Utilities\n// ============================================================================\n\nimport type {Session} from \"./types\";\n\n/**\n * Validate that session has the expected structure\n * Prevents crashes from malformed data and DoS attacks\n */\nexport function isValidSessionStructure(input: unknown): input is Session {\n // Must be an object\n if (!input || typeof input !== 'object') {\n return false;\n }\n\n const session = input as any;\n\n // Required: userId must be a non-empty string\n if (typeof session.userId !== 'string' || !session.userId.trim()) {\n return false;\n }\n\n // Security: Check for unexpected null bytes or control characters in userId\n if (/[\\x00-\\x1F\\x7F]/.test(session.userId)) {\n return false;\n }\n\n // DoS protection: Limit userId length\n if (session.userId.length > 255) {\n return false;\n }\n\n // Optional fields validation (if present)\n if (session.email !== undefined && session.email !== null) {\n if (typeof session.email !== 'string') {\n return false;\n }\n // Reasonable email length limit\n if (session.email.length > 320) {\n return false;\n }\n // Basic email format sanity check (not full validation, just security)\n if (session.email.length > 0 && !session.email.includes('@')) {\n return false;\n }\n }\n\n if (session.role !== undefined && session.role !== null) {\n if (typeof session.role !== 'string') {\n return false;\n }\n // Reasonable role length limit\n if (session.role.length > 100) {\n return false;\n }\n // Security: No control characters in role\n if (/[\\x00-\\x1F\\x7F]/.test(session.role)) {\n return false;\n }\n }\n\n if (session.roles !== undefined && session.roles !== null) {\n if (!Array.isArray(session.roles)) {\n return false;\n }\n // DoS protection: Limit array size\n if (session.roles.length > 100) {\n return false;\n }\n // All items must be strings with reasonable length\n if (!session.roles.every((r: any) => typeof r === 'string' && r.length <= 100)) {\n return false;\n }\n }\n\n if (session.permissions !== undefined && session.permissions !== null) {\n if (!Array.isArray(session.permissions)) {\n return false;\n }\n // DoS protection: Limit array size\n if (session.permissions.length > 500) {\n return false;\n }\n // All items must be strings with reasonable length\n if (!session.permissions.every((p: any) => typeof p === 'string' && p.length <= 200)) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Validate route patterns to prevent ReDoS attacks\n */\nexport function isValidPattern(pattern: string): boolean {\n\n if (typeof pattern !== 'string' || pattern.length === 0) return false;\n\n // Length limit\n if (pattern.length > 1000) return false;\n\n // Must start with /\n if (!pattern.startsWith(\"/\")) return false;\n\n // ReDoS protection: reject 4+ consecutive asterisks anywhere\n if (/\\*{4,}/.test(pattern)) return false;\n\n // Additional sanity: ensure any '*' run is 1..3\n for (let i = 0; i < pattern.length; i++) {\n if (pattern[i] !== \"*\") continue;\n\n let j = i;\n while (j < pattern.length && pattern[j] === \"*\") j++;\n\n const run = j - i; // consecutive '*'\n if (run < 1 || run > 3) return false; // (4+ already blocked above)\n\n // Optional: keep your patterns segment-oriented.\n // Wildcards should not be glued to letters (e.g., \"/**abc\").\n const next = pattern[j];\n if (next !== undefined && next !== \"/\") return false;\n\n i = j - 1;\n }\n\n return true;\n}\n\n/**\n * Validate redirect path (open redirect protection)\n */\nexport function isValidRedirectPath(path: string): boolean {\n\n if (typeof path !== 'string') return false;\n\n // Reasonable length limit\n if (path.length === 0 || path.length > 500) return false;\n\n // Must be a site-relative path (exactly one leading slash)\n // Reject protocol-relative URLs like \"//example.com\"\n if (!path.startsWith(\"/\") || path.startsWith(\"//\")) return false;\n\n // Extra hardening: reject anything that looks like a URL scheme\n // (e.g. \"http://\", \"https://\", \"javascript:\", \"data:\", etc.)\n return !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(path);\n\n\n}"],"names":[],"mappings":"AAUM,SAAU,uBAAuB,CAAC,KAAc,EAAA;IAElD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AACrC,QAAA,OAAO,KAAK;IAChB;IAEA,MAAM,OAAO,GAAG,KAAY;AAG5B,IAAA,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE;AAC9D,QAAA,OAAO,KAAK;IAChB;IAGA,IAAI,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;AACxC,QAAA,OAAO,KAAK;IAChB;IAGA,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE;AAC7B,QAAA,OAAO,KAAK;IAChB;AAGA,IAAA,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,EAAE;AACvD,QAAA,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE;AACnC,YAAA,OAAO,KAAK;QAChB;QAEA,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE;AAC5B,YAAA,OAAO,KAAK;QAChB;AAEA,QAAA,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC1D,YAAA,OAAO,KAAK;QAChB;IACJ;AAEA,IAAA,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE;AACrD,QAAA,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE;AAClC,YAAA,OAAO,KAAK;QAChB;QAEA,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE;AAC3B,YAAA,OAAO,KAAK;QAChB;QAEA,IAAI,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;AACtC,YAAA,OAAO,KAAK;QAChB;IACJ;AAEA,IAAA,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,EAAE;QACvD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;AAC/B,YAAA,OAAO,KAAK;QAChB;QAEA,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE;AAC5B,YAAA,OAAO,KAAK;QAChB;QAEA,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAM,KAAK,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,EAAE;AAC5E,YAAA,OAAO,KAAK;QAChB;IACJ;AAEA,IAAA,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,KAAK,IAAI,EAAE;QACnE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE;AACrC,YAAA,OAAO,KAAK;QAChB;QAEA,IAAI,OAAO,CAAC,WAAW,CAAC,MAAM,GAAG,GAAG,EAAE;AAClC,YAAA,OAAO,KAAK;QAChB;QAEA,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAM,KAAK,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,EAAE;AAClF,YAAA,OAAO,KAAK;QAChB;IACJ;AAEA,IAAA,OAAO,IAAI;AACf;AAKM,SAAU,cAAc,CAAC,OAAe,EAAA;IAE1C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,OAAO,KAAK;AAGrE,IAAA,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI;AAAE,QAAA,OAAO,KAAK;AAGvC,IAAA,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;AAAE,QAAA,OAAO,KAAK;AAG1C,IAAA,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;AAAE,QAAA,OAAO,KAAK;AAGxC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACrC,QAAA,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE;QAExB,IAAI,CAAC,GAAG,CAAC;QACT,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG;AAAE,YAAA,CAAC,EAAE;AAEpD,QAAA,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC;AACjB,QAAA,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;AAAE,YAAA,OAAO,KAAK;AAIpC,QAAA,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC;AACvB,QAAA,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,GAAG;AAAE,YAAA,OAAO,KAAK;AAEpD,QAAA,CAAC,GAAG,CAAC,GAAG,CAAC;IACb;AAEA,IAAA,OAAO,IAAI;AACf;AAKM,SAAU,mBAAmB,CAAC,IAAY,EAAA;IAE5C,IAAI,OAAO,IAAI,KAAK,QAAQ;AAAE,QAAA,OAAO,KAAK;IAG1C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG;AAAE,QAAA,OAAO,KAAK;AAIxD,IAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;AAAE,QAAA,OAAO,KAAK;AAIhE,IAAA,OAAO,CAAC,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC;AAGlD;;;;"}