@eminuckan/spine 0.3.1 → 0.3.3
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/CHANGELOG.md +12 -0
- package/SECURITY.md +5 -15
- package/dist/access/index.mjs +2 -0
- package/dist/{access-CGrsDMwu.js → access-BHy1v5sr.mjs} +6 -8
- package/dist/access-BHy1v5sr.mjs.map +1 -0
- package/dist/api-client/index.mjs +3 -0
- package/dist/api-client/server.mjs +4 -0
- package/dist/{api-client-CgZUMukO.js → api-client-CB7XZaMg.mjs} +8 -10
- package/dist/api-client-CB7XZaMg.mjs.map +1 -0
- package/dist/auth/auth.server.d.ts +1 -0
- package/dist/auth/auth.server.d.ts.map +1 -1
- package/dist/auth/index.mjs +1 -0
- package/dist/auth/route-protection.server.d.ts.map +1 -1
- package/dist/auth/server.mjs +3 -0
- package/dist/{identity-C5un-xAs.js → identity/index.mjs} +24 -21
- package/dist/identity/index.mjs.map +1 -0
- package/dist/{server-Dxsww9v9.js → identity/server.mjs} +7 -8
- package/dist/identity/server.mjs.map +1 -0
- package/dist/{index.js → index.mjs} +11 -13
- package/dist/{logging-DviiAkLx.js → logging/index.mjs} +22 -24
- package/dist/logging/index.mjs.map +1 -0
- package/dist/{permission-service-CvGpwI0d.js → permission-service-DUyKomU3.mjs} +6 -10
- package/dist/permission-service-DUyKomU3.mjs.map +1 -0
- package/dist/permissions/index.mjs +3 -0
- package/dist/{permissions-_n1hIG7Q.js → permissions-C5dTP9y4.mjs} +23 -15
- package/dist/permissions-C5dTP9y4.mjs.map +1 -0
- package/dist/query-client/index.mjs +2 -0
- package/dist/{query-client-Bh3TyB8W.js → query-client-_xB300gc.mjs} +49 -15
- package/dist/query-client-_xB300gc.mjs.map +1 -0
- package/dist/react-router/{index.js → index.mjs} +11 -13
- package/dist/react-router/server.mjs +17 -0
- package/dist/{redis-session-storage.server-v2VcpFsQ.js → redis-session-storage.server-DGgQcJ9B.mjs} +30 -42
- package/dist/redis-session-storage.server-DGgQcJ9B.mjs.map +1 -0
- package/dist/{server-C96NMFBy.js → server-ByScGRXA.mjs} +39 -36
- package/dist/server-ByScGRXA.mjs.map +1 -0
- package/dist/{auth.server-D5oiz9aF.js → server-CqX2yCa4.mjs} +388 -119
- package/dist/server-CqX2yCa4.mjs.map +1 -0
- package/dist/server.mjs +90 -0
- package/dist/server.mjs.map +1 -0
- package/dist/signalr/index.mjs +2 -0
- package/dist/{signalr-k-70VHKo.js → signalr-C-TA8nEE.mjs} +11 -6
- package/dist/signalr-C-TA8nEE.mjs.map +1 -0
- package/dist/{tenant-DfLemb6n.js → tenant/index.mjs} +19 -12
- package/dist/tenant/index.mjs.map +1 -0
- package/dist/{server-Ca-3DJf2.js → tenant/server.mjs} +12 -12
- package/dist/tenant/server.mjs.map +1 -0
- package/dist/{types-BaaE6wjY.js → types-DqCXinAk.mjs} +3 -3
- package/dist/types-DqCXinAk.mjs.map +1 -0
- package/docs/codex-for-oss-application.md +4 -0
- package/package.json +33 -21
- package/dist/access/index.js +0 -4
- package/dist/access-CGrsDMwu.js.map +0 -1
- package/dist/api-client/index.js +0 -5
- package/dist/api-client/server.js +0 -6
- package/dist/api-client-CgZUMukO.js.map +0 -1
- package/dist/auth/index.js +0 -0
- package/dist/auth/server.js +0 -6
- package/dist/auth.server-D5oiz9aF.js.map +0 -1
- package/dist/auth.server-lC94lmlx.js +0 -5
- package/dist/identity/index.js +0 -3
- package/dist/identity/server.js +0 -5
- package/dist/identity-C5un-xAs.js.map +0 -1
- package/dist/logging/index.js +0 -3
- package/dist/logging-DviiAkLx.js.map +0 -1
- package/dist/permission-service-CvGpwI0d.js.map +0 -1
- package/dist/permissions/index.js +0 -4
- package/dist/permissions-_n1hIG7Q.js.map +0 -1
- package/dist/query-client/index.js +0 -3
- package/dist/query-client-Bh3TyB8W.js.map +0 -1
- package/dist/react-router/server.js +0 -20
- package/dist/redis-session-storage.server-v2VcpFsQ.js.map +0 -1
- package/dist/server-B6PCxc-T.js +0 -304
- package/dist/server-B6PCxc-T.js.map +0 -1
- package/dist/server-C96NMFBy.js.map +0 -1
- package/dist/server-Ca-3DJf2.js.map +0 -1
- package/dist/server-Dxsww9v9.js.map +0 -1
- package/dist/server-hGrElJMG.js +0 -78
- package/dist/server-hGrElJMG.js.map +0 -1
- package/dist/server.js +0 -20
- package/dist/signalr/index.js +0 -3
- package/dist/signalr-k-70VHKo.js.map +0 -1
- package/dist/src-B9rDUgSb.js +0 -0
- package/dist/tenant/index.js +0 -3
- package/dist/tenant/server.js +0 -5
- package/dist/tenant-DfLemb6n.js.map +0 -1
- package/dist/types-BaaE6wjY.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/logging/types.ts","../../src/logging/config.ts","../../src/logging/logger.ts"],"sourcesContent":["/**\n * Logging Types\n */\n\nexport enum LogLevel {\n DEBUG = 'debug',\n INFO = 'info',\n WARN = 'warn',\n ERROR = 'error',\n}\n\nexport interface LogContext {\n [key: string]: unknown;\n}\n\nexport interface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n context?: LogContext;\n error?: Error;\n}\n\nexport interface LoggerConfig {\n minLevel: LogLevel;\n enableConsole: boolean;\n enableRemote: boolean;\n remoteEndpoint?: string;\n serviceName?: string;\n}\n","/**\n * Logger Configuration\n */\n\nimport { LogLevel, type LoggerConfig } from './types';\n\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n [LogLevel.DEBUG]: 0,\n [LogLevel.INFO]: 1,\n [LogLevel.WARN]: 2,\n [LogLevel.ERROR]: 3,\n};\n\nexport const defaultLoggerConfig: LoggerConfig = {\n minLevel: process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG,\n enableConsole: true,\n enableRemote: false,\n serviceName: 'spine',\n};\n","/**\n * Logger Service\n * \n * Centralized logging implementation with structured logging format.\n */\n\nimport { LogLevel, type LogContext, type LogEntry, type LoggerConfig } from './types';\nimport { defaultLoggerConfig, LOG_LEVEL_PRIORITY } from './config';\n\nexport class Logger {\n private config: LoggerConfig;\n\n constructor(config?: Partial<LoggerConfig>) {\n this.config = {\n ...defaultLoggerConfig,\n ...config,\n };\n }\n\n debug(message: string, context?: LogContext): void {\n this.log(LogLevel.DEBUG, message, context);\n }\n\n info(message: string, context?: LogContext): void {\n this.log(LogLevel.INFO, message, context);\n }\n\n warn(message: string, context?: LogContext): void {\n this.log(LogLevel.WARN, message, context);\n }\n\n error(message: string, error?: Error, context?: LogContext): void {\n this.log(LogLevel.ERROR, message, context, error);\n }\n\n private log(\n level: LogLevel,\n message: string,\n context?: LogContext,\n error?: Error\n ): void {\n if (!this.shouldLog(level)) {\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n context,\n error,\n };\n\n if (this.config.enableConsole) {\n this.logToConsole(entry);\n }\n\n if (this.config.enableRemote && this.config.remoteEndpoint) {\n this.sendToRemote(entry).catch((err) => {\n console.error('Failed to send log to remote endpoint:', err);\n });\n }\n }\n\n private shouldLog(level: LogLevel): boolean {\n return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.config.minLevel];\n }\n\n private logToConsole(entry: LogEntry): void {\n const formatted = this.formatEntry(entry);\n\n switch (entry.level) {\n case LogLevel.DEBUG:\n console.debug(formatted);\n break;\n case LogLevel.INFO:\n console.info(formatted);\n break;\n case LogLevel.WARN:\n console.warn(formatted);\n break;\n case LogLevel.ERROR:\n console.error(formatted);\n if (entry.error) {\n console.error(entry.error);\n }\n break;\n }\n }\n\n private formatEntry(entry: LogEntry): string {\n const parts = [\n `[${entry.timestamp}]`,\n `[${entry.level.toUpperCase()}]`,\n entry.message,\n ];\n\n if (entry.context && Object.keys(entry.context).length > 0) {\n parts.push(JSON.stringify(entry.context));\n }\n\n return parts.join(' ');\n }\n\n private async sendToRemote(entry: LogEntry): Promise<void> {\n if (!this.config.remoteEndpoint) return;\n\n await fetch(this.config.remoteEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...entry,\n serviceName: this.config.serviceName,\n error: entry.error\n ? {\n name: entry.error.name,\n message: entry.error.message,\n stack: entry.error.stack,\n }\n : undefined,\n }),\n });\n }\n}\n\n// Default logger instance\nexport const logger = new Logger();\n\n// Create logger with custom config\nexport function createLogger(config?: Partial<LoggerConfig>): Logger {\n return new Logger(config);\n}\n"],"mappings":";;;;AAIA,IAAY,WAAL,yBAAA,UAAA;CACL,SAAA,WAAA;CACA,SAAA,UAAA;CACA,SAAA,UAAA;CACA,SAAA,WAAA;;AACF,EAAA,CAAA,CAAA;;;ACHA,MAAa,qBAA+C;YACxC;WACD;WACA;YACC;AACpB;AAEA,MAAa,sBAAoC;CAC/C,UAAU,QAAQ,IAAI,aAAa,eAAA,SAAA;CACnC,eAAe;CACf,cAAc;CACd,aAAa;AACf;;;ACTA,IAAa,SAAb,MAAoB;CAClB;CAEA,YAAY,QAAgC;EAC1C,KAAK,SAAS;GACZ,GAAG;GACH,GAAG;EACL;CACF;CAEA,MAAM,SAAiB,SAA4B;EACjD,KAAK,IAAA,SAAoB,SAAS,OAAO;CAC3C;CAEA,KAAK,SAAiB,SAA4B;EAChD,KAAK,IAAA,QAAmB,SAAS,OAAO;CAC1C;CAEA,KAAK,SAAiB,SAA4B;EAChD,KAAK,IAAA,QAAmB,SAAS,OAAO;CAC1C;CAEA,MAAM,SAAiB,OAAe,SAA4B;EAChE,KAAK,IAAA,SAAoB,SAAS,SAAS,KAAK;CAClD;CAEA,IACE,OACA,SACA,SACA,OACM;EACN,IAAI,CAAC,KAAK,UAAU,KAAK,GACvB;EAGF,MAAM,QAAkB;GACtB,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;GAClC;GACA;GACA;GACA;EACF;EAEA,IAAI,KAAK,OAAO,eACd,KAAK,aAAa,KAAK;EAGzB,IAAI,KAAK,OAAO,gBAAgB,KAAK,OAAO,gBAC1C,KAAK,aAAa,KAAK,CAAC,CAAC,OAAO,QAAQ;GACtC,QAAQ,MAAM,0CAA0C,GAAG;EAC7D,CAAC;CAEL;CAEA,UAAkB,OAA0B;EAC1C,OAAO,mBAAmB,UAAU,mBAAmB,KAAK,OAAO;CACrE;CAEA,aAAqB,OAAuB;EAC1C,MAAM,YAAY,KAAK,YAAY,KAAK;EAExC,QAAQ,MAAM,OAAd;GACE,KAAA;IACE,QAAQ,MAAM,SAAS;IACvB;GACF,KAAA;IACE,QAAQ,KAAK,SAAS;IACtB;GACF,KAAA;IACE,QAAQ,KAAK,SAAS;IACtB;GACF,KAAA;IACE,QAAQ,MAAM,SAAS;IACvB,IAAI,MAAM,OACR,QAAQ,MAAM,MAAM,KAAK;IAE3B;EACJ;CACF;CAEA,YAAoB,OAAyB;EAC3C,MAAM,QAAQ;GACZ,IAAI,MAAM,UAAU;GACpB,IAAI,MAAM,MAAM,YAAY,EAAE;GAC9B,MAAM;EACR;EAEA,IAAI,MAAM,WAAW,OAAO,KAAK,MAAM,OAAO,CAAC,CAAC,SAAS,GACvD,MAAM,KAAK,KAAK,UAAU,MAAM,OAAO,CAAC;EAG1C,OAAO,MAAM,KAAK,GAAG;CACvB;CAEA,MAAc,aAAa,OAAgC;EACzD,IAAI,CAAC,KAAK,OAAO,gBAAgB;EAEjC,MAAM,MAAM,KAAK,OAAO,gBAAgB;GACtC,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU;IACnB,GAAG;IACH,aAAa,KAAK,OAAO;IACzB,OAAO,MAAM,QACT;KACE,MAAM,MAAM,MAAM;KAClB,SAAS,MAAM,MAAM;KACrB,OAAO,MAAM,MAAM;IACrB,IACA,KAAA;GACN,CAAC;EACH,CAAC;CACH;AACF;AAGA,MAAa,SAAS,IAAI,OAAO;AAGjC,SAAgB,aAAa,QAAwC;CACnE,OAAO,IAAI,OAAO,MAAM;AAC1B"}
|
|
@@ -24,14 +24,10 @@ var PermissionChecker = class {
|
|
|
24
24
|
const { operator = "OR", wildcard = false } = options;
|
|
25
25
|
const matchedPermissions = [];
|
|
26
26
|
const missingPermissions = [];
|
|
27
|
-
for (const permission of permissions)
|
|
28
|
-
|
|
29
|
-
if (hasIt) matchedPermissions.push(permission);
|
|
30
|
-
else missingPermissions.push(permission);
|
|
31
|
-
}
|
|
32
|
-
const hasPermission = operator === "AND" ? missingPermissions.length === 0 : matchedPermissions.length > 0;
|
|
27
|
+
for (const permission of permissions) if (wildcard ? this.hasWildcardPermission(permission) : this.hasPermission(permission)) matchedPermissions.push(permission);
|
|
28
|
+
else missingPermissions.push(permission);
|
|
33
29
|
return {
|
|
34
|
-
hasPermission,
|
|
30
|
+
hasPermission: operator === "AND" ? missingPermissions.length === 0 : matchedPermissions.length > 0,
|
|
35
31
|
matchedPermissions,
|
|
36
32
|
missingPermissions
|
|
37
33
|
};
|
|
@@ -89,7 +85,7 @@ var PermissionChecker = class {
|
|
|
89
85
|
return this.permissionArray.length;
|
|
90
86
|
}
|
|
91
87
|
};
|
|
92
|
-
|
|
93
88
|
//#endregion
|
|
94
|
-
export { PermissionChecker };
|
|
95
|
-
|
|
89
|
+
export { PermissionChecker as t };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=permission-service-DUyKomU3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-service-DUyKomU3.mjs","names":[],"sources":["../src/permissions/permission-service.ts"],"sourcesContent":["/**\n * Permission Service\n * \n * Core permission checking logic.\n */\n\nimport type {\n PermissionCode,\n PermissionModule,\n PermissionCheckOptions,\n PermissionCheckResult,\n FieldPermissionResult,\n} from './types';\n\n/**\n * Permission Checker Class\n * \n * Provides methods for checking user permissions.\n */\nexport class PermissionChecker {\n private permissions: Set<PermissionCode>;\n private permissionArray: PermissionCode[];\n\n constructor(permissions: PermissionCode[]) {\n this.permissionArray = permissions;\n this.permissions = new Set(permissions);\n }\n\n /**\n * Check if user has a single permission\n */\n hasPermission(permission: PermissionCode): boolean {\n return this.permissions.has(permission);\n }\n\n /**\n * Check multiple permissions with AND/OR logic\n */\n hasPermissions(\n permissions: PermissionCode[],\n options: PermissionCheckOptions = {}\n ): PermissionCheckResult {\n const { operator = 'OR', wildcard = false } = options;\n\n const matchedPermissions: PermissionCode[] = [];\n const missingPermissions: PermissionCode[] = [];\n\n for (const permission of permissions) {\n const hasIt = wildcard\n ? this.hasWildcardPermission(permission)\n : this.hasPermission(permission);\n\n if (hasIt) {\n matchedPermissions.push(permission);\n } else {\n missingPermissions.push(permission);\n }\n }\n\n const hasPermission =\n operator === 'AND'\n ? missingPermissions.length === 0\n : matchedPermissions.length > 0;\n\n return {\n hasPermission,\n matchedPermissions,\n missingPermissions,\n };\n }\n\n /**\n * Check wildcard permission pattern\n */\n private hasWildcardPermission(pattern: PermissionCode): boolean {\n if (!pattern.includes('*')) {\n return this.hasPermission(pattern);\n }\n\n const regex = new RegExp(\n '^' + pattern.replace(/\\./g, '\\\\.').replace(/\\*/g, '.*') + '$'\n );\n\n return this.permissionArray.some((p) => regex.test(p));\n }\n\n /**\n * Check if user has any permission in a module\n */\n hasModulePermission(module: PermissionModule): boolean {\n const prefix = `${module}.`;\n return this.permissionArray.some((p) => p.startsWith(prefix));\n }\n\n /**\n * Get field-level permissions for an entity\n */\n getFieldPermissions(\n module: PermissionModule,\n entity: string\n ): FieldPermissionResult {\n const base = `${module}.${entity}`;\n\n return {\n canView: this.hasPermission(`${base}.View`),\n canEdit: this.hasPermission(`${base}.Edit`),\n canCreate: this.hasPermission(`${base}.Create`),\n canDelete: this.hasPermission(`${base}.Delete`),\n };\n }\n\n /**\n * Get all permissions\n */\n getAllPermissions(): readonly PermissionCode[] {\n return Object.freeze([...this.permissionArray]);\n }\n\n /**\n * Get permissions for a specific module\n */\n getModulePermissions(module: PermissionModule): PermissionCode[] {\n const prefix = `${module}.`;\n return this.permissionArray.filter((p) => p.startsWith(prefix));\n }\n\n /**\n * Check if user has any permissions\n */\n hasAnyPermissions(): boolean {\n return this.permissionArray.length > 0;\n }\n\n /**\n * Get permission count\n */\n getPermissionCount(): number {\n return this.permissionArray.length;\n }\n}\n"],"mappings":";;;;;;AAmBA,IAAa,oBAAb,MAA+B;CAC7B;CACA;CAEA,YAAY,aAA+B;EACzC,KAAK,kBAAkB;EACvB,KAAK,cAAc,IAAI,IAAI,WAAW;CACxC;;;;CAKA,cAAc,YAAqC;EACjD,OAAO,KAAK,YAAY,IAAI,UAAU;CACxC;;;;CAKA,eACE,aACA,UAAkC,CAAC,GACZ;EACvB,MAAM,EAAE,WAAW,MAAM,WAAW,UAAU;EAE9C,MAAM,qBAAuC,CAAC;EAC9C,MAAM,qBAAuC,CAAC;EAE9C,KAAK,MAAM,cAAc,aAKvB,IAJc,WACV,KAAK,sBAAsB,UAAU,IACrC,KAAK,cAAc,UAAU,GAG/B,mBAAmB,KAAK,UAAU;OAElC,mBAAmB,KAAK,UAAU;EAStC,OAAO;GACL,eALA,aAAa,QACT,mBAAmB,WAAW,IAC9B,mBAAmB,SAAS;GAIhC;GACA;EACF;CACF;;;;CAKA,sBAA8B,SAAkC;EAC9D,IAAI,CAAC,QAAQ,SAAS,GAAG,GACvB,OAAO,KAAK,cAAc,OAAO;EAGnC,MAAM,QAAQ,IAAI,OAChB,MAAM,QAAQ,QAAQ,OAAO,KAAK,CAAC,CAAC,QAAQ,OAAO,IAAI,IAAI,GAC7D;EAEA,OAAO,KAAK,gBAAgB,MAAM,MAAM,MAAM,KAAK,CAAC,CAAC;CACvD;;;;CAKA,oBAAoB,QAAmC;EACrD,MAAM,SAAS,GAAG,OAAO;EACzB,OAAO,KAAK,gBAAgB,MAAM,MAAM,EAAE,WAAW,MAAM,CAAC;CAC9D;;;;CAKA,oBACE,QACA,QACuB;EACvB,MAAM,OAAO,GAAG,OAAO,GAAG;EAE1B,OAAO;GACL,SAAS,KAAK,cAAc,GAAG,KAAK,MAAM;GAC1C,SAAS,KAAK,cAAc,GAAG,KAAK,MAAM;GAC1C,WAAW,KAAK,cAAc,GAAG,KAAK,QAAQ;GAC9C,WAAW,KAAK,cAAc,GAAG,KAAK,QAAQ;EAChD;CACF;;;;CAKA,oBAA+C;EAC7C,OAAO,OAAO,OAAO,CAAC,GAAG,KAAK,eAAe,CAAC;CAChD;;;;CAKA,qBAAqB,QAA4C;EAC/D,MAAM,SAAS,GAAG,OAAO;EACzB,OAAO,KAAK,gBAAgB,QAAQ,MAAM,EAAE,WAAW,MAAM,CAAC;CAChE;;;;CAKA,oBAA6B;EAC3B,OAAO,KAAK,gBAAgB,SAAS;CACvC;;;;CAKA,qBAA6B;EAC3B,OAAO,KAAK,gBAAgB;CAC9B;AACF"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { t as PermissionChecker } from "../permission-service-DUyKomU3.mjs";
|
|
2
|
+
import { _ as initializePermissions, a as ProtectedSection, c as useHasPermission, d as usePermission, f as usePermissionChecker, g as getPermissions, h as getPermissionChecker, i as ProtectedLink, l as useHasPermissions, m as PermissionInitializer, n as ProtectedAction, o as useAllPermissions, p as usePermissions, r as ProtectedButton, s as useHasModulePermission, t as PERMISSION_MODULES, u as useModulePermission, v as usePermissionStore } from "../permissions-C5dTP9y4.mjs";
|
|
3
|
+
export { PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions };
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { PermissionChecker } from "./permission-service-
|
|
1
|
+
import { t as PermissionChecker } from "./permission-service-DUyKomU3.mjs";
|
|
2
2
|
import { create } from "zustand";
|
|
3
3
|
import { useCallback, useEffect, useMemo } from "react";
|
|
4
4
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
5
|
-
|
|
6
5
|
//#region src/permissions/permission-store.ts
|
|
7
6
|
/**
|
|
7
|
+
* Permission Store (Zustand)
|
|
8
|
+
*
|
|
9
|
+
* State management for permissions.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
8
12
|
* Selectors for focused store reads
|
|
9
13
|
*/
|
|
10
14
|
const selectChecker = (state) => state.checker;
|
|
@@ -53,10 +57,14 @@ function getPermissionChecker() {
|
|
|
53
57
|
function getPermissions() {
|
|
54
58
|
return usePermissionStore.getState().permissions;
|
|
55
59
|
}
|
|
56
|
-
|
|
57
60
|
//#endregion
|
|
58
61
|
//#region src/permissions/permission-initializer.tsx
|
|
59
62
|
/**
|
|
63
|
+
* Permission Initializer Component
|
|
64
|
+
*
|
|
65
|
+
* Initializes permission store from identity context.
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
60
68
|
* Component that initializes permissions from props
|
|
61
69
|
*/
|
|
62
70
|
function PermissionInitializer({ permissions, isLoading = false, children }) {
|
|
@@ -65,10 +73,14 @@ function PermissionInitializer({ permissions, isLoading = false, children }) {
|
|
|
65
73
|
}, [permissions, isLoading]);
|
|
66
74
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
67
75
|
}
|
|
68
|
-
|
|
69
76
|
//#endregion
|
|
70
77
|
//#region src/permissions/hooks.ts
|
|
71
78
|
/**
|
|
79
|
+
* Permission Hooks
|
|
80
|
+
*
|
|
81
|
+
* React hooks for permission checks.
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
72
84
|
* Hook for checking a single permission
|
|
73
85
|
*/
|
|
74
86
|
function usePermission(permission) {
|
|
@@ -119,15 +131,13 @@ function useAllPermissions() {
|
|
|
119
131
|
const checker = usePermissionStore(selectChecker);
|
|
120
132
|
return useMemo(() => checker.getAllPermissions(), [checker]);
|
|
121
133
|
}
|
|
122
|
-
|
|
123
134
|
//#endregion
|
|
124
135
|
//#region src/permissions/components.tsx
|
|
125
136
|
/**
|
|
126
137
|
* Button that only renders if user has the required permission
|
|
127
138
|
*/
|
|
128
|
-
function ProtectedButton({ permission, fallback = null, children
|
|
129
|
-
|
|
130
|
-
if (!hasPermission) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
139
|
+
function ProtectedButton({ permission, fallback = null, children, ...props }) {
|
|
140
|
+
if (!usePermission(permission)) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
131
141
|
return /* @__PURE__ */ jsx("button", {
|
|
132
142
|
...props,
|
|
133
143
|
children
|
|
@@ -136,9 +146,8 @@ function ProtectedButton({ permission, fallback = null, children,...props }) {
|
|
|
136
146
|
/**
|
|
137
147
|
* Link that only renders if user has the required permission
|
|
138
148
|
*/
|
|
139
|
-
function ProtectedLink({ permission, fallback = null, children
|
|
140
|
-
|
|
141
|
-
if (!hasPermission) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
149
|
+
function ProtectedLink({ permission, fallback = null, children, ...props }) {
|
|
150
|
+
if (!usePermission(permission)) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
142
151
|
return /* @__PURE__ */ jsx("a", {
|
|
143
152
|
...props,
|
|
144
153
|
children
|
|
@@ -167,7 +176,6 @@ function ProtectedAction({ permission, onAction, children }) {
|
|
|
167
176
|
disabled: !hasPermission
|
|
168
177
|
}) });
|
|
169
178
|
}
|
|
170
|
-
|
|
171
179
|
//#endregion
|
|
172
180
|
//#region src/permissions/constants.ts
|
|
173
181
|
/**
|
|
@@ -177,7 +185,7 @@ function ProtectedAction({ permission, onAction, children }) {
|
|
|
177
185
|
* module constants in the consuming app instead.
|
|
178
186
|
*/
|
|
179
187
|
const PERMISSION_MODULES = [];
|
|
180
|
-
|
|
181
188
|
//#endregion
|
|
182
|
-
export {
|
|
183
|
-
|
|
189
|
+
export { initializePermissions as _, ProtectedSection as a, useHasPermission as c, usePermission as d, usePermissionChecker as f, getPermissions as g, getPermissionChecker as h, ProtectedLink as i, useHasPermissions as l, PermissionInitializer as m, ProtectedAction as n, useAllPermissions as o, usePermissions as p, ProtectedButton as r, useHasModulePermission as s, PERMISSION_MODULES as t, useModulePermission as u, usePermissionStore as v };
|
|
190
|
+
|
|
191
|
+
//# sourceMappingURL=permissions-C5dTP9y4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions-C5dTP9y4.mjs","names":[],"sources":["../src/permissions/permission-store.ts","../src/permissions/permission-initializer.tsx","../src/permissions/hooks.ts","../src/permissions/components.tsx","../src/permissions/constants.ts"],"sourcesContent":["/**\n * Permission Store (Zustand)\n * \n * State management for permissions.\n */\n\nimport { create } from 'zustand';\nimport { PermissionChecker } from './permission-service';\nimport type { PermissionCode } from './types';\n\n/**\n * Permission store state interface\n */\ninterface PermissionState {\n checker: PermissionChecker;\n permissions: PermissionCode[];\n isLoading: boolean;\n setPermissions: (permissions: PermissionCode[]) => void;\n setLoading: (isLoading: boolean) => void;\n reset: () => void;\n}\n\n/**\n * Selectors for focused store reads\n */\nexport const selectChecker = (state: PermissionState) => state.checker;\nexport const selectPermissions = (state: PermissionState) => state.permissions;\nexport const selectIsLoading = (state: PermissionState) => state.isLoading;\n\n/**\n * Initial state\n */\nconst initialState = {\n checker: new PermissionChecker([]),\n permissions: [] as PermissionCode[],\n isLoading: false,\n};\n\n/**\n * Permission store\n */\nexport const usePermissionStore = create<PermissionState>((set) => ({\n ...initialState,\n\n setPermissions: (permissions: PermissionCode[]) => {\n set({\n permissions,\n checker: new PermissionChecker(permissions),\n });\n },\n\n setLoading: (isLoading: boolean) => {\n set({ isLoading });\n },\n\n reset: () => {\n set(initialState);\n },\n}));\n\n/**\n * Initialize permission store\n */\nexport function initializePermissions(\n permissions: PermissionCode[],\n isLoading: boolean = false\n): void {\n usePermissionStore.getState().setPermissions(permissions);\n usePermissionStore.getState().setLoading(isLoading);\n}\n\n/**\n * Get permission checker outside React components\n */\nexport function getPermissionChecker(): PermissionChecker {\n return usePermissionStore.getState().checker;\n}\n\n/**\n * Get all permissions outside React components\n */\nexport function getPermissions(): PermissionCode[] {\n return usePermissionStore.getState().permissions;\n}\n","/**\n * Permission Initializer Component\n * \n * Initializes permission store from identity context.\n */\n\nimport { useEffect, type ReactNode } from 'react';\nimport { initializePermissions } from './permission-store';\nimport type { PermissionCode } from './types';\n\ninterface PermissionInitializerProps {\n permissions: PermissionCode[];\n isLoading?: boolean;\n children: ReactNode;\n}\n\n/**\n * Component that initializes permissions from props\n */\nexport function PermissionInitializer({\n permissions,\n isLoading = false,\n children,\n}: PermissionInitializerProps) {\n useEffect(() => {\n initializePermissions(permissions, isLoading);\n }, [permissions, isLoading]);\n\n return <>{children}</>;\n}\n","/**\n * Permission Hooks\n * \n * React hooks for permission checks.\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { usePermissionStore, selectChecker } from './permission-store';\nimport type { PermissionCode, PermissionCheckOptions, PermissionModule } from './types';\n\n/**\n * Hook for checking a single permission\n */\nexport function usePermission(permission: PermissionCode): boolean {\n const checker = usePermissionStore(selectChecker);\n return useMemo(() => checker.hasPermission(permission), [checker, permission]);\n}\n\n/**\n * Alias for usePermission\n */\nexport const useHasPermission = usePermission;\n\n/**\n * Hook for checking multiple permissions\n */\nexport function usePermissions(\n permissions: PermissionCode[],\n options?: PermissionCheckOptions\n) {\n const checker = usePermissionStore(selectChecker);\n return useMemo(\n () => checker.hasPermissions(permissions, options),\n [checker, permissions, options]\n );\n}\n\n/**\n * Alias for usePermissions\n */\nexport const useHasPermissions = usePermissions;\n\n/**\n * Hook for checking module-level permissions\n */\nexport function useModulePermission(module: PermissionModule): boolean {\n const checker = usePermissionStore(selectChecker);\n return useMemo(() => checker.hasModulePermission(module), [checker, module]);\n}\n\n/**\n * Alias for useModulePermission\n */\nexport const useHasModulePermission = useModulePermission;\n\n/**\n * Hook for getting a memoized permission checker function\n */\nexport function usePermissionChecker() {\n const checker = usePermissionStore(selectChecker);\n return useCallback(\n (permission: PermissionCode) => checker.hasPermission(permission),\n [checker]\n );\n}\n\n/**\n * Hook for getting all user permissions\n */\nexport function useAllPermissions(): readonly PermissionCode[] {\n const checker = usePermissionStore(selectChecker);\n return useMemo(() => checker.getAllPermissions(), [checker]);\n}\n\n/**\n * Hook for field-level permissions\n */\nexport function useFieldPermissions(module: PermissionModule, entity: string) {\n const checker = usePermissionStore(selectChecker);\n return useMemo(\n () => checker.getFieldPermissions(module, entity),\n [checker, module, entity]\n );\n}\n\n/**\n * Hook for permission loading state\n */\nexport function usePermissionLoading(): boolean {\n return usePermissionStore((state) => state.isLoading);\n}\n","/**\n * Permission Components\n * \n * Protected UI components that conditionally render based on permissions.\n */\n\nimport React, { type ReactNode, type ComponentProps } from 'react';\nimport { usePermission, usePermissions } from './hooks';\nimport type { PermissionCode, PermissionCheckOptions } from './types';\n\n// ============================================================================\n// ProtectedButton\n// ============================================================================\n\ninterface ProtectedButtonProps extends ComponentProps<'button'> {\n permission: PermissionCode;\n fallback?: ReactNode;\n}\n\n/**\n * Button that only renders if user has the required permission\n */\nexport function ProtectedButton({\n permission,\n fallback = null,\n children,\n ...props\n}: ProtectedButtonProps) {\n const hasPermission = usePermission(permission);\n\n if (!hasPermission) {\n return <>{fallback}</>;\n }\n\n return <button {...props}>{children}</button>;\n}\n\n// ============================================================================\n// ProtectedLink\n// ============================================================================\n\ninterface ProtectedLinkProps extends ComponentProps<'a'> {\n permission: PermissionCode;\n fallback?: ReactNode;\n}\n\n/**\n * Link that only renders if user has the required permission\n */\nexport function ProtectedLink({\n permission,\n fallback = null,\n children,\n ...props\n}: ProtectedLinkProps) {\n const hasPermission = usePermission(permission);\n\n if (!hasPermission) {\n return <>{fallback}</>;\n }\n\n return <a {...props}>{children}</a>;\n}\n\n// ============================================================================\n// ProtectedSection\n// ============================================================================\n\ninterface ProtectedSectionProps {\n permission?: PermissionCode;\n permissions?: PermissionCode[];\n options?: PermissionCheckOptions;\n fallback?: ReactNode;\n children: ReactNode;\n}\n\n/**\n * Section that only renders if user has the required permission(s)\n */\nexport function ProtectedSection({\n permission,\n permissions,\n options,\n fallback = null,\n children,\n}: ProtectedSectionProps) {\n // Single permission check\n const singlePermissionResult = usePermission(permission || '');\n \n // Multiple permissions check\n const multiplePermissionsResult = usePermissions(permissions || [], options);\n\n // Determine if we should render\n let hasPermission = false;\n \n if (permission) {\n hasPermission = singlePermissionResult;\n } else if (permissions && permissions.length > 0) {\n hasPermission = multiplePermissionsResult.hasPermission;\n } else {\n // No permission specified, render by default\n hasPermission = true;\n }\n\n if (!hasPermission) {\n return <>{fallback}</>;\n }\n\n return <>{children}</>;\n}\n\n// ============================================================================\n// ProtectedAction\n// ============================================================================\n\ninterface ProtectedActionProps {\n permission: PermissionCode;\n onAction: () => void;\n children: (props: { onClick: () => void; disabled: boolean }) => ReactNode;\n}\n\n/**\n * Render prop component for protected actions\n */\nexport function ProtectedAction({\n permission,\n onAction,\n children,\n}: ProtectedActionProps) {\n const hasPermission = usePermission(permission);\n\n return (\n <>\n {children({\n onClick: hasPermission ? onAction : () => {},\n disabled: !hasPermission,\n })}\n </>\n );\n}\n","/**\n * Permission Constants\n */\n\nimport type { PermissionModule } from './types';\n\n/**\n * Available permission modules\n *\n * @deprecated Spine does not own an application's permission taxonomy. Define\n * module constants in the consuming app instead.\n */\nexport const PERMISSION_MODULES: PermissionModule[] = [];\n"],"mappings":";;;;;;;;;;;;;AAyBA,MAAa,iBAAiB,UAA2B,MAAM;;;;AAO/D,MAAM,eAAe;CACnB,SAAS,IAAI,kBAAkB,CAAC,CAAC;CACjC,aAAa,CAAC;CACd,WAAW;AACb;;;;AAKA,MAAa,qBAAqB,QAAyB,SAAS;CAClE,GAAG;CAEH,iBAAiB,gBAAkC;EACjD,IAAI;GACF;GACA,SAAS,IAAI,kBAAkB,WAAW;EAC5C,CAAC;CACH;CAEA,aAAa,cAAuB;EAClC,IAAI,EAAE,UAAU,CAAC;CACnB;CAEA,aAAa;EACX,IAAI,YAAY;CAClB;AACF,EAAE;;;;AAKF,SAAgB,sBACd,aACA,YAAqB,OACf;CACN,mBAAmB,SAAS,CAAC,CAAC,eAAe,WAAW;CACxD,mBAAmB,SAAS,CAAC,CAAC,WAAW,SAAS;AACpD;;;;AAKA,SAAgB,uBAA0C;CACxD,OAAO,mBAAmB,SAAS,CAAC,CAAC;AACvC;;;;AAKA,SAAgB,iBAAmC;CACjD,OAAO,mBAAmB,SAAS,CAAC,CAAC;AACvC;;;;;;;;;;;AChEA,SAAgB,sBAAsB,EACpC,aACA,YAAY,OACZ,YAC6B;CAC7B,gBAAgB;EACd,sBAAsB,aAAa,SAAS;CAC9C,GAAG,CAAC,aAAa,SAAS,CAAC;CAE3B,OAAO,oBAAA,UAAA,EAAG,SAAW,CAAA;AACvB;;;;;;;;;;;AChBA,SAAgB,cAAc,YAAqC;CACjE,MAAM,UAAU,mBAAmB,aAAa;CAChD,OAAO,cAAc,QAAQ,cAAc,UAAU,GAAG,CAAC,SAAS,UAAU,CAAC;AAC/E;;;;AAKA,MAAa,mBAAmB;;;;AAKhC,SAAgB,eACd,aACA,SACA;CACA,MAAM,UAAU,mBAAmB,aAAa;CAChD,OAAO,cACC,QAAQ,eAAe,aAAa,OAAO,GACjD;EAAC;EAAS;EAAa;CAAO,CAChC;AACF;;;;AAKA,MAAa,oBAAoB;;;;AAKjC,SAAgB,oBAAoB,QAAmC;CACrE,MAAM,UAAU,mBAAmB,aAAa;CAChD,OAAO,cAAc,QAAQ,oBAAoB,MAAM,GAAG,CAAC,SAAS,MAAM,CAAC;AAC7E;;;;AAKA,MAAa,yBAAyB;;;;AAKtC,SAAgB,uBAAuB;CACrC,MAAM,UAAU,mBAAmB,aAAa;CAChD,OAAO,aACJ,eAA+B,QAAQ,cAAc,UAAU,GAChE,CAAC,OAAO,CACV;AACF;;;;AAKA,SAAgB,oBAA+C;CAC7D,MAAM,UAAU,mBAAmB,aAAa;CAChD,OAAO,cAAc,QAAQ,kBAAkB,GAAG,CAAC,OAAO,CAAC;AAC7D;;;;;;AClDA,SAAgB,gBAAgB,EAC9B,YACA,WAAW,MACX,UACA,GAAG,SACoB;CAGvB,IAAI,CAFkB,cAAc,UAEnB,GACf,OAAO,oBAAA,UAAA,EAAA,UAAG,SAAW,CAAA;CAGvB,OAAO,oBAAC,UAAD;EAAQ,GAAI;EAAQ;CAAiB,CAAA;AAC9C;;;;AAcA,SAAgB,cAAc,EAC5B,YACA,WAAW,MACX,UACA,GAAG,SACkB;CAGrB,IAAI,CAFkB,cAAc,UAEnB,GACf,OAAO,oBAAA,UAAA,EAAA,UAAG,SAAW,CAAA;CAGvB,OAAO,oBAAC,KAAD;EAAG,GAAI;EAAQ;CAAY,CAAA;AACpC;;;;AAiBA,SAAgB,iBAAiB,EAC/B,YACA,aACA,SACA,WAAW,MACX,YACwB;CAExB,MAAM,yBAAyB,cAAc,cAAc,EAAE;CAG7D,MAAM,4BAA4B,eAAe,eAAe,CAAC,GAAG,OAAO;CAG3E,IAAI,gBAAgB;CAEpB,IAAI,YACF,gBAAgB;MACX,IAAI,eAAe,YAAY,SAAS,GAC7C,gBAAgB,0BAA0B;MAG1C,gBAAgB;CAGlB,IAAI,CAAC,eACH,OAAO,oBAAA,UAAA,EAAA,UAAG,SAAW,CAAA;CAGvB,OAAO,oBAAA,UAAA,EAAG,SAAW,CAAA;AACvB;;;;AAeA,SAAgB,gBAAgB,EAC9B,YACA,UACA,YACuB;CACvB,MAAM,gBAAgB,cAAc,UAAU;CAE9C,OACE,oBAAA,UAAA,EAAA,UACG,SAAS;EACR,SAAS,gBAAgB,iBAAiB,CAAC;EAC3C,UAAU,CAAC;CACb,CAAC,EACD,CAAA;AAEN;;;;;;;;;AC/HA,MAAa,qBAAyC,CAAC"}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { MutationCache, QueryCache, QueryClient } from "@tanstack/react-query";
|
|
2
|
-
|
|
3
2
|
//#region src/query-client/query-config.ts
|
|
4
3
|
/**
|
|
4
|
+
* Query Client Configuration
|
|
5
|
+
*
|
|
6
|
+
* Optimized caching strategy for different types of data:
|
|
7
|
+
* - Static data (rarely changes): Long staleTime and gcTime
|
|
8
|
+
* - Dynamic data (changes frequently): Short staleTime
|
|
9
|
+
* - Critical data (needs to be fresh): Very short staleTime with prefetching
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
5
12
|
* Default configuration
|
|
6
13
|
*/
|
|
7
14
|
const DEFAULT_CONFIG = {
|
|
8
|
-
staleTime:
|
|
9
|
-
gcTime:
|
|
15
|
+
staleTime: 300 * 1e3,
|
|
16
|
+
gcTime: 600 * 1e3,
|
|
10
17
|
maxRetries: 3,
|
|
11
18
|
maxMutationRetries: 1,
|
|
12
19
|
refetchOnWindowFocus: true,
|
|
@@ -54,7 +61,7 @@ function createQueryClient(config = {}) {
|
|
|
54
61
|
...config
|
|
55
62
|
};
|
|
56
63
|
const handleLogout = onLogout || createDefaultLogoutHandler();
|
|
57
|
-
|
|
64
|
+
return new QueryClient({
|
|
58
65
|
defaultOptions: {
|
|
59
66
|
queries: {
|
|
60
67
|
staleTime,
|
|
@@ -97,7 +104,6 @@ function createQueryClient(config = {}) {
|
|
|
97
104
|
logger?.error?.("Mutation error", error instanceof Error ? error : void 0, { is401: is401Error(error) });
|
|
98
105
|
} })
|
|
99
106
|
});
|
|
100
|
-
return queryClient;
|
|
101
107
|
}
|
|
102
108
|
/**
|
|
103
109
|
* Cache Presets for different data types
|
|
@@ -105,24 +111,40 @@ function createQueryClient(config = {}) {
|
|
|
105
111
|
* Use these presets for consistent caching across the application.
|
|
106
112
|
*/
|
|
107
113
|
const cachePresets = {
|
|
114
|
+
/**
|
|
115
|
+
* Static data - rarely changes
|
|
116
|
+
* Good for: permissions, roles, system settings
|
|
117
|
+
*/
|
|
108
118
|
static: {
|
|
109
|
-
staleTime:
|
|
110
|
-
gcTime:
|
|
119
|
+
staleTime: 1800 * 1e3,
|
|
120
|
+
gcTime: 3600 * 1e3,
|
|
111
121
|
refetchOnWindowFocus: false
|
|
112
122
|
},
|
|
123
|
+
/**
|
|
124
|
+
* Normal data - changes occasionally
|
|
125
|
+
* Good for: resources, users, tenant data
|
|
126
|
+
*/
|
|
113
127
|
normal: {
|
|
114
|
-
staleTime:
|
|
115
|
-
gcTime:
|
|
128
|
+
staleTime: 300 * 1e3,
|
|
129
|
+
gcTime: 600 * 1e3,
|
|
116
130
|
refetchOnWindowFocus: true
|
|
117
131
|
},
|
|
132
|
+
/**
|
|
133
|
+
* Dynamic data - changes frequently
|
|
134
|
+
* Good for: transactions, activities, notifications
|
|
135
|
+
*/
|
|
118
136
|
dynamic: {
|
|
119
|
-
staleTime:
|
|
120
|
-
gcTime:
|
|
137
|
+
staleTime: 60 * 1e3,
|
|
138
|
+
gcTime: 300 * 1e3,
|
|
121
139
|
refetchOnWindowFocus: true
|
|
122
140
|
},
|
|
141
|
+
/**
|
|
142
|
+
* Realtime data - always fresh
|
|
143
|
+
* Good for: live status, counters, dashboards
|
|
144
|
+
*/
|
|
123
145
|
realtime: {
|
|
124
146
|
staleTime: 0,
|
|
125
|
-
gcTime:
|
|
147
|
+
gcTime: 60 * 1e3,
|
|
126
148
|
refetchOnWindowFocus: true
|
|
127
149
|
}
|
|
128
150
|
};
|
|
@@ -150,12 +172,21 @@ function createQueryKeyFactory(module) {
|
|
|
150
172
|
* Invalidation helpers for common patterns
|
|
151
173
|
*/
|
|
152
174
|
const invalidationHelpers = {
|
|
175
|
+
/**
|
|
176
|
+
* Invalidate all queries for a module
|
|
177
|
+
*/
|
|
153
178
|
invalidateModule: async (queryClient, module) => {
|
|
154
179
|
await queryClient.invalidateQueries({ queryKey: [module] });
|
|
155
180
|
},
|
|
181
|
+
/**
|
|
182
|
+
* Invalidate all list queries for a module
|
|
183
|
+
*/
|
|
156
184
|
invalidateLists: async (queryClient, module) => {
|
|
157
185
|
await queryClient.invalidateQueries({ queryKey: [module, "list"] });
|
|
158
186
|
},
|
|
187
|
+
/**
|
|
188
|
+
* Invalidate a specific detail query
|
|
189
|
+
*/
|
|
159
190
|
invalidateDetail: async (queryClient, module, id) => {
|
|
160
191
|
await queryClient.invalidateQueries({ queryKey: [
|
|
161
192
|
module,
|
|
@@ -163,12 +194,15 @@ const invalidationHelpers = {
|
|
|
163
194
|
id
|
|
164
195
|
] });
|
|
165
196
|
},
|
|
197
|
+
/**
|
|
198
|
+
* Invalidate and refetch (useful for critical data)
|
|
199
|
+
*/
|
|
166
200
|
invalidateAndRefetch: async (queryClient, queryKey) => {
|
|
167
201
|
await queryClient.invalidateQueries({ queryKey });
|
|
168
202
|
await queryClient.refetchQueries({ queryKey });
|
|
169
203
|
}
|
|
170
204
|
};
|
|
171
|
-
|
|
172
205
|
//#endregion
|
|
173
|
-
export {
|
|
174
|
-
|
|
206
|
+
export { invalidationHelpers as i, createQueryClient as n, createQueryKeyFactory as r, cachePresets as t };
|
|
207
|
+
|
|
208
|
+
//# sourceMappingURL=query-client-_xB300gc.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-client-_xB300gc.mjs","names":[],"sources":["../src/query-client/query-config.ts"],"sourcesContent":["/**\n * Query Client Configuration\n * \n * Optimized caching strategy for different types of data:\n * - Static data (rarely changes): Long staleTime and gcTime\n * - Dynamic data (changes frequently): Short staleTime\n * - Critical data (needs to be fresh): Very short staleTime with prefetching\n */\n\nimport { QueryClient, QueryCache, MutationCache } from '@tanstack/react-query';\nimport type { QueryClientConfig, CachePresets } from './types';\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<Omit<QueryClientConfig, 'logger' | 'onLogout'>> = {\n staleTime: 5 * 60 * 1000, // 5 minutes\n gcTime: 10 * 60 * 1000, // 10 minutes\n maxRetries: 3,\n maxMutationRetries: 1,\n refetchOnWindowFocus: true,\n refetchOnReconnect: false,\n refetchOnMount: false,\n};\n\n/**\n * Check if error is a 401 Unauthorized error\n */\nfunction is401Error(error: unknown): boolean {\n if (error instanceof Response) {\n return error.status === 401;\n }\n\n if (error && typeof error === 'object' && 'status' in error) {\n return (error as any).status === 401;\n }\n\n return false;\n}\n\n/**\n * Check if error requires logout (refresh token expired)\n */\nfunction shouldLogout(error: unknown): boolean {\n if (error && typeof error === 'object') {\n // Check for shouldLogout flag from server\n if ('shouldLogout' in error && (error as any).shouldLogout === true) {\n return true;\n }\n\n // Check for SESSION_EXPIRED code\n if ('code' in error && (error as any).code === 'SESSION_EXPIRED') {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Create default logout handler\n */\nfunction createDefaultLogoutHandler(): (returnUrl?: string) => void {\n return (returnUrl?: string) => {\n if (typeof window !== 'undefined') {\n const currentUrl = returnUrl || window.location.pathname + window.location.search;\n window.location.href = `/auth/logout?logout=local&returnUrl=${encodeURIComponent(currentUrl)}`;\n }\n };\n}\n\n/**\n * Create optimized QueryClient instance\n * \n * @param config - Configuration options\n * @returns Configured QueryClient with optimized defaults\n */\nexport function createQueryClient(config: QueryClientConfig = {}): QueryClient {\n const {\n staleTime,\n gcTime,\n maxRetries,\n maxMutationRetries,\n refetchOnWindowFocus,\n refetchOnReconnect,\n refetchOnMount,\n logger,\n onLogout,\n } = { ...DEFAULT_CONFIG, ...config };\n\n const handleLogout = onLogout || createDefaultLogoutHandler();\n\n const queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime,\n gcTime,\n retry: (failureCount, error) => {\n // Don't retry 401 errors - the fetch client handles refresh flows separately\n if (is401Error(error)) {\n logger?.debug?.('Query failed with 401, not retrying');\n return false;\n }\n return failureCount < maxRetries;\n },\n retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),\n refetchOnWindowFocus,\n refetchOnReconnect,\n refetchOnMount,\n },\n mutations: {\n retry: (failureCount, error) => {\n if (is401Error(error)) {\n logger?.debug?.('Mutation failed with 401, not retrying');\n return false;\n }\n return failureCount < maxMutationRetries;\n },\n retryDelay: 1000,\n },\n },\n queryCache: new QueryCache({\n onError: (error) => {\n if (shouldLogout(error)) {\n handleLogout();\n return;\n }\n logger?.error?.('Query error', error instanceof Error ? error : undefined, {\n is401: is401Error(error),\n });\n },\n }),\n mutationCache: new MutationCache({\n onError: (error) => {\n if (shouldLogout(error)) {\n handleLogout();\n return;\n }\n logger?.error?.('Mutation error', error instanceof Error ? error : undefined, {\n is401: is401Error(error),\n });\n },\n }),\n });\n\n return queryClient;\n}\n\n/**\n * Cache Presets for different data types\n * \n * Use these presets for consistent caching across the application.\n */\nexport const cachePresets: CachePresets = {\n /**\n * Static data - rarely changes\n * Good for: permissions, roles, system settings\n */\n static: {\n staleTime: 30 * 60 * 1000, // 30 minutes\n gcTime: 60 * 60 * 1000, // 1 hour\n refetchOnWindowFocus: false,\n },\n\n /**\n * Normal data - changes occasionally\n * Good for: resources, users, tenant data\n */\n normal: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n gcTime: 10 * 60 * 1000, // 10 minutes\n refetchOnWindowFocus: true,\n },\n\n /**\n * Dynamic data - changes frequently\n * Good for: transactions, activities, notifications\n */\n dynamic: {\n staleTime: 1 * 60 * 1000, // 1 minute\n gcTime: 5 * 60 * 1000, // 5 minutes\n refetchOnWindowFocus: true,\n },\n\n /**\n * Realtime data - always fresh\n * Good for: live status, counters, dashboards\n */\n realtime: {\n staleTime: 0, // Always stale\n gcTime: 1 * 60 * 1000, // 1 minute\n refetchOnWindowFocus: true,\n },\n};\n\n/**\n * Create base query keys for a module\n */\nexport function createQueryKeyFactory<T extends string>(\n module: T\n): {\n all: readonly [T];\n lists: () => readonly [T, 'list'];\n list: (filters?: Record<string, unknown>) => readonly [T, 'list', Record<string, unknown>?];\n details: () => readonly [T, 'detail'];\n detail: (id: string) => readonly [T, 'detail', string];\n} {\n return {\n all: [module] as const,\n lists: () => [module, 'list'] as const,\n list: (filters?: Record<string, unknown>) => [module, 'list', filters] as const,\n details: () => [module, 'detail'] as const,\n detail: (id: string) => [module, 'detail', id] as const,\n };\n}\n\n/**\n * Invalidation helpers for common patterns\n */\nexport const invalidationHelpers = {\n /**\n * Invalidate all queries for a module\n */\n invalidateModule: async (queryClient: QueryClient, module: string): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey: [module] });\n },\n\n /**\n * Invalidate all list queries for a module\n */\n invalidateLists: async (queryClient: QueryClient, module: string): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey: [module, 'list'] });\n },\n\n /**\n * Invalidate a specific detail query\n */\n invalidateDetail: async (\n queryClient: QueryClient,\n module: string,\n id: string\n ): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey: [module, 'detail', id] });\n },\n\n /**\n * Invalidate and refetch (useful for critical data)\n */\n invalidateAndRefetch: async (queryClient: QueryClient, queryKey: readonly unknown[]): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey });\n await queryClient.refetchQueries({ queryKey });\n },\n};\n"],"mappings":"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"}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging
|
|
2
|
-
import { PermissionChecker } from "../permission-service-
|
|
3
|
-
import {
|
|
4
|
-
import { evaluateAccessRequirement } from "../access-
|
|
5
|
-
import { TenantProvider, configureTenantClient, configureTenantEndpoints, getTenantCookieName, initializeTenantStore, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetTenantClientConfig, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useOrganization, useTenant, useTenantData, useTenantStore, useTenantSwitcher } from "../tenant
|
|
6
|
-
import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, initialIdentityContext, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, resetIdentityStoreConfig, useHasEntitlement, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "../identity
|
|
7
|
-
import { isApiClientError } from "../types-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export { ApiError, ErrorCodes, ErrorHandler, IdentityContextProvider, IdentitySignalRClient, LOG_LEVEL_PRIORITY, LogLevel, Logger, PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, SignalRClient, TenantProvider, cachePresets, calculateBackoffDelay, cleanupSignalR, configureIdentityEndpoints, configureIdentityStore, configureTenantClient, configureTenantEndpoints, createIdentitySignalRClient, createLogger, createQueryClient, createQueryKeyFactory, createRetryFetch, defaultLoggerConfig, evaluateAccessRequirement, fetchWithTimeout, getIdentitySignalRClient, getPermissionChecker, getPermissions, getTenantCookieName, handleApiError, initialIdentityContext, initializePermissions, initializeSignalR, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, logger, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetIdentityStoreConfig, resetTenantClientConfig, useAllPermissions, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useHasEntitlement, useHasModulePermission, useHasPermission, useHasPermissions, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useModulePermission, useOrganization, usePermission, usePermissionChecker, usePermissionStore, usePermissions, useTenant, useTenantData, useTenantStore, useTenantSwitcher, useUserPermissions, useUserTenants, withRetry };
|
|
1
|
+
import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging/index.mjs";
|
|
2
|
+
import { t as PermissionChecker } from "../permission-service-DUyKomU3.mjs";
|
|
3
|
+
import { _ as initializePermissions, a as ProtectedSection, c as useHasPermission, d as usePermission, f as usePermissionChecker, g as getPermissions, h as getPermissionChecker, i as ProtectedLink, l as useHasPermissions, m as PermissionInitializer, n as ProtectedAction, o as useAllPermissions, p as usePermissions, r as ProtectedButton, s as useHasModulePermission, t as PERMISSION_MODULES, u as useModulePermission, v as usePermissionStore } from "../permissions-C5dTP9y4.mjs";
|
|
4
|
+
import { t as evaluateAccessRequirement } from "../access-BHy1v5sr.mjs";
|
|
5
|
+
import { TenantProvider, configureTenantClient, configureTenantEndpoints, getTenantCookieName, initializeTenantStore, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetTenantClientConfig, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useOrganization, useTenant, useTenantData, useTenantStore, useTenantSwitcher } from "../tenant/index.mjs";
|
|
6
|
+
import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, initialIdentityContext, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, resetIdentityStoreConfig, useHasEntitlement, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "../identity/index.mjs";
|
|
7
|
+
import { t as isApiClientError } from "../types-DqCXinAk.mjs";
|
|
8
|
+
import { a as withRetry, c as handleApiError, i as isRetryableError, l as ErrorCodes, n as createRetryFetch, o as ApiError, r as fetchWithTimeout, s as ErrorHandler, t as calculateBackoffDelay } from "../api-client-CB7XZaMg.mjs";
|
|
9
|
+
import { a as getIdentitySignalRClient, i as createIdentitySignalRClient, n as SignalRClient, o as initializeSignalR, r as cleanupSignalR, t as IdentitySignalRClient } from "../signalr-C-TA8nEE.mjs";
|
|
10
|
+
import { i as invalidationHelpers, n as createQueryClient, r as createQueryKeyFactory, t as cachePresets } from "../query-client-_xB300gc.mjs";
|
|
11
|
+
export { ApiError, ErrorCodes, ErrorHandler, IdentityContextProvider, IdentitySignalRClient, LOG_LEVEL_PRIORITY, LogLevel, Logger, PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, SignalRClient, TenantProvider, cachePresets, calculateBackoffDelay, cleanupSignalR, configureIdentityEndpoints, configureIdentityStore, configureTenantClient, configureTenantEndpoints, createIdentitySignalRClient, createLogger, createQueryClient, createQueryKeyFactory, createRetryFetch, defaultLoggerConfig, evaluateAccessRequirement, fetchWithTimeout, getIdentitySignalRClient, getPermissionChecker, getPermissions, getTenantCookieName, handleApiError, initialIdentityContext, initializePermissions, initializeSignalR, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, logger, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetIdentityStoreConfig, resetTenantClientConfig, useAllPermissions, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useHasEntitlement, useHasModulePermission, useHasPermission, useHasPermissions, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useModulePermission, useOrganization, usePermission, usePermissionChecker, usePermissionStore, usePermissions, useTenant, useTenantData, useTenantStore, useTenantSwitcher, useUserPermissions, useUserTenants, withRetry };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging/index.mjs";
|
|
2
|
+
import { t as PermissionChecker } from "../permission-service-DUyKomU3.mjs";
|
|
3
|
+
import { _ as initializePermissions, a as ProtectedSection, c as useHasPermission, d as usePermission, f as usePermissionChecker, g as getPermissions, h as getPermissionChecker, i as ProtectedLink, l as useHasPermissions, m as PermissionInitializer, n as ProtectedAction, o as useAllPermissions, p as usePermissions, r as ProtectedButton, s as useHasModulePermission, t as PERMISSION_MODULES, u as useModulePermission, v as usePermissionStore } from "../permissions-C5dTP9y4.mjs";
|
|
4
|
+
import { t as evaluateAccessRequirement } from "../access-BHy1v5sr.mjs";
|
|
5
|
+
import { TenantProvider, configureTenantClient, configureTenantEndpoints, getTenantCookieName, initializeTenantStore, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetTenantClientConfig, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useOrganization, useTenant, useTenantData, useTenantStore, useTenantSwitcher } from "../tenant/index.mjs";
|
|
6
|
+
import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, initialIdentityContext, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, resetIdentityStoreConfig, useHasEntitlement, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "../identity/index.mjs";
|
|
7
|
+
import { t as isApiClientError } from "../types-DqCXinAk.mjs";
|
|
8
|
+
import { a as withRetry, c as handleApiError, i as isRetryableError, l as ErrorCodes, n as createRetryFetch, o as ApiError, r as fetchWithTimeout, s as ErrorHandler, t as calculateBackoffDelay } from "../api-client-CB7XZaMg.mjs";
|
|
9
|
+
import { a as getIdentitySignalRClient, i as createIdentitySignalRClient, n as SignalRClient, o as initializeSignalR, r as cleanupSignalR, t as IdentitySignalRClient } from "../signalr-C-TA8nEE.mjs";
|
|
10
|
+
import { i as invalidationHelpers, n as createQueryClient, r as createQueryKeyFactory, t as cachePresets } from "../query-client-_xB300gc.mjs";
|
|
11
|
+
import { S as updateAuthSession, _ as isSessionValid, a as createOAuthState, b as listCurrentUserAuthSessions, c as destroyAuthSessionById, d as destroyAuthSessionsBySid, f as destroyAuthSessionsForUser, g as getOAuthState, h as getCurrentAuthSessionId, i as createAuthSession, l as destroyAuthSessionsByIdentitySession, m as getAuthSessionById, n as cleanupExpiredOAuthStates, o as deleteOAuthState, p as getAuthSession, r as closeRedisConnection, s as destroyAuthSession, t as AUTH_ERROR_COOKIE_PREFIX, u as destroyAuthSessionsBySessionState, v as listAuthSessionDataForUser, x as requireAuthSession, y as listAuthSessionsForUser } from "../redis-session-storage.server-DGgQcJ9B.mjs";
|
|
12
|
+
import { A as resetAuthClaimMapping, C as handleCallback, D as logout, E as login, O as refreshTokens, S as handleBackChannelLogout, T as hasAuthError, _ as createAuthorizationResponseUrl, a as configureRouteProtection, b as getAuthError, c as protectRoute, d as resetRouteProtectionConfig, f as shouldRefreshToken, g as configureAuthClaimMapping, h as clearAuthServerCache, i as authRoute, k as requireAuth, l as protectedRoute, m as clearAuthErrorHeaders, n as isTokenExpiredResponse, o as onboardingRoute, p as subscriptionRoute, r as shouldAttemptTokenRefresh, s as policyRoute, t as attemptTokenRefresh, u as publicRoute, v as extractUserInfo, w as handleFrontChannelLogout, x as getUser, y as getAccessToken } from "../server-CqX2yCa4.mjs";
|
|
13
|
+
import { clearActiveTenant, clearCurrentTenant, configureIdentityContextFetcher, configureTenantCookie, configureTenantResolution, getActiveTenant, getAvailableTenants, getCurrentTenant, getTenantFromIdentityContext, initializeTenant, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant } from "../tenant/server.mjs";
|
|
14
|
+
import { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion } from "../identity/server.mjs";
|
|
15
|
+
import { n as createFetchMiddleware, r as createAPIConfigFactory, t as createApiClient } from "../server-ByScGRXA.mjs";
|
|
16
|
+
import { checkPermission, configurePermissionRouteProtection, requirePermission, resetPermissionRouteProtection, withPermission } from "../server.mjs";
|
|
17
|
+
export { AUTH_ERROR_COOKIE_PREFIX, ApiError, ErrorCodes, ErrorHandler, IdentityContextProvider, IdentitySignalRClient, LOG_LEVEL_PRIORITY, LogLevel, Logger, PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, SignalRClient, TenantProvider, attemptTokenRefresh, authRoute, cachePresets, calculateBackoffDelay, checkPermission, cleanupExpiredOAuthStates, cleanupSignalR, clearActiveTenant, clearAuthErrorHeaders, clearAuthServerCache, clearCurrentTenant, clearIdentityContextCache, closeRedisConnection, configureAuthClaimMapping, configureIdentityAPIFetcher, configureIdentityContextFetcher, configureIdentityEndpoints, configureIdentityStore, configurePermissionFetcher, configurePermissionRouteProtection, configureRouteProtection, configureTenantClient, configureTenantCookie, configureTenantEndpoints, configureTenantResolution, contextToUserInfo, createAPIConfigFactory, createApiClient, createAuthSession, createAuthorizationResponseUrl, createFetchMiddleware, createIdentitySignalRClient, createLogger, createOAuthState, createQueryClient, createQueryKeyFactory, createRetryFetch, defaultLoggerConfig, deleteOAuthState, destroyAuthSession, destroyAuthSessionById, destroyAuthSessionsByIdentitySession, destroyAuthSessionsBySessionState, destroyAuthSessionsBySid, destroyAuthSessionsForUser, evaluateAccessRequirement, extractUserInfo, fetchIdentityContext, fetchUserPermissions, fetchWithTimeout, getAccessToken, getActiveTenant, getAuthError, getAuthSession, getAuthSessionById, getAvailableTenants, getCurrentAuthSessionId, getCurrentTenant, getIdentityContext, getIdentitySignalRClient, getOAuthState, getPermissionChecker, getPermissions, getTenantCookieName, getTenantFromIdentityContext, getUser, handleApiError, handleBackChannelLogout, handleCallback, handleFrontChannelLogout, hasAuthError, hasContextVersionChanged, initialIdentityContext, initializePermissions, initializeSignalR, initializeTenant, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, isSessionValid, isTokenExpiredResponse, listAuthSessionDataForUser, listAuthSessionsForUser, listCurrentUserAuthSessions, logger, login, logout, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, normalizeTenantDataPayload, normalizeTenantSwitchPayload, onboardingRoute, policyRoute, protectRoute, protectedRoute, publicRoute, refreshTokens, requireAuth, requireAuthSession, requirePermission, resetAuthClaimMapping, resetIdentityStoreConfig, resetPermissionRouteProtection, resetRouteProtectionConfig, resetTenantClientConfig, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute, updateAuthSession, updateContextVersion, useAllPermissions, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useHasEntitlement, useHasModulePermission, useHasPermission, useHasPermissions, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useModulePermission, useOrganization, usePermission, usePermissionChecker, usePermissionStore, usePermissions, useTenant, useTenantData, useTenantStore, useTenantSwitcher, useUserPermissions, useUserTenants, withPermission, withRetry };
|
package/dist/{redis-session-storage.server-v2VcpFsQ.js → redis-session-storage.server-DGgQcJ9B.mjs}
RENAMED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { logger } from "./logging
|
|
1
|
+
import { logger } from "./logging/index.mjs";
|
|
2
2
|
import Redis from "ioredis";
|
|
3
3
|
import { createCipheriv, createDecipheriv, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
|
-
|
|
5
4
|
//#region src/auth/redis-session-storage.server.ts
|
|
5
|
+
/**
|
|
6
|
+
* Redis Session Storage Server Module
|
|
7
|
+
*
|
|
8
|
+
* Redis-backed session storage for authentication.
|
|
9
|
+
* Provides secure session management with automatic expiration,
|
|
10
|
+
* without depending on a framework-specific session implementation.
|
|
11
|
+
*/
|
|
6
12
|
let redis = null;
|
|
7
13
|
function getRedis() {
|
|
8
14
|
if (!redis) {
|
|
@@ -25,9 +31,8 @@ const REDIS_KEYS = {
|
|
|
25
31
|
sessionBySessionState: (sessionState) => `${KEY_PREFIX}session:index:session_state:${sessionState}`
|
|
26
32
|
};
|
|
27
33
|
const DEFAULT_SESSION_SECRET = "default-secret-change-in-production";
|
|
28
|
-
const DEFAULT_SESSION_TTL =
|
|
29
|
-
const
|
|
30
|
-
const OAUTH_STATE_TTL = resolvePositiveIntegerEnv("OAUTH_STATE_TTL", DEFAULT_OAUTH_STATE_TTL);
|
|
34
|
+
const DEFAULT_SESSION_TTL = 3600 * 24 * 7;
|
|
35
|
+
const OAUTH_STATE_TTL = resolvePositiveIntegerEnv("OAUTH_STATE_TTL", 600);
|
|
31
36
|
const SESSION_TTL = resolvePositiveIntegerEnv("SESSION_DEFAULT_TTL", resolvePositiveIntegerEnv("SESSION_TTL", DEFAULT_SESSION_TTL));
|
|
32
37
|
const SESSION_COOKIE_NAME = process.env.SESSION_COOKIE_NAME || "__session_id";
|
|
33
38
|
const SESSION_SECRET = resolveSessionSecret();
|
|
@@ -120,8 +125,7 @@ function safelyCompareValues(left, right) {
|
|
|
120
125
|
return timingSafeEqual(leftBuffer, rightBuffer);
|
|
121
126
|
}
|
|
122
127
|
function signSessionId(sessionId) {
|
|
123
|
-
|
|
124
|
-
return `${sessionId}.${signature}`;
|
|
128
|
+
return `${sessionId}.${createCookieSignature(sessionId, SESSION_SECRET)}`;
|
|
125
129
|
}
|
|
126
130
|
function unsignSessionId(cookieValue) {
|
|
127
131
|
if (!cookieValue) return null;
|
|
@@ -134,9 +138,7 @@ function unsignSessionId(cookieValue) {
|
|
|
134
138
|
const separatorIndex = decodedValue.lastIndexOf(".");
|
|
135
139
|
if (separatorIndex <= 0 || separatorIndex === decodedValue.length - 1) return null;
|
|
136
140
|
const sessionId = decodedValue.slice(0, separatorIndex);
|
|
137
|
-
|
|
138
|
-
const expectedSignature = createCookieSignature(sessionId, SESSION_SECRET);
|
|
139
|
-
return safelyCompareValues(signature, expectedSignature) ? sessionId : null;
|
|
141
|
+
return safelyCompareValues(decodedValue.slice(separatorIndex + 1), createCookieSignature(sessionId, SESSION_SECRET)) ? sessionId : null;
|
|
140
142
|
}
|
|
141
143
|
function serializeSessionCookieValue(sessionId, options) {
|
|
142
144
|
const signedValue = encodeURIComponent(signSessionId(sessionId));
|
|
@@ -164,11 +166,10 @@ function serializeExpiredSessionCookie() {
|
|
|
164
166
|
return segments.join("; ");
|
|
165
167
|
}
|
|
166
168
|
async function getSessionIdFromRequest(request) {
|
|
167
|
-
|
|
168
|
-
return unsignSessionId(cookies[SESSION_COOKIE_NAME]);
|
|
169
|
+
return unsignSessionId(parseCookieHeader(request.headers.get("Cookie"))[SESSION_COOKIE_NAME]);
|
|
169
170
|
}
|
|
170
171
|
function getSessionIndexKeys(data) {
|
|
171
|
-
const keys = new Set();
|
|
172
|
+
const keys = /* @__PURE__ */ new Set();
|
|
172
173
|
if (data.userId) keys.add(REDIS_KEYS.sessionByUser(data.userId));
|
|
173
174
|
if (data.sid) keys.add(REDIS_KEYS.sessionBySid(data.sid));
|
|
174
175
|
if (data.sessionState) keys.add(REDIS_KEYS.sessionBySessionState(data.sessionState));
|
|
@@ -177,8 +178,7 @@ function getSessionIndexKeys(data) {
|
|
|
177
178
|
async function registerSessionIndexes(sessionId, data) {
|
|
178
179
|
const indexKeys = getSessionIndexKeys(data);
|
|
179
180
|
if (indexKeys.length === 0) return;
|
|
180
|
-
const
|
|
181
|
-
const pipeline = redisClient.pipeline();
|
|
181
|
+
const pipeline = getRedis().pipeline();
|
|
182
182
|
for (const indexKey of indexKeys) {
|
|
183
183
|
pipeline.sadd(indexKey, sessionId);
|
|
184
184
|
pipeline.expire(indexKey, SESSION_TTL);
|
|
@@ -188,8 +188,7 @@ async function registerSessionIndexes(sessionId, data) {
|
|
|
188
188
|
async function unregisterSessionIndexes(sessionId, data) {
|
|
189
189
|
const indexKeys = getSessionIndexKeys(data);
|
|
190
190
|
if (indexKeys.length === 0) return;
|
|
191
|
-
const
|
|
192
|
-
const pipeline = redisClient.pipeline();
|
|
191
|
+
const pipeline = getRedis().pipeline();
|
|
193
192
|
for (const indexKey of indexKeys) pipeline.srem(indexKey, sessionId);
|
|
194
193
|
await pipeline.exec();
|
|
195
194
|
}
|
|
@@ -279,11 +278,8 @@ async function getSessionIdsFromIndex(indexKey) {
|
|
|
279
278
|
const sessionIds = await getRedis().smembers(indexKey);
|
|
280
279
|
const activeSessionIds = [];
|
|
281
280
|
const staleSessionIds = [];
|
|
282
|
-
for (const sessionId of sessionIds)
|
|
283
|
-
|
|
284
|
-
if (exists) activeSessionIds.push(sessionId);
|
|
285
|
-
else staleSessionIds.push(sessionId);
|
|
286
|
-
}
|
|
281
|
+
for (const sessionId of sessionIds) if (await getRedis().exists(REDIS_KEYS.session(sessionId))) activeSessionIds.push(sessionId);
|
|
282
|
+
else staleSessionIds.push(sessionId);
|
|
287
283
|
if (staleSessionIds.length > 0) await getRedis().srem(indexKey, ...staleSessionIds);
|
|
288
284
|
return activeSessionIds;
|
|
289
285
|
}
|
|
@@ -317,8 +313,7 @@ async function listSessionDataByIds(sessionIds) {
|
|
|
317
313
|
return sessions;
|
|
318
314
|
}
|
|
319
315
|
function getRequestIpAddress(request) {
|
|
320
|
-
|
|
321
|
-
return forwardedFor || request.headers.get("cf-connecting-ip")?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
|
|
316
|
+
return request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("cf-connecting-ip")?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
|
|
322
317
|
}
|
|
323
318
|
async function createAuthSession(request, sessionData) {
|
|
324
319
|
const currentSessionId = await getSessionIdFromRequest(request);
|
|
@@ -361,11 +356,10 @@ async function updateAuthSession(request, updates) {
|
|
|
361
356
|
const currentSessionId = await getSessionIdFromRequest(request);
|
|
362
357
|
const currentSessionData = currentSessionId ? await readSessionData(currentSessionId) ?? {} : {};
|
|
363
358
|
const nextSessionId = currentSessionId ?? crypto.randomUUID();
|
|
364
|
-
|
|
359
|
+
await writeSessionData(nextSessionId, mergeSessionData(currentSessionData, {
|
|
365
360
|
...updates,
|
|
366
361
|
sessionId: nextSessionId
|
|
367
|
-
});
|
|
368
|
-
await writeSessionData(nextSessionId, nextSessionData);
|
|
362
|
+
}));
|
|
369
363
|
return new Headers({ "Set-Cookie": serializeSessionCookieValue(nextSessionId, { maxAge: SESSION_TTL }) });
|
|
370
364
|
}
|
|
371
365
|
async function destroyAuthSession(request) {
|
|
@@ -384,12 +378,10 @@ async function getAuthSessionById(sessionId) {
|
|
|
384
378
|
} : null;
|
|
385
379
|
}
|
|
386
380
|
async function listAuthSessionDataForUser(userId) {
|
|
387
|
-
|
|
388
|
-
return await listSessionDataByIds(sessionIds);
|
|
381
|
+
return await listSessionDataByIds(await getSessionIdsFromIndex(REDIS_KEYS.sessionByUser(userId)));
|
|
389
382
|
}
|
|
390
383
|
async function listAuthSessionsForUser(userId, currentSessionId) {
|
|
391
|
-
|
|
392
|
-
return sessions.map(({ sessionId, data }) => toAuthSessionSummary(sessionId, data, currentSessionId)).sort((left, right) => (right.lastActivity ?? right.createdAt ?? 0) - (left.lastActivity ?? left.createdAt ?? 0));
|
|
384
|
+
return (await listAuthSessionDataForUser(userId)).map(({ sessionId, data }) => toAuthSessionSummary(sessionId, data, currentSessionId)).sort((left, right) => (right.lastActivity ?? right.createdAt ?? 0) - (left.lastActivity ?? left.createdAt ?? 0));
|
|
393
385
|
}
|
|
394
386
|
async function listCurrentUserAuthSessions(request) {
|
|
395
387
|
const currentSession = await getAuthSession(request);
|
|
@@ -397,8 +389,7 @@ async function listCurrentUserAuthSessions(request) {
|
|
|
397
389
|
return await listAuthSessionsForUser(currentSession.userId, currentSession.sessionId);
|
|
398
390
|
}
|
|
399
391
|
async function destroyAuthSessionById(sessionId) {
|
|
400
|
-
|
|
401
|
-
if (!session) return false;
|
|
392
|
+
if (!await readSessionData(sessionId)) return false;
|
|
402
393
|
await deleteSessionData(sessionId);
|
|
403
394
|
return true;
|
|
404
395
|
}
|
|
@@ -474,12 +465,9 @@ async function cleanupExpiredOAuthStates() {
|
|
|
474
465
|
const pattern = REDIS_KEYS.oauthState("*");
|
|
475
466
|
const keys = await getRedis().keys(pattern);
|
|
476
467
|
let cleaned = 0;
|
|
477
|
-
for (const key of keys) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
await getRedis().del(key);
|
|
481
|
-
cleaned++;
|
|
482
|
-
}
|
|
468
|
+
for (const key of keys) if (await getRedis().ttl(key) === -1) {
|
|
469
|
+
await getRedis().del(key);
|
|
470
|
+
cleaned++;
|
|
483
471
|
}
|
|
484
472
|
logger.info("OAuth state cleanup completed", {
|
|
485
473
|
cleaned,
|
|
@@ -494,7 +482,7 @@ async function closeRedisConnection() {
|
|
|
494
482
|
logger.info("Redis connection closed");
|
|
495
483
|
}
|
|
496
484
|
}
|
|
497
|
-
|
|
498
485
|
//#endregion
|
|
499
|
-
export {
|
|
500
|
-
|
|
486
|
+
export { updateAuthSession as S, isSessionValid as _, createOAuthState as a, listCurrentUserAuthSessions as b, destroyAuthSessionById as c, destroyAuthSessionsBySid as d, destroyAuthSessionsForUser as f, getOAuthState as g, getCurrentAuthSessionId as h, createAuthSession as i, destroyAuthSessionsByIdentitySession as l, getAuthSessionById as m, cleanupExpiredOAuthStates as n, deleteOAuthState as o, getAuthSession as p, closeRedisConnection as r, destroyAuthSession as s, AUTH_ERROR_COOKIE_PREFIX as t, destroyAuthSessionsBySessionState as u, listAuthSessionDataForUser as v, requireAuthSession as x, listAuthSessionsForUser as y };
|
|
487
|
+
|
|
488
|
+
//# sourceMappingURL=redis-session-storage.server-DGgQcJ9B.mjs.map
|