astro-sessionkit 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/config.d.ts +3 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +40 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/context.js.map +1 -1
- package/dist/core/guardMiddleware.d.ts.map +1 -1
- package/dist/core/guardMiddleware.js +15 -3
- package/dist/core/guardMiddleware.js.map +1 -1
- package/dist/core/logger.d.ts +4 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +11 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/matcher.js.map +1 -1
- package/dist/core/sessionMiddleware.d.ts.map +1 -1
- package/dist/core/sessionMiddleware.js +4 -4
- package/dist/core/sessionMiddleware.js.map +1 -1
- package/dist/core/types.d.ts +3 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/validation.js.map +1 -1
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integration.js +1 -1
- package/dist/integration.js.map +1 -1
- package/dist/server.js.map +1 -1
- package/package.json +2 -1
- package/readme.md +40 -1
- package/src/core/config.ts +153 -0
- package/src/core/context.ts +30 -0
- package/src/core/guardMiddleware.ts +131 -0
- package/src/core/logger.ts +31 -0
- package/src/core/matcher.ts +61 -0
- package/src/core/sessionMiddleware.ts +65 -0
- package/src/core/types.ts +147 -0
- package/src/core/validation.ts +137 -0
- package/src/guard.ts +7 -0
- package/src/index.ts +78 -0
- package/src/integration.ts +58 -0
- package/src/middleware.ts +5 -0
- package/src/server.ts +245 -0
package/dist/core/config.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ 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
|
+
globalProtect: boolean;
|
|
12
|
+
exclude: string[];
|
|
13
|
+
debug: boolean;
|
|
11
14
|
}
|
|
12
15
|
export declare function setConfig(userConfig: SessionKitConfig): void;
|
|
13
16
|
export declare function getConfig(): ResolvedConfig;
|
|
@@ -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;AAMpG,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;
|
|
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;AAMpG,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,CAuG5D;AAKD,wBAAgB,SAAS,IAAI,cAAc,CAE1C"}
|
package/dist/core/config.js
CHANGED
|
@@ -8,6 +8,9 @@ const DEFAULT_CONFIG = {
|
|
|
8
8
|
getPermissions: (session) => session?.permissions ?? [],
|
|
9
9
|
check: undefined,
|
|
10
10
|
},
|
|
11
|
+
globalProtect: false,
|
|
12
|
+
exclude: [],
|
|
13
|
+
debug: false,
|
|
11
14
|
};
|
|
12
15
|
let config = { ...DEFAULT_CONFIG };
|
|
13
16
|
function setConfig(userConfig) {
|
|
@@ -40,10 +43,47 @@ function setConfig(userConfig) {
|
|
|
40
43
|
getPermissions: userConfig.access.getPermissions ?? DEFAULT_CONFIG.access.getPermissions,
|
|
41
44
|
check: userConfig.access.check,
|
|
42
45
|
};
|
|
46
|
+
const anyAccess = userConfig.access;
|
|
47
|
+
if (anyAccess.globalProtect !== undefined && userConfig.globalProtect === undefined) {
|
|
48
|
+
newConfig.globalProtect = anyAccess.globalProtect;
|
|
49
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
50
|
+
console.warn('[SessionKit] Deprecation: globalProtect should be at the top level of configuration, not inside "access".');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (anyAccess.exclude !== undefined && userConfig.exclude === undefined) {
|
|
54
|
+
newConfig.exclude = anyAccess.exclude;
|
|
55
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
56
|
+
console.warn('[SessionKit] Deprecation: exclude should be at the top level of configuration, not inside "access".');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (anyAccess.debug !== undefined && userConfig.debug === undefined) {
|
|
60
|
+
newConfig.debug = anyAccess.debug;
|
|
61
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
62
|
+
console.warn('[SessionKit] Deprecation: debug should be at the top level of configuration, not inside "access".');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
43
65
|
}
|
|
44
66
|
newConfig.runWithContext = userConfig.runWithContext;
|
|
45
67
|
newConfig.getContextStore = userConfig.getContextStore;
|
|
46
68
|
newConfig.setContextStore = userConfig.setContextStore;
|
|
69
|
+
if (userConfig.globalProtect !== undefined) {
|
|
70
|
+
newConfig.globalProtect = userConfig.globalProtect;
|
|
71
|
+
}
|
|
72
|
+
if (userConfig.exclude !== undefined) {
|
|
73
|
+
for (const pattern of userConfig.exclude) {
|
|
74
|
+
if (!isValidPattern(pattern)) {
|
|
75
|
+
throw new Error(`[SessionKit] Invalid exclude pattern: "${pattern}". ` +
|
|
76
|
+
`Patterns must start with / and be less than 1000 characters.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
newConfig.exclude = [...userConfig.exclude];
|
|
80
|
+
}
|
|
81
|
+
else if (newConfig.exclude.length === 0) {
|
|
82
|
+
newConfig.exclude = [...DEFAULT_CONFIG.exclude];
|
|
83
|
+
}
|
|
84
|
+
if (userConfig.debug !== undefined) {
|
|
85
|
+
newConfig.debug = userConfig.debug;
|
|
86
|
+
}
|
|
47
87
|
config = Object.freeze(newConfig);
|
|
48
88
|
}
|
|
49
89
|
function getConfig() {
|
package/dist/core/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sources":["../../src/core/config.ts"],"sourcesContent":[null],"names":[],"mappings":";;
|
|
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\";\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 if (process.env.NODE_ENV !== 'production') {\n console.warn('[SessionKit] Deprecation: globalProtect should be at the top level of configuration, not inside \"access\".');\n }\n }\n if (anyAccess.exclude !== undefined && userConfig.exclude === undefined) {\n newConfig.exclude = anyAccess.exclude;\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[SessionKit] Deprecation: exclude should be at the top level of configuration, not inside \"access\".');\n }\n }\n if (anyAccess.debug !== undefined && userConfig.debug === undefined) {\n newConfig.debug = anyAccess.debug;\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[SessionKit] Deprecation: debug should be at the top level of configuration, not inside \"access\".');\n }\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":[],"mappings":"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"}
|
package/dist/core/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sources":["../../src/core/context.ts"],"sourcesContent":[
|
|
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 +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;
|
|
1
|
+
{"version":3,"file":"guardMiddleware.d.ts","sourceRoot":"","sources":["../../src/core/guardMiddleware.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAa,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAyEzD,wBAAgB,qBAAqB,IAAI,iBAAiB,CAqDzD"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getContextStore } from './context.js';
|
|
2
2
|
import { getConfig } from './config.js';
|
|
3
3
|
import { matchesPattern } from './matcher.js';
|
|
4
|
+
import { isValidSessionStructure } from './validation.js';
|
|
4
5
|
|
|
5
6
|
async function checkRule(rule, session) {
|
|
6
7
|
const { access } = getConfig();
|
|
@@ -26,7 +27,7 @@ async function checkRule(rule, session) {
|
|
|
26
27
|
return false;
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
|
-
if (!session) {
|
|
30
|
+
if (!session || !isValidSessionStructure(session)) {
|
|
30
31
|
return false;
|
|
31
32
|
}
|
|
32
33
|
if ("role" in rule) {
|
|
@@ -49,8 +50,8 @@ async function checkRule(rule, session) {
|
|
|
49
50
|
}
|
|
50
51
|
function createGuardMiddleware() {
|
|
51
52
|
return async (context, next) => {
|
|
52
|
-
const { protect, loginPath } = getConfig();
|
|
53
|
-
if (protect.length === 0) {
|
|
53
|
+
const { protect, loginPath, globalProtect, exclude } = getConfig();
|
|
54
|
+
if (protect.length === 0 && !globalProtect) {
|
|
54
55
|
return next();
|
|
55
56
|
}
|
|
56
57
|
let pathname;
|
|
@@ -64,6 +65,17 @@ function createGuardMiddleware() {
|
|
|
64
65
|
const session = sessionContext?.session ?? null;
|
|
65
66
|
const rule = protect.find((r) => matchesPattern(r.pattern, pathname));
|
|
66
67
|
if (!rule) {
|
|
68
|
+
if (globalProtect) {
|
|
69
|
+
if (exclude.some((pattern) => matchesPattern(pattern, pathname))) {
|
|
70
|
+
return next();
|
|
71
|
+
}
|
|
72
|
+
if (pathname === loginPath) {
|
|
73
|
+
return next();
|
|
74
|
+
}
|
|
75
|
+
if (!session || !isValidSessionStructure(session)) {
|
|
76
|
+
return context.redirect(loginPath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
67
79
|
return next();
|
|
68
80
|
}
|
|
69
81
|
const allowed = await checkRule(rule, session);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guardMiddleware.js","sources":["../../src/core/guardMiddleware.ts"],"sourcesContent":[null],"names":[],"mappings":"
|
|
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\";\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 if (process.env.NODE_ENV !== 'production') {\n console.error('[SessionKit] Error in custom access check hook:', error);\n }\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 if (process.env.NODE_ENV !== 'production') {\n console.error('[SessionKit] Error in custom rule allow function:', error);\n }\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 // No rules configured and no global protect - skip\n if (protect.length === 0 && !globalProtect) {\n return next();\n }\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 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 // 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 return next();\n }\n \n // Skip if it's the login page itself (to avoid redirect loops)\n if (pathname === loginPath) {\n return next();\n }\n\n // Require valid session\n if (!session || !isValidSessionStructure(session)) {\n return context.redirect(loginPath);\n }\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 return context.redirect(redirectTo);\n }\n\n return next();\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"AAKA,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAK3D;AAKD,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAK3D;AAKD,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAK1D"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { getConfig } from './config.js';
|
|
2
|
+
|
|
3
|
+
function warn(message, ...args) {
|
|
4
|
+
const { debug } = getConfig();
|
|
5
|
+
if (debug || process.env.NODE_ENV !== 'production') {
|
|
6
|
+
console.warn(`[SessionKit] ${message}`, ...args);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { warn };
|
|
11
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sources":["../../src/core/logger.ts"],"sourcesContent":["import { getConfig } from \"./config\";\n\n/**\n * Log message if debug mode is enabled\n */\nexport function debug(message: string, ...args: any[]): void {\n const { debug } = getConfig();\n if (debug) {\n console.debug(`[SessionKit] ${message}`, ...args);\n }\n}\n\n/**\n * Log error message. Always logs unless in production, but can be forced via debug flag.\n */\nexport function error(message: string, ...args: any[]): void {\n const { debug } = getConfig();\n if (debug || process.env.NODE_ENV !== 'production') {\n console.error(`[SessionKit] ${message}`, ...args);\n }\n}\n\n/**\n * Log warning message. Always logs unless in production, but can be forced via debug flag.\n */\nexport function warn(message: string, ...args: any[]): void {\n const { debug } = getConfig();\n if (debug || process.env.NODE_ENV !== 'production') {\n console.warn(`[SessionKit] ${message}`, ...args);\n }\n}\n"],"names":[],"mappings":";;SAyBgB,IAAI,CAAC,OAAe,EAAE,GAAG,IAAW,EAAA;AAClD,IAAA,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,EAAE;IAC7B,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE;QAClD,OAAO,CAAC,IAAI,CAAC,CAAA,aAAA,EAAgB,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC;IAClD;AACF;;;;"}
|
package/dist/core/matcher.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matcher.js","sources":["../../src/core/matcher.ts"],"sourcesContent":[
|
|
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 +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;
|
|
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,6 +1,7 @@
|
|
|
1
1
|
import { runWithContext } from './context.js';
|
|
2
2
|
import { isValidSessionStructure } from './validation.js';
|
|
3
3
|
import { getConfig } from './config.js';
|
|
4
|
+
import { warn } from './logger.js';
|
|
4
5
|
|
|
5
6
|
const SESSION_KEY = "__session__";
|
|
6
7
|
const sessionMiddleware = async (context, next) => {
|
|
@@ -11,10 +12,9 @@ const sessionMiddleware = async (context, next) => {
|
|
|
11
12
|
session = rawSession;
|
|
12
13
|
}
|
|
13
14
|
else {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
15
|
+
warn('Invalid session structure detected. Session will be ignored. ' +
|
|
16
|
+
'Ensure context.session.set("__session__", ...) has the correct structure. ' +
|
|
17
|
+
'Received: ' + JSON.stringify(rawSession));
|
|
18
18
|
session = null;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessionMiddleware.js","sources":["../../src/core/sessionMiddleware.ts"],"sourcesContent":[null],"names":["defaultRunWithContext"],"mappings":"
|
|
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 {warn} 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 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 if (process.env.NODE_ENV !== 'production') {\n console.error('[SessionKit] 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":["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;AAEH,YAAA,IAAI,CACA,+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,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE;AAC9C,YAAA,OAAO,CAAC,KAAK,CAAC,qEAAqE,CAAC;QACxF;QACA,OAAO,IAAI,EAAE;IACjB;AAEA,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,IAAIA,cAAqB;AAC7D,IAAA,OAAO,MAAM,CAAC,EAAC,OAAO,EAAC,EAAE,MAAM,IAAI,EAAE,CAAC;AAC1C;;;;"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -41,6 +41,9 @@ export interface SessionKitConfig {
|
|
|
41
41
|
runWithContext?: <T>(context: SessionContext, fn: () => T) => T;
|
|
42
42
|
getContextStore?: () => SessionContext | undefined;
|
|
43
43
|
setContextStore?: (context: SessionContext) => void;
|
|
44
|
+
globalProtect?: boolean;
|
|
45
|
+
exclude?: string[];
|
|
46
|
+
debug?: boolean;
|
|
44
47
|
}
|
|
45
48
|
export {};
|
|
46
49
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/core/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":""}
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sources":["../../src/core/validation.ts"],"sourcesContent":[null],"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 // 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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"}
|
package/dist/guard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard.js","sources":["../src/guard.ts"],"sourcesContent":[
|
|
1
|
+
{"version":3,"file":"guard.js","sources":["../src/guard.ts"],"sourcesContent":["// ============================================================================\n// Guard Middleware Entry Point\n// ============================================================================\n\nimport { createGuardMiddleware } from \"./core/guardMiddleware\";\n\nexport const onRequest = createGuardMiddleware();\n"],"names":[],"mappings":";;AAMO,MAAM,SAAS,GAAG,qBAAqB;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,9 @@ interface SessionKitConfig {
|
|
|
43
43
|
runWithContext?: <T>(context: SessionContext, fn: () => T) => T;
|
|
44
44
|
getContextStore?: () => SessionContext | undefined;
|
|
45
45
|
setContextStore?: (context: SessionContext) => void;
|
|
46
|
+
globalProtect?: boolean;
|
|
47
|
+
exclude?: string[];
|
|
48
|
+
debug?: boolean;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
declare function sessionkit(config?: SessionKitConfig): AstroIntegration;
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ function sessionkit(config = {}) {
|
|
|
10
10
|
entrypoint: "astro-sessionkit/middleware",
|
|
11
11
|
order: "pre",
|
|
12
12
|
});
|
|
13
|
-
if (config.protect && config.protect.length > 0) {
|
|
13
|
+
if ((config.protect && config.protect.length > 0) || config.globalProtect) {
|
|
14
14
|
addMiddleware({
|
|
15
15
|
entrypoint: "astro-sessionkit/guard",
|
|
16
16
|
order: "pre",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":[
|
|
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 { 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\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 ((config.protect && config.protect.length > 0) || config.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;IAEjB,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,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,KAAK,MAAM,CAAC,aAAa,EAAE;AACvE,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;;;;"}
|
package/dist/integration.js
CHANGED
|
@@ -9,7 +9,7 @@ export default function sessionKit(config = {}) {
|
|
|
9
9
|
entrypoint: "astro-sessionkit/middleware",
|
|
10
10
|
order: "pre",
|
|
11
11
|
});
|
|
12
|
-
if (config.protect && config.protect.length > 0) {
|
|
12
|
+
if ((config.protect && config.protect.length > 0) || config.globalProtect) {
|
|
13
13
|
addMiddleware({
|
|
14
14
|
entrypoint: "astro-sessionkit/guard",
|
|
15
15
|
order: "pre",
|
package/dist/integration.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAyB1C,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,SAA2B,EAAE;IAE9D,SAAS,CAAC,MAAM,CAAC,CAAC;IAElB,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,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAyB1C,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,SAA2B,EAAE;IAE9D,SAAS,CAAC,MAAM,CAAC,CAAC;IAElB,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,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;oBAC1E,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 { 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\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 ((config.protect && config.protect.length > 0) || config.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"]}
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../src/server.ts"],"sourcesContent":[null],"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} 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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-sessionkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Simple session access and route protection for Astro applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
28
|
+
"src",
|
|
28
29
|
"SECURITY.md",
|
|
29
30
|
"README.md"
|
|
30
31
|
],
|
package/readme.md
CHANGED
|
@@ -6,6 +6,7 @@ Simple session access and route protection for Astro applications.
|
|
|
6
6
|
|
|
7
7
|
- ✅ **Simple API** - Access session data anywhere in your app
|
|
8
8
|
- 🛡️ **Route Protection** - Declarative route guards with roles/permissions
|
|
9
|
+
- 🔒 **Global Protection** - Protect all routes by default with a single flag
|
|
9
10
|
- 🚀 **Type Safe** - Full TypeScript support
|
|
10
11
|
- 🎯 **Flexible** - Works with any session storage (cookies, Redis, DB, etc.)
|
|
11
12
|
- ⚡ **Fast** - Uses AsyncLocalStorage for zero-overhead access
|
|
@@ -44,7 +45,10 @@ export default defineConfig({
|
|
|
44
45
|
pattern: '/premium/**',
|
|
45
46
|
allow: (session) => session?.subscription === 'premium'
|
|
46
47
|
}
|
|
47
|
-
]
|
|
48
|
+
],
|
|
49
|
+
globalProtect: false, // Set to true to protect all routes by default
|
|
50
|
+
exclude: ['/public/**', '/about'], // Routes to ignore if globalProtect is true
|
|
51
|
+
debug: false // Enable debug logging
|
|
48
52
|
})
|
|
49
53
|
]
|
|
50
54
|
});
|
|
@@ -382,6 +386,41 @@ Patterns support glob syntax:
|
|
|
382
386
|
- `/admin/*` - One or more segments (`/admin/users`, `/admin/users/123`)
|
|
383
387
|
- `/admin/**` - Any path under admin (`/admin`, `/admin/users`, `/admin/x/y/z`)
|
|
384
388
|
|
|
389
|
+
### Global Protection
|
|
390
|
+
|
|
391
|
+
You can protect all routes in your application by default using the `globalProtect` option. When enabled, any route that doesn't match an explicit rule in `protect` or isn't listed in `exclude` will require an active session.
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
sessionkit({
|
|
395
|
+
loginPath: '/login',
|
|
396
|
+
globalProtect: true,
|
|
397
|
+
exclude: [
|
|
398
|
+
'/', // Public landing page
|
|
399
|
+
'/login', // Login page (automatically excluded but good to be explicit)
|
|
400
|
+
'/about', // Public about page
|
|
401
|
+
'/public/**', // All public assets/pages
|
|
402
|
+
'/api/health' // Health check endpoint
|
|
403
|
+
]
|
|
404
|
+
})
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
- **If `globalProtect` is `true`**: All routes require authentication unless explicitly excluded.
|
|
408
|
+
- **If `globalProtect` is `false` (default)**: Only routes matching patterns in the `protect` array are guarded.
|
|
409
|
+
- **Exclusion rules**: The `exclude` array accepts the same glob patterns as the `protect` rules.
|
|
410
|
+
- **Auto-exclusion**: The `loginPath` is automatically excluded from global protection to prevent redirect loops.
|
|
411
|
+
|
|
412
|
+
### Debug Mode
|
|
413
|
+
|
|
414
|
+
Enable debug logging to troubleshoot configuration issues or pattern matching:
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
sessionkit({
|
|
418
|
+
debug: true
|
|
419
|
+
})
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
When enabled, SessionKit will log detailed information about matching rules and access decisions to the console.
|
|
423
|
+
|
|
385
424
|
## Session Type
|
|
386
425
|
|
|
387
426
|
The session object must have this shape:
|