astro-sessionkit 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/core/config.d.ts +2 -0
  2. package/dist/core/config.d.ts.map +1 -1
  3. package/dist/core/config.js +16 -3
  4. package/dist/core/config.js.map +1 -1
  5. package/dist/core/context.d.ts +2 -1
  6. package/dist/core/context.d.ts.map +1 -1
  7. package/dist/core/context.js +9 -4
  8. package/dist/core/context.js.map +1 -1
  9. package/dist/core/guardMiddleware.d.ts.map +1 -1
  10. package/dist/core/guardMiddleware.js +30 -9
  11. package/dist/core/guardMiddleware.js.map +1 -1
  12. package/dist/core/matcher.d.ts.map +1 -1
  13. package/dist/core/matcher.js +7 -1
  14. package/dist/core/matcher.js.map +1 -1
  15. package/dist/core/sessionMiddleware.d.ts.map +1 -1
  16. package/dist/core/sessionMiddleware.js +38 -10
  17. package/dist/core/sessionMiddleware.js.map +1 -1
  18. package/dist/core/types.d.ts +9 -0
  19. package/dist/core/types.d.ts.map +1 -1
  20. package/dist/core/types.js.map +1 -1
  21. package/dist/core/validation.d.ts.map +1 -1
  22. package/dist/core/validation.js +10 -1
  23. package/dist/core/validation.js.map +1 -1
  24. package/dist/index.d.ts +12 -4
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -24
  27. package/dist/index.js.map +1 -1
  28. package/dist/integration.d.ts +1 -1
  29. package/dist/integration.d.ts.map +1 -1
  30. package/dist/integration.js +12 -4
  31. package/dist/integration.js.map +1 -1
  32. package/dist/server.d.ts +12 -5
  33. package/dist/server.d.ts.map +1 -1
  34. package/dist/server.js +47 -12
  35. package/dist/server.js.map +1 -1
  36. package/package.json +4 -2
  37. package/src/core/config.ts +30 -4
  38. package/src/core/context.ts +29 -6
  39. package/src/core/guardMiddleware.ts +56 -35
  40. package/src/core/matcher.ts +8 -1
  41. package/src/core/sessionMiddleware.ts +45 -10
  42. package/src/core/types.ts +27 -5
  43. package/src/core/validation.ts +14 -1
  44. package/src/index.ts +3 -52
  45. package/src/integration.ts +18 -3
  46. package/src/server.ts +110 -26
package/dist/index.d.ts CHANGED
@@ -1,5 +1,11 @@
1
- import { AstroIntegration } from 'astro';
1
+ import { AstroCookies, AstroSession, AstroIntegration } from 'astro';
2
2
 
3
+ interface SessionKitContext {
4
+ cookies: AstroCookies;
5
+ session?: AstroSession;
6
+ redirect: (path: string, status?: number) => Response;
7
+ [key: string]: any;
8
+ }
3
9
  interface Session {
4
10
  userId: string;
5
11
  email?: string;
@@ -10,6 +16,7 @@ interface Session {
10
16
  }
11
17
  interface SessionContext {
12
18
  session: Session | null;
19
+ astroContext?: SessionKitContext;
13
20
  }
14
21
  interface BaseProtectionRule {
15
22
  pattern: string;
@@ -45,12 +52,13 @@ interface SessionKitConfig {
45
52
  setContextStore?: (context: SessionContext) => void;
46
53
  globalProtect?: boolean;
47
54
  exclude?: string[];
55
+ context?: any;
48
56
  debug?: boolean;
49
57
  }
50
58
 
51
- declare function sessionkit(config?: SessionKitConfig): AstroIntegration;
59
+ declare function sessionKit(config?: SessionKitConfig): AstroIntegration;
52
60
 
53
- declare const version = "0.1.0";
61
+ declare const version = "0.1.20";
54
62
 
55
- export { sessionkit as default, version };
63
+ export { sessionKit as default, version };
56
64
  export type { AccessHooks, CustomProtectionRule, PermissionProtectionRule, PermissionsProtectionRule, ProtectionRule, RoleProtectionRule, RolesProtectionRule, Session, SessionContext, SessionKitConfig };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAwBrD,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,MAAM,GAAE,gBAAqB,GAAG,gBAAgB,CAyBlF;AAMD,YAAY,EACR,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACjB,MAAM,cAAc,CAAC;AAMtB,eAAO,MAAM,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,UAAU,MAAM,eAAe,CAAC;AAEvC,eAAe,UAAU,CAAC;AAM1B,YAAY,EACR,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACjB,MAAM,cAAc,CAAC;AAMtB,eAAO,MAAM,OAAO,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -1,27 +1,6 @@
1
- import { setConfig, getConfig } from './core/config.js';
1
+ import sessionKit from './integration.js';
2
2
 
3
- function sessionkit(config = {}) {
4
- setConfig(config);
5
- const resolvedConfig = getConfig();
6
- return {
7
- name: "astro-sessionkit",
8
- hooks: {
9
- "astro:config:setup": ({ addMiddleware }) => {
10
- addMiddleware({
11
- entrypoint: "astro-sessionkit/middleware",
12
- order: "pre",
13
- });
14
- if ((resolvedConfig.protect && resolvedConfig.protect.length > 0) || resolvedConfig.globalProtect) {
15
- addMiddleware({
16
- entrypoint: "astro-sessionkit/guard",
17
- order: "pre",
18
- });
19
- }
20
- },
21
- },
22
- };
23
- }
24
- const version = "0.1.0";
3
+ const version = "0.1.20";
25
4
 
26
- export { sessionkit as default, version };
5
+ export { sessionKit as default, version };
27
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// ============================================================================\n// Astro SessionKit - Main Integration Entry Point\n// ============================================================================\n\nimport type {AstroIntegration } from \"astro\";\nimport {getConfig, setConfig} from \"./core/config\";\nimport type { SessionKitConfig } from \"./core/types\";\n\n/**\n * SessionKit - Simple session access and route protection for Astro\n *\n * @example\n * ```ts\n * // astro.config.mjs\n * import sessionkit from 'astro-sessionkit';\n *\n * export default defineConfig({\n * integrations: [\n * sessionkit({\n * loginPath: '/login',\n * protect: [\n * { pattern: '/admin/**', role: 'admin' },\n * { pattern: '/dashboard', roles: ['user', 'admin'] },\n * { pattern: '/settings', permissions: ['settings:write'] }\n * ]\n * })\n * ]\n * });\n * ```\n */\nexport default function sessionkit(config: SessionKitConfig = {}): AstroIntegration {\n // Store configuration\n setConfig(config);\n const resolvedConfig = getConfig();\n\n return {\n name: \"astro-sessionkit\",\n hooks: {\n \"astro:config:setup\": ({ addMiddleware }) => {\n // 1. Always add session context middleware first\n addMiddleware({\n entrypoint: \"astro-sessionkit/middleware\",\n order: \"pre\",\n });\n\n // 2. Add route guard if there are protection rules or global protection is enabled\n if ((resolvedConfig.protect && resolvedConfig.protect.length > 0) || resolvedConfig.globalProtect) {\n addMiddleware({\n entrypoint: \"astro-sessionkit/guard\",\n order: \"pre\",\n });\n }\n },\n },\n };\n}\n\n// ============================================================================\n// Re-export types for convenience\n// ============================================================================\n\nexport type {\n Session,\n ProtectionRule,\n RoleProtectionRule,\n RolesProtectionRule,\n PermissionProtectionRule,\n PermissionsProtectionRule,\n CustomProtectionRule,\n SessionKitConfig,\n AccessHooks,\n SessionContext\n} from \"./core/types\";\n\n// ============================================================================\n// Version export\n// ============================================================================\n\nexport const version = \"0.1.0\";"],"names":[],"mappings":";;AA8Bc,SAAU,UAAU,CAAC,SAA2B,EAAE,EAAA;IAE5D,SAAS,CAAC,MAAM,CAAC;AACjB,IAAA,MAAM,cAAc,GAAG,SAAS,EAAE;IAElC,OAAO;AACH,QAAA,IAAI,EAAE,kBAAkB;AACxB,QAAA,KAAK,EAAE;AACH,YAAA,oBAAoB,EAAE,CAAC,EAAE,aAAa,EAAE,KAAI;AAExC,gBAAA,aAAa,CAAC;AACV,oBAAA,UAAU,EAAE,6BAA6B;AACzC,oBAAA,KAAK,EAAE,KAAK;AACf,iBAAA,CAAC;AAGF,gBAAA,IAAI,CAAC,cAAc,CAAC,OAAO,IAAI,cAAc,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,KAAK,cAAc,CAAC,aAAa,EAAE;AAC/F,oBAAA,aAAa,CAAC;AACV,wBAAA,UAAU,EAAE,wBAAwB;AACpC,wBAAA,KAAK,EAAE,KAAK;AACf,qBAAA,CAAC;gBACN;YACJ,CAAC;AACJ,SAAA;KACJ;AACL;AAuBO,MAAM,OAAO,GAAG;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// ============================================================================\n// Astro SessionKit - Main Integration Entry Point\n// ============================================================================\n\nimport sessionkit from \"./integration\";\n\nexport default sessionkit;\n\n// ============================================================================\n// Re-export types for convenience\n// ============================================================================\n\nexport type {\n Session,\n ProtectionRule,\n RoleProtectionRule,\n RolesProtectionRule,\n PermissionProtectionRule,\n PermissionsProtectionRule,\n CustomProtectionRule,\n SessionKitConfig,\n AccessHooks,\n SessionContext\n} from \"./core/types\";\n\n// ============================================================================\n// Version export\n// ============================================================================\n\nexport const version = \"0.1.20\";\n"],"names":[],"mappings":";;AA6BO,MAAM,OAAO,GAAG;;;;"}
@@ -1,5 +1,5 @@
1
1
  import type { AstroIntegration } from "astro";
2
2
  import type { SessionKitConfig } from "./core/types";
3
3
  export default function sessionKit(config?: SessionKitConfig): AstroIntegration;
4
- export type { Session, ProtectionRule, SessionKitConfig } from "./core/types";
4
+ export type { Session, ProtectionRule, RoleProtectionRule, RolesProtectionRule, PermissionProtectionRule, PermissionsProtectionRule, CustomProtectionRule, SessionKitConfig, AccessHooks, SessionContext } from "./core/types";
5
5
  //# sourceMappingURL=integration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAwBrD,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,MAAM,GAAE,gBAAqB,GAAG,gBAAgB,CAyBlF;AAGD,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAwBrD,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,MAAM,GAAE,gBAAqB,GAAG,gBAAgB,CA8BlF;AAED,YAAY,EACV,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACf,MAAM,cAAc,CAAC"}
@@ -1,5 +1,6 @@
1
- import { getConfig, setConfig } from "./core/config";
2
- export default function sessionKit(config = {}) {
1
+ import { setConfig, getConfig } from './core/config.js';
2
+
3
+ function sessionKit(config = {}) {
3
4
  setConfig(config);
4
5
  const resolvedConfig = getConfig();
5
6
  return {
@@ -10,14 +11,21 @@ export default function sessionKit(config = {}) {
10
11
  entrypoint: "astro-sessionkit/middleware",
11
12
  order: "pre",
12
13
  });
13
- if ((resolvedConfig.protect && resolvedConfig.protect.length > 0) || resolvedConfig.globalProtect) {
14
+ const hasRules = (resolvedConfig.protect && resolvedConfig.protect.length > 0);
15
+ const isGlobal = !!resolvedConfig.globalProtect;
16
+ if (hasRules || isGlobal) {
14
17
  addMiddleware({
15
18
  entrypoint: "astro-sessionkit/guard",
16
19
  order: "pre",
17
20
  });
18
21
  }
22
+ else if (resolvedConfig.debug) {
23
+ console.log("[SessionKit] Route guard NOT registered: no rules and globalProtect is false.");
24
+ }
19
25
  },
20
26
  },
21
27
  };
22
28
  }
23
- //# sourceMappingURL=integration.js.map
29
+
30
+ export { sessionKit as default };
31
+ //# sourceMappingURL=integration.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAKA,OAAO,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,eAAe,CAAC;AAyBnD,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,SAA2B,EAAE;IAE9D,SAAS,CAAC,MAAM,CAAC,CAAC;IAClB,MAAM,cAAc,GAAG,SAAS,EAAE,CAAC;IAEnC,OAAO;QACL,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE;YACL,oBAAoB,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE;gBAE1C,aAAa,CAAC;oBACZ,UAAU,EAAE,6BAA6B;oBACzC,KAAK,EAAE,KAAK;iBACb,CAAC,CAAC;gBAGH,IAAI,CAAC,cAAc,CAAC,OAAO,IAAI,cAAc,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,cAAc,CAAC,aAAa,EAAE,CAAC;oBAClG,aAAa,CAAC;wBACZ,UAAU,EAAE,wBAAwB;wBACpC,KAAK,EAAE,KAAK;qBACb,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["// ============================================================================\n// Astro Integration\n// ============================================================================\n\nimport type { AstroIntegration } from \"astro\";\nimport {getConfig, setConfig} from \"./core/config\";\nimport type { SessionKitConfig } from \"./core/types\";\n\n/**\n * SessionKit - Simple session access and route protection for Astro\n * \n * @example\n * ```ts\n * // astro.config.mjs\n * import sessionkit from 'astro-sessionkit';\n * \n * export default defineConfig({\n * integrations: [\n * sessionkit({\n * loginPath: '/login',\n * protect: [\n * { pattern: '/admin/**', role: 'admin' },\n * { pattern: '/dashboard', roles: ['user', 'admin'] },\n * { pattern: '/settings', permissions: ['settings:write'] }\n * ]\n * })\n * ]\n * });\n * ```\n */\nexport default function sessionKit(config: SessionKitConfig = {}): AstroIntegration {\n // Store configuration\n setConfig(config);\n const resolvedConfig = getConfig();\n\n return {\n name: \"astro-sessionkit\",\n hooks: {\n \"astro:config:setup\": ({ addMiddleware }) => {\n // 1. Always add session context middleware first\n addMiddleware({\n entrypoint: \"astro-sessionkit/middleware\",\n order: \"pre\",\n });\n\n // 2. Add route guard if there are protection rules or global protection is enabled\n if ((resolvedConfig.protect && resolvedConfig.protect.length > 0) || resolvedConfig.globalProtect) {\n addMiddleware({\n entrypoint: \"astro-sessionkit/guard\",\n order: \"pre\",\n });\n }\n },\n },\n };\n}\n\n// Re-export types for convenience\nexport type { Session, ProtectionRule, SessionKitConfig } from \"./core/types\";\n"]}
1
+ {"version":3,"file":"integration.js","sources":["../src/integration.ts"],"sourcesContent":["// ============================================================================\n// Astro Integration\n// ============================================================================\n\nimport type { AstroIntegration } from \"astro\";\nimport {getConfig, setConfig} from \"./core/config\";\nimport type { SessionKitConfig } from \"./core/types\";\n\n/**\n * SessionKit - Simple session access and route protection for Astro\n * \n * @example\n * ```ts\n * // astro.config.mjs\n * import sessionkit from 'astro-sessionkit';\n * \n * export default defineConfig({\n * integrations: [\n * sessionkit({\n * loginPath: '/login',\n * protect: [\n * { pattern: '/admin/**', role: 'admin' },\n * { pattern: '/dashboard', roles: ['user', 'admin'] },\n * { pattern: '/settings', permissions: ['settings:write'] }\n * ]\n * })\n * ]\n * });\n * ```\n */\nexport default function sessionKit(config: SessionKitConfig = {}): AstroIntegration {\n // Store configuration\n setConfig(config);\n const resolvedConfig = getConfig();\n\n return {\n name: \"astro-sessionkit\",\n hooks: {\n \"astro:config:setup\": ({ addMiddleware }) => {\n // 1. Always add session context middleware first\n addMiddleware({\n entrypoint: \"astro-sessionkit/middleware\",\n order: \"pre\",\n });\n\n // 2. Add route guard if there are protection rules or global protection is enabled\n const hasRules = (resolvedConfig.protect && resolvedConfig.protect.length > 0);\n const isGlobal = !!resolvedConfig.globalProtect;\n\n if (hasRules || isGlobal) {\n addMiddleware({\n entrypoint: \"astro-sessionkit/guard\",\n order: \"pre\",\n });\n } else if (resolvedConfig.debug) {\n console.log(\"[SessionKit] Route guard NOT registered: no rules and globalProtect is false.\");\n }\n },\n },\n };\n}\n\nexport type {\n Session,\n ProtectionRule,\n RoleProtectionRule,\n RolesProtectionRule,\n PermissionProtectionRule,\n PermissionsProtectionRule,\n CustomProtectionRule,\n SessionKitConfig,\n AccessHooks,\n SessionContext\n} from \"./core/types\";\n"],"names":[],"mappings":";;AA8Bc,SAAU,UAAU,CAAC,SAA2B,EAAE,EAAA;IAE9D,SAAS,CAAC,MAAM,CAAC;AACjB,IAAA,MAAM,cAAc,GAAG,SAAS,EAAE;IAElC,OAAO;AACL,QAAA,IAAI,EAAE,kBAAkB;AACxB,QAAA,KAAK,EAAE;AACL,YAAA,oBAAoB,EAAE,CAAC,EAAE,aAAa,EAAE,KAAI;AAE1C,gBAAA,aAAa,CAAC;AACZ,oBAAA,UAAU,EAAE,6BAA6B;AACzC,oBAAA,KAAK,EAAE,KAAK;AACb,iBAAA,CAAC;AAGF,gBAAA,MAAM,QAAQ,IAAI,cAAc,CAAC,OAAO,IAAI,cAAc,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9E,gBAAA,MAAM,QAAQ,GAAG,CAAC,CAAC,cAAc,CAAC,aAAa;AAE/C,gBAAA,IAAI,QAAQ,IAAI,QAAQ,EAAE;AACxB,oBAAA,aAAa,CAAC;AACZ,wBAAA,UAAU,EAAE,wBAAwB;AACpC,wBAAA,KAAK,EAAE,KAAK;AACb,qBAAA,CAAC;gBACJ;AAAO,qBAAA,IAAI,cAAc,CAAC,KAAK,EAAE;AAC/B,oBAAA,OAAO,CAAC,GAAG,CAAC,+EAA+E,CAAC;gBAC9F;YACF,CAAC;AACF,SAAA;KACF;AACH;;;;"}
package/dist/server.d.ts CHANGED
@@ -1,5 +1,11 @@
1
- import { APIContext } from 'astro';
1
+ import { AstroCookies, AstroSession } from 'astro';
2
2
 
3
+ interface SessionKitContext {
4
+ cookies: AstroCookies;
5
+ session?: AstroSession;
6
+ redirect: (path: string, status?: number) => Response;
7
+ [key: string]: any;
8
+ }
3
9
  interface Session {
4
10
  userId: string;
5
11
  email?: string;
@@ -17,8 +23,9 @@ declare function hasPermission(permission: string): boolean;
17
23
  declare function hasAllPermissions(...permissions: string[]): boolean;
18
24
  declare function hasAnyPermission(...permissions: string[]): boolean;
19
25
  declare function hasRolePermission(role: string, permission: string): boolean;
20
- declare function setSession(context: APIContext, session: Session): void;
21
- declare function clearSession(context: APIContext): void;
22
- declare function updateSession(context: APIContext, updates: Partial<Session>): void;
26
+ declare function setSession(session: Session, context?: SessionKitContext): void;
27
+ declare function clearSession(context?: SessionKitContext): void;
28
+ declare function regenerateSession(context?: SessionKitContext): void;
29
+ declare function updateSession(updates: Partial<Session>, context?: SessionKitContext): void;
23
30
 
24
- export { clearSession, getSession, hasAllPermissions, hasAnyPermission, hasPermission, hasRole, hasRolePermission, isAuthenticated, requireSession, setSession, updateSession };
31
+ export { clearSession, getSession, hasAllPermissions, hasAnyPermission, hasPermission, hasRole, hasRolePermission, isAuthenticated, regenerateSession, requireSession, setSession, updateSession };
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,OAAO,CAAC;AActC,wBAAgB,UAAU,IAAI,OAAO,GAAG,IAAI,CAG3C;AAcD,wBAAgB,cAAc,IAAI,OAAO,CAQxC;AAKD,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAKD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAS7C;AAKD,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAKzD;AAKD,wBAAgB,iBAAiB,CAAC,GAAG,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAMnE;AAKD,wBAAgB,gBAAgB,CAAC,GAAG,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAMlE;AAsBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE3E;AAqCD,wBAAgB,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAUtE;AAyBD,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAEtD;AA6BD,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAkBlF"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,OAAO,EAAE,iBAAiB,EAAC,MAAM,cAAc,CAAC;AAc7D,wBAAgB,UAAU,IAAI,OAAO,GAAG,IAAI,CAG3C;AAcD,wBAAgB,cAAc,IAAI,OAAO,CAQxC;AAKD,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAKD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAS7C;AAKD,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAKzD;AAKD,wBAAgB,iBAAiB,CAAC,GAAG,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAMnE;AAKD,wBAAgB,gBAAgB,CAAC,GAAG,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAMlE;AAsBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE3E;AAqCD,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAyB9E;AAyBD,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAiB9D;AAyBD,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAanE;AA6BD,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAmC1F"}
package/dist/server.js CHANGED
@@ -46,26 +46,61 @@ function hasAnyPermission(...permissions) {
46
46
  function hasRolePermission(role, permission) {
47
47
  return hasRole(role) && hasPermission(permission);
48
48
  }
49
- function setSession(context, session) {
49
+ function setSession(session, context) {
50
+ const store = getContextStore();
51
+ const ctx = context || store?.astroContext;
52
+ if (!ctx) {
53
+ throw new Error('[SessionKit] Cannot set session: Astro context is missing. ' +
54
+ 'Provide it as a second argument or ensure sessionMiddleware is running.');
55
+ }
50
56
  if (!isValidSessionStructure(session)) {
51
57
  throw new Error('[SessionKit] Invalid session structure. Session must have a valid userId and follow the Session interface.');
52
58
  }
53
- context.session?.set('__session__', session);
59
+ if (store) {
60
+ store.session = session;
61
+ }
62
+ ctx.session?.set('__session__', session);
54
63
  }
55
64
  function clearSession(context) {
56
- context.session?.delete('__session__');
65
+ const store = getContextStore();
66
+ const ctx = context || store?.astroContext;
67
+ if (!ctx) {
68
+ throw new Error('[SessionKit] Cannot clear session: Astro context is missing. ' +
69
+ 'Provide it as an argument or ensure sessionMiddleware is running.');
70
+ }
71
+ if (store) {
72
+ store.session = null;
73
+ }
74
+ ctx.session?.delete('__session__');
75
+ }
76
+ function regenerateSession(context) {
77
+ const ctx = context || getContextStore()?.astroContext;
78
+ if (!ctx) {
79
+ throw new Error('[SessionKit] Cannot regenerate session: Astro context is missing. ' +
80
+ 'Provide it as an argument or ensure sessionMiddleware is running.');
81
+ }
82
+ if (ctx.session?.regenerate) {
83
+ ctx.session.regenerate();
84
+ }
57
85
  }
58
- function updateSession(context, updates) {
59
- const currentSession = context.session?.get('__session__');
60
- if (!currentSession) {
61
- throw new Error('[SessionKit] Cannot update session: no session exists');
86
+ function updateSession(updates, context) {
87
+ const store = getContextStore();
88
+ const ctx = context || store?.astroContext;
89
+ if (!ctx) {
90
+ throw new Error('[SessionKit] Cannot update session: Astro context is missing. ' +
91
+ 'Provide it as a second argument or ensure sessionMiddleware is running.');
62
92
  }
63
- const updatedSession = { ...currentSession, ...updates };
64
- if (!isValidSessionStructure(updatedSession)) {
65
- throw new Error('[SessionKit] Invalid session structure after update. Ensure all fields are valid.');
93
+ const currentSession = store?.session || ctx.session?.get('__session__');
94
+ if (!currentSession || (currentSession instanceof Promise)) {
95
+ const session = currentSession instanceof Promise ? null : currentSession;
96
+ if (!session) {
97
+ throw new Error('[SessionKit] Cannot update session: no session exists');
98
+ }
66
99
  }
67
- context.session?.set('__session__', updatedSession);
100
+ const session = currentSession;
101
+ const updatedSession = { ...session, ...updates };
102
+ setSession(updatedSession, ctx);
68
103
  }
69
104
 
70
- export { clearSession, getSession, hasAllPermissions, hasAnyPermission, hasPermission, hasRole, hasRolePermission, isAuthenticated, requireSession, setSession, updateSession };
105
+ export { clearSession, getSession, hasAllPermissions, hasAnyPermission, hasPermission, hasRole, hasRolePermission, isAuthenticated, regenerateSession, requireSession, setSession, updateSession };
71
106
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sources":["../src/server.ts"],"sourcesContent":["// ============================================================================\n// Public Server API - Use these in your Astro components/endpoints\n// ============================================================================\n\nimport {getContextStore} from \"./core/context\";\nimport {isValidSessionStructure} from \"./core/validation\";\nimport type {Session} from \"./core/types\";\nimport type {APIContext} from \"astro\";\n\n/**\n * Get the current session (returns null if not authenticated)\n *\n * @example\n * ```ts\n * // In .astro component\n * const session = getSession();\n * if (session) {\n * console.log('User ID:', session.userId);\n * }\n * ```\n */\nexport function getSession(): Session | null {\n const context = getContextStore();\n return context?.session ?? null;\n}\n\n/**\n * Get the current session or throw if not authenticated\n *\n * @throws {Response} 401 Unauthorized if no session\n *\n * @example\n * ```ts\n * // In API endpoint\n * const session = requireSession();\n * // TypeScript knows session is not null here\n * ```\n */\nexport function requireSession(): Session {\n const session = getSession();\n\n if (!session) {\n throw new Response(\"Unauthorized\", {status: 401});\n }\n\n return session;\n}\n\n/**\n * Check if user is authenticated\n */\nexport function isAuthenticated(): boolean {\n return getSession() !== null;\n}\n\n/**\n * Check if user has a specific role\n */\nexport function hasRole(role: string): boolean {\n const session = getSession();\n if (!session) return false;\n\n // Check primary role\n if (session.role === role) return true;\n\n // Check additional roles\n return session.roles?.includes(role) ?? false;\n}\n\n/**\n * Check if user has a specific permission\n */\nexport function hasPermission(permission: string): boolean {\n const session = getSession();\n if (!session) return false;\n\n return session.permissions?.includes(permission) ?? false;\n}\n\n/**\n * Check if user has ALL of the specified permissions\n */\nexport function hasAllPermissions(...permissions: string[]): boolean {\n const session = getSession();\n if (!session) return false;\n\n const userPermissions = session.permissions ?? [];\n return permissions.every((p) => userPermissions.includes(p));\n}\n\n/**\n * Check if user has ANY of the specified permissions\n */\nexport function hasAnyPermission(...permissions: string[]): boolean {\n const session = getSession();\n if (!session) return false;\n\n const userPermissions = session.permissions ?? [];\n return permissions.some((p) => userPermissions.includes(p));\n}\n\n// ============================================================================\n// Session Management\n// ============================================================================\n\n/**\n * Check if a specific role has a specific permission.\n *\n * This checks if the current user has the specified role and if that role\n * is associated with the specified permission.\n *\n * @param role - The role to check\n * @param permission - The permission to check\n *\n * @example\n * ```ts\n * if (hasRolePermission(\"admin\", \"delete users\")) {\n * // ...\n * }\n * ```\n */\nexport function hasRolePermission(role: string, permission: string): boolean {\n return hasRole(role) && hasPermission(permission);\n}\n\n/**\n * Set session data in context.locals.session\n *\n * Use this after successful authentication to register the user's session.\n * This does NOT handle session storage (cookies, Redis, etc.) - you must do that separately.\n *\n * @param context - Astro API context\n * @param session - Session data to set\n *\n * @throws {Error} If session structure is invalid\n *\n * @example\n * ```ts\n * // In API endpoint after verifying credentials\n * export const POST: APIRoute = async (context) => {\n * const { email, password } = await context.request.json();\n * const user = await verifyCredentials(email, password);\n *\n * if (user) {\n * // Register session with SessionKit\n * setSession(context, {\n * userId: user.id,\n * email: user.email,\n * role: user.role,\n * permissions: user.permissions\n * });\n *\n * // YOU must also store the session (cookie, Redis, etc.)\n * context.cookies.set('session_id', sessionId, { httpOnly: true });\n *\n * return new Response(JSON.stringify({ success: true }));\n * }\n * };\n * ```\n */\nexport function setSession(context: APIContext, session: Session): void {\n // Validate session structure\n if (!isValidSessionStructure(session)) {\n throw new Error(\n '[SessionKit] Invalid session structure. Session must have a valid userId and follow the Session interface.'\n );\n }\n\n // Set in context.locals for SessionKit middleware to read\n context.session?.set('__session__', session);\n}\n\n/**\n * Clear session from context.locals.session\n *\n * Use this during logout. This does NOT delete session storage (cookies, Redis, etc.) -\n * you must do that separately.\n *\n * @param context - Astro API context\n *\n * @example\n * ```ts\n * // In logout endpoint\n * export const POST: APIRoute = async (context) => {\n * // Clear from SessionKit\n * clearSession(context);\n *\n * // YOU must also delete the session storage\n * context.cookies.delete('session_id');\n * await db.deleteSession(sessionId);\n *\n * return context.redirect('/');\n * };\n * ```\n */\nexport function clearSession(context: APIContext): void {\n context.session?.delete('__session__');\n}\n\n/**\n * Update specific fields in the current session\n *\n * Useful for updating session data without replacing the entire session.\n * The updated session is validated before being set.\n *\n * @param context - Astro API context\n * @param updates - Partial session data to merge\n *\n * @throws {Error} If no session exists or updated session is invalid\n *\n * @example\n * ```ts\n * // Update user's role after promotion\n * export const POST: APIRoute = async (context) => {\n * updateSession(context, {\n * role: 'admin',\n * permissions: ['admin:read', 'admin:write']\n * });\n *\n * // YOU must also update session storage\n * await db.updateSession(sessionId, updatedData);\n *\n * return new Response(JSON.stringify({ success: true }));\n * };\n * ```\n */\nexport function updateSession(context: APIContext, updates: Partial<Session>): void {\n const currentSession = context.session?.get<Session>('__session__');\n\n if (!currentSession) {\n throw new Error('[SessionKit] Cannot update session: no session exists');\n }\n\n // Merge updates with current session\n const updatedSession = {...currentSession, ...updates};\n\n // Validate merged session\n if (!isValidSessionStructure(updatedSession)) {\n throw new Error(\n '[SessionKit] Invalid session structure after update. Ensure all fields are valid.'\n );\n }\n\n context.session?.set('__session__', updatedSession);\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sources":["../src/server.ts"],"sourcesContent":["// ============================================================================\n// Public Server API - Use these in your Astro components/endpoints\n// ============================================================================\n\nimport {getContextStore} from \"./core/context\";\nimport {isValidSessionStructure} from \"./core/validation\";\nimport type {Session, SessionKitContext} from \"./core/types\";\n\n/**\n * Get the current session (returns null if not authenticated)\n *\n * @example\n * ```ts\n * // In .astro component\n * const session = getSession();\n * if (session) {\n * console.log('User ID:', session.userId);\n * }\n * ```\n */\nexport function getSession(): Session | null {\n const context = getContextStore();\n return context?.session ?? null;\n}\n\n/**\n * Get the current session or throw if not authenticated\n *\n * @throws {Response} 401 Unauthorized if no session\n *\n * @example\n * ```ts\n * // In API endpoint\n * const session = requireSession();\n * // TypeScript knows session is not null here\n * ```\n */\nexport function requireSession(): Session {\n const session = getSession();\n\n if (!session) {\n throw new Response(\"Unauthorized\", {status: 401});\n }\n\n return session;\n}\n\n/**\n * Check if user is authenticated\n */\nexport function isAuthenticated(): boolean {\n return getSession() !== null;\n}\n\n/**\n * Check if user has a specific role\n */\nexport function hasRole(role: string): boolean {\n const session = getSession();\n if (!session) return false;\n\n // Check primary role\n if (session.role === role) return true;\n\n // Check additional roles\n return session.roles?.includes(role) ?? false;\n}\n\n/**\n * Check if user has a specific permission\n */\nexport function hasPermission(permission: string): boolean {\n const session = getSession();\n if (!session) return false;\n\n return session.permissions?.includes(permission) ?? false;\n}\n\n/**\n * Check if user has ALL of the specified permissions\n */\nexport function hasAllPermissions(...permissions: string[]): boolean {\n const session = getSession();\n if (!session) return false;\n\n const userPermissions = session.permissions ?? [];\n return permissions.every((p) => userPermissions.includes(p));\n}\n\n/**\n * Check if user has ANY of the specified permissions\n */\nexport function hasAnyPermission(...permissions: string[]): boolean {\n const session = getSession();\n if (!session) return false;\n\n const userPermissions = session.permissions ?? [];\n return permissions.some((p) => userPermissions.includes(p));\n}\n\n// ============================================================================\n// Session Management\n// ============================================================================\n\n/**\n * Check if a specific role has a specific permission.\n *\n * This checks if the current user has the specified role and if that role\n * is associated with the specified permission.\n *\n * @param role - The role to check\n * @param permission - The permission to check\n *\n * @example\n * ```ts\n * if (hasRolePermission(\"admin\", \"delete users\")) {\n * // ...\n * }\n * ```\n */\nexport function hasRolePermission(role: string, permission: string): boolean {\n return hasRole(role) && hasPermission(permission);\n}\n\n/**\n * Set session data in context.locals.session\n *\n * Use this after successful authentication to register the user's session.\n * This does NOT handle session storage (cookies, Redis, etc.) - you must do that separately.\n *\n * @param session - Session data to set\n * @param context - Astro API context (optional if called within request context)\n *\n * @throws {Error} If session structure is invalid or context missing\n *\n * @example\n * ```ts\n * // In API endpoint after verifying credentials\n * export const POST: APIRoute = async (context) => {\n * const { email, password } = await context.request.json();\n * const user = await verifyCredentials(email, password);\n *\n * if (user) {\n * // Register session with SessionKit\n * setSession({\n * userId: user.id,\n * email: user.email,\n * role: user.role,\n * permissions: user.permissions\n * });\n *\n * // YOU must also store the session (cookie, Redis, etc.)\n * context.cookies.set('session_id', sessionId, { httpOnly: true });\n *\n * return new Response(JSON.stringify({ success: true }));\n * }\n * };\n * ```\n */\nexport function setSession(session: Session, context?: SessionKitContext): void {\n const store = getContextStore();\n const ctx = context || store?.astroContext;\n\n if (!ctx) {\n throw new Error(\n '[SessionKit] Cannot set session: Astro context is missing. ' +\n 'Provide it as a second argument or ensure sessionMiddleware is running.'\n );\n }\n\n // Validate session structure\n if (!isValidSessionStructure(session)) {\n throw new Error(\n '[SessionKit] Invalid session structure. Session must have a valid userId and follow the Session interface.'\n );\n }\n\n // Update ALS store if available for same-request consistency\n if (store) {\n store.session = session;\n }\n\n // Set in context.session for Astro to persist\n ctx.session?.set('__session__', session);\n}\n\n/**\n * Clear session from context.locals.session\n *\n * Use this during logout. This does NOT delete session storage (cookies, Redis, etc.) -\n * you must do that separately.\n *\n * @param context - Astro API context (optional if called within request context)\n *\n * @example\n * ```ts\n * // In logout endpoint\n * export const POST: APIRoute = async (context) => {\n * // Clear from SessionKit\n * clearSession();\n *\n * // YOU must also delete the session storage\n * context.cookies.delete('session_id');\n * await db.deleteSession(sessionId);\n *\n * return context.redirect('/');\n * };\n * ```\n */\nexport function clearSession(context?: SessionKitContext): void {\n const store = getContextStore();\n const ctx = context || store?.astroContext;\n\n if (!ctx) {\n throw new Error(\n '[SessionKit] Cannot clear session: Astro context is missing. ' +\n 'Provide it as an argument or ensure sessionMiddleware is running.'\n );\n }\n\n // Update ALS store if available for same-request consistency\n if (store) {\n store.session = null;\n }\n\n ctx.session?.delete('__session__');\n}\n\n/**\n * Regenerate the session ID to prevent session fixation attacks\n *\n * Use this after a successful login or privilege change.\n * This is only supported if the underlying Astro session driver supports it.\n *\n * @param context - Astro API context (optional if called within request context)\n *\n * @example\n * ```ts\n * // In login endpoint\n * export const POST: APIRoute = async (context) => {\n * const user = await authenticate(request);\n * if (user) {\n * // 1. Regenerate session ID\n * regenerateSession();\n *\n * // 2. Set new session data\n * setSession({ userId: user.id, role: user.role });\n * }\n * }\n * ```\n */\nexport function regenerateSession(context?: SessionKitContext): void {\n const ctx = context || getContextStore()?.astroContext;\n\n if (!ctx) {\n throw new Error(\n '[SessionKit] Cannot regenerate session: Astro context is missing. ' +\n 'Provide it as an argument or ensure sessionMiddleware is running.'\n );\n }\n\n if (ctx.session?.regenerate) {\n ctx.session.regenerate();\n }\n}\n\n/**\n * Update specific fields in the current session\n *\n * Useful for updating session data without replacing the entire session.\n * The updated session is validated before being set.\n *\n * @param updates - Partial session data to merge\n * @param context - Astro API context (optional if called within request context)\n *\n * @throws {Error} If no session exists or updated session is invalid\n *\n * @example\n * ```ts\n * // Update user's role after promotion\n * export const POST: APIRoute = async (context) => {\n * updateSession({\n * role: 'admin',\n * permissions: ['admin:read', 'admin:write']\n * });\n *\n * // YOU must also update session storage\n * await db.updateSession(sessionId, updatedData);\n *\n * return new Response(JSON.stringify({ success: true }));\n * };\n * ```\n */\nexport function updateSession(updates: Partial<Session>, context?: SessionKitContext): void {\n const store = getContextStore();\n const ctx = context || store?.astroContext;\n\n if (!ctx) {\n throw new Error(\n '[SessionKit] Cannot update session: Astro context is missing. ' +\n 'Provide it as a second argument or ensure sessionMiddleware is running.'\n );\n }\n\n // Get current session from ALS (preferred) or Astro session\n const currentSession = store?.session || ctx.session?.get<Session>('__session__');\n\n // Note: ctx.session.get might return a Promise in some Astro versions/drivers.\n // However, since sessionMiddleware already awaits it, store.session should be populated.\n // If store.session is missing but we are in a middleware-managed request, it means no session exists.\n \n if (!currentSession || (currentSession instanceof Promise)) {\n // If it's a promise, we might have a sync/async mismatch, but usually getSession() handles this.\n // For robustness, we check if we actually have a session object.\n const session = currentSession instanceof Promise ? null : currentSession;\n if (!session) {\n throw new Error('[SessionKit] Cannot update session: no session exists');\n }\n }\n\n // We can safely cast here if it's not a promise\n const session = currentSession as Session;\n\n // Merge updates with current session\n const updatedSession = {...session, ...updates};\n\n // Use setSession to handle validation and both store updates\n setSession(updatedSession, ctx);\n}"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-sessionkit",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Simple session access and route protection for Astro applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -85,6 +85,8 @@
85
85
  "test": "vitest run",
86
86
  "test:watch": "vitest",
87
87
  "test:coverage": "vitest run --coverage",
88
- "bench": "vitest bench run bench/"
88
+ "bench": "vitest bench run bench/",
89
+ "playground:install": "cd playground && npm install",
90
+ "playground:dev": "cd playground && npm run dev"
89
91
  }
90
92
  }
@@ -18,11 +18,15 @@ export interface ResolvedConfig {
18
18
  runWithContext?: <T>(context: SessionContext, fn: () => T) => T | Promise<T>;
19
19
  getContextStore?: () => SessionContext | undefined;
20
20
  setContextStore?: (context: SessionContext) => void;
21
+ context?: any;
21
22
  globalProtect: boolean;
22
23
  exclude: string[];
23
24
  debug: boolean;
24
25
  }
25
26
 
27
+ const CONFIG_KEY = Symbol.for('astro-sessionkit.config');
28
+ const globalStorage = globalThis as any;
29
+
26
30
  const DEFAULT_CONFIG: ResolvedConfig = {
27
31
  loginPath: "/login",
28
32
  protect: [],
@@ -36,7 +40,17 @@ const DEFAULT_CONFIG: ResolvedConfig = {
36
40
  debug: false,
37
41
  };
38
42
 
39
- let config: ResolvedConfig = { ...DEFAULT_CONFIG };
43
+ // Initialize global storage if not present
44
+ if (!globalStorage[CONFIG_KEY]) {
45
+ globalStorage[CONFIG_KEY] = Object.freeze({ ...DEFAULT_CONFIG });
46
+ }
47
+
48
+ /**
49
+ * Reset configuration to defaults (mainly for testing)
50
+ */
51
+ export function resetConfig(): void {
52
+ globalStorage[CONFIG_KEY] = Object.freeze({ ...DEFAULT_CONFIG });
53
+ }
40
54
 
41
55
  /**
42
56
  * Set configuration (called by integration)
@@ -113,7 +127,8 @@ export function setConfig(userConfig: SessionKitConfig): void {
113
127
  newConfig.runWithContext = userConfig.runWithContext;
114
128
  newConfig.getContextStore = userConfig.getContextStore;
115
129
  newConfig.setContextStore = userConfig.setContextStore;
116
-
130
+ newConfig.context = userConfig.context;
131
+
117
132
  if (userConfig.globalProtect !== undefined) {
118
133
  newConfig.globalProtect = userConfig.globalProtect;
119
134
  }
@@ -137,12 +152,23 @@ export function setConfig(userConfig: SessionKitConfig): void {
137
152
  }
138
153
 
139
154
  // Atomic update
140
- config = Object.freeze(newConfig);
155
+ globalStorage[CONFIG_KEY] = Object.freeze(newConfig);
141
156
  }
142
157
 
143
158
  /**
144
159
  * Get current configuration
145
160
  */
146
161
  export function getConfig(): ResolvedConfig {
147
- return config;
162
+ return globalStorage[CONFIG_KEY] || DEFAULT_CONFIG;
163
+ }
164
+
165
+ // Handle injected configuration from Astro integration
166
+ try {
167
+ // @ts-ignore
168
+ const injectedConfig = typeof __SESSIONKIT_CONFIG__ !== 'undefined' ? __SESSIONKIT_CONFIG__ : undefined;
169
+ if (injectedConfig) {
170
+ setConfig(injectedConfig);
171
+ }
172
+ } catch (e) {
173
+ // Ignore errors in environments where __SESSIONKIT_CONFIG__ might be restricted
148
174
  }
@@ -19,12 +19,35 @@ export function runWithContext<T>(
19
19
  }
20
20
 
21
21
  /**
22
- * Get the current session context (only available inside middleware chain)
22
+ * Get current Astro context (from middleware binding or explicit)
23
23
  */
24
- export function getContextStore(): SessionContext | undefined {
25
- const customGetter = getConfig().getContextStore;
26
- if (customGetter) {
27
- return customGetter();
24
+ export function getContextStore(): SessionContext {
25
+ const config = getConfig();
26
+ const getStore = config.getContextStore;
27
+ const context = (config as any).context || als;
28
+
29
+ const store = getStore
30
+ ? getStore()
31
+ : (context as AsyncLocalStorage<SessionContext>).getStore();
32
+
33
+ if (!store) {
34
+ return undefined as any;
28
35
  }
29
- return als.getStore();
36
+
37
+ return store;
30
38
  }
39
+
40
+ /**
41
+ * Check if context is available
42
+ */
43
+ export function hasContext(): boolean {
44
+ const config = getConfig();
45
+ const getStore = config.getContextStore;
46
+ const context = (config as any).context || als;
47
+
48
+ const store = getStore
49
+ ? getStore()
50
+ : (context as AsyncLocalStorage<SessionContext>).getStore();
51
+
52
+ return !!store;
53
+ }
@@ -2,7 +2,7 @@
2
2
  // Route Guard Middleware - Enforces protection rules
3
3
  // ============================================================================
4
4
 
5
- import type {APIContext, MiddlewareHandler} from "astro";
5
+ import type {MiddlewareHandler} from "astro";
6
6
  import { getContextStore } from "./context";
7
7
  import { getConfig } from "./config";
8
8
  import { matchesPattern } from "./matcher";
@@ -73,70 +73,91 @@ async function checkRule(rule: ProtectionRule, session: Session | null): Promise
73
73
  * Create route guard middleware
74
74
  */
75
75
  export function createGuardMiddleware(): MiddlewareHandler {
76
- return async (context : APIContext, next) => {
77
- const { protect, loginPath, globalProtect, exclude } = getConfig();
78
-
76
+ return async (context, next) => {
79
77
  let pathname: string;
80
78
  try {
81
- pathname = new URL(context.request.url).pathname;
79
+ pathname = new URL(context.request.url).pathname;
82
80
  } catch {
83
- // Fallback if URL is invalid (unlikely in Astro)
84
- pathname = "/";
81
+ pathname = "/";
85
82
  }
86
83
 
87
- logger.debug(`[Guard] Pathname: ${pathname}, GlobalProtect: ${globalProtect}, Rules: ${protect.length}`);
84
+ const config = getConfig();
85
+ const {protect, loginPath, globalProtect, exclude, debug} = config;
86
+
87
+ if (debug) {
88
+ logger.debug(`[Guard] Pathname: ${pathname}, GlobalProtect: ${globalProtect}, Rules: ${protect.length}`);
89
+ }
88
90
 
89
91
  // No rules configured and no global protect - skip
90
92
  if (protect.length === 0 && !globalProtect) {
91
- return next();
93
+ if (debug) {
94
+ logger.debug(`[Guard] Skipping ${pathname} because no rules are configured and globalProtect is false`);
95
+ }
96
+ return next();
92
97
  }
93
98
 
94
99
  const sessionContext = getContextStore();
95
100
  const session = sessionContext?.session ?? null;
96
101
 
102
+ if (debug) {
103
+ logger.debug(`[Guard] Session retrieved from store: ${session ? 'exists' : 'null'}`);
104
+ }
105
+
97
106
  // Find matching rule
98
107
  const rule = protect.find((r) => matchesPattern(r.pattern, pathname));
99
-
100
- if (rule) {
101
- logger.debug(`[Guard] Found matching rule for ${pathname}:`, rule);
108
+
109
+ if (rule && debug) {
110
+ logger.debug(`[Guard] Found matching rule for ${pathname}:`, rule);
102
111
  }
103
112
 
104
113
  // No matching rule - check global protection
105
114
  if (!rule) {
106
- if (globalProtect) {
107
- // Skip if path is in exclude list
108
- if (exclude.some((pattern) => matchesPattern(pattern, pathname))) {
109
- logger.debug(`[GlobalProtect] Skipping ${pathname} because it matches an exclude pattern`);
110
- return next();
111
- }
112
-
113
- // Skip if it's the login page itself (to avoid redirect loops)
114
- if (pathname === loginPath) {
115
- logger.debug(`[GlobalProtect] Skipping ${pathname} because it is the loginPath`);
116
- return next();
115
+ if (globalProtect) {
116
+ // Skip if path is in exclude list
117
+ if (exclude.some((pattern) => matchesPattern(pattern, pathname))) {
118
+ if (debug) {
119
+ logger.debug(`[GlobalProtect] Skipping ${pathname} because it matches an exclude pattern`);
120
+ }
121
+ return next();
122
+ }
123
+
124
+ // Skip if it's the login page itself (to avoid redirect loops)
125
+ if (pathname === loginPath) {
126
+ if (debug) {
127
+ logger.debug(`[GlobalProtect] Skipping ${pathname} because it is the loginPath`);
128
+ }
129
+ return next();
130
+ }
131
+
132
+ // Require valid session
133
+ if (!session || !isValidSessionStructure(session)) {
134
+ if (debug) {
135
+ logger.debug(`[GlobalProtect] Redirecting to ${loginPath} because session is ${session ? 'invalid' : 'missing'}`);
136
+ }
137
+ return context.redirect(loginPath);
138
+ }
117
139
  }
118
140
 
119
- // Require valid session
120
- if (!session || !isValidSessionStructure(session)) {
121
- logger.debug(`[GlobalProtect] Redirecting to ${loginPath} because session is ${session ? 'invalid' : 'missing'}`);
122
- return context.redirect(loginPath);
141
+ if (debug) {
142
+ logger.debug(`[GlobalProtect] Allowing ${pathname} because session is valid or globalProtect is false`);
123
143
  }
124
- }
125
-
126
- logger.debug(`[GlobalProtect] Allowing ${pathname} because session is valid or globalProtect is false`);
127
- return next();
144
+ return next();
128
145
  }
129
146
 
130
147
  // Check if access is allowed
131
148
  const allowed = await checkRule(rule, session);
132
149
 
133
150
  if (!allowed) {
134
- const redirectTo = rule.redirectTo ?? loginPath;
135
- logger.debug(`[Guard] Redirecting to ${redirectTo} because access was denied by rule:`, rule);
136
- return context.redirect(redirectTo);
151
+ const redirectTo = rule.redirectTo ?? loginPath;
152
+ if (debug) {
153
+ logger.debug(`[Guard] Redirecting to ${redirectTo} because access was denied by rule:`, rule);
154
+ }
155
+ return context.redirect(redirectTo);
137
156
  }
138
157
 
139
- logger.debug(`[Guard] Allowing ${pathname} because access was granted by rule:`, rule);
158
+ if (debug) {
159
+ logger.debug(`[Guard] Allowing ${pathname} because access was granted by rule:`, rule);
160
+ }
140
161
  return next();
141
162
  };
142
163
  }
@@ -6,7 +6,12 @@ function escapeRegex(str: string): string {
6
6
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7
7
  }
8
8
 
9
+ const regexCache = new Map<string, RegExp>();
10
+
9
11
  function globToRegex(pattern: string): RegExp {
12
+ const cached = regexCache.get(pattern);
13
+ if (cached) return cached;
14
+
10
15
  let regex = "";
11
16
  let i = 0;
12
17
 
@@ -53,7 +58,9 @@ function globToRegex(pattern: string): RegExp {
53
58
  i += 1;
54
59
  }
55
60
 
56
- return new RegExp(`^${regex}$`);
61
+ const result = new RegExp(`^${regex}$`);
62
+ regexCache.set(pattern, result);
63
+ return result;
57
64
  }
58
65
 
59
66
  export function matchesPattern(pattern: string, path: string): boolean {