@parsrun/server 0.1.0
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/README.md +142 -0
- package/dist/app.d.ts +87 -0
- package/dist/app.js +59 -0
- package/dist/app.js.map +1 -0
- package/dist/context.d.ts +208 -0
- package/dist/context.js +23 -0
- package/dist/context.js.map +1 -0
- package/dist/health.d.ts +81 -0
- package/dist/health.js +112 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +2094 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +888 -0
- package/dist/middleware/index.js +880 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/module-loader.d.ts +125 -0
- package/dist/module-loader.js +309 -0
- package/dist/module-loader.js.map +1 -0
- package/dist/rbac.d.ts +171 -0
- package/dist/rbac.js +347 -0
- package/dist/rbac.js.map +1 -0
- package/dist/rls.d.ts +114 -0
- package/dist/rls.js +126 -0
- package/dist/rls.js.map +1 -0
- package/dist/utils/index.d.ts +262 -0
- package/dist/utils/index.js +193 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/validation/index.d.ts +118 -0
- package/dist/validation/index.js +245 -0
- package/dist/validation/index.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/module-loader.ts","../src/context.ts"],"sourcesContent":["/**\n * @parsrun/server - Module Loader\n * Dynamic module loading system for Pars server\n */\n\nimport { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { createLogger, type Logger } from \"@parsrun/core\";\nimport type {\n CorsConfig,\n DatabaseAdapter,\n HonoApp,\n ModuleManifest,\n ServerConfig,\n ServerContextVariables,\n} from \"./context.js\";\nimport { generateRequestId } from \"./context.js\";\n\n/**\n * Module Loader options\n */\nexport interface ModuleLoaderOptions {\n /** Server configuration */\n config: ServerConfig;\n /** Cookie prefix */\n cookiePrefix?: string;\n /** Logger instance */\n logger?: Logger;\n}\n\n/**\n * Module Loader\n * Manages dynamic module registration and lifecycle\n *\n * @example\n * ```typescript\n * const loader = new ModuleLoader({\n * config: {\n * database: drizzleDb,\n * cors: { origin: '*' },\n * },\n * });\n *\n * await loader.initialize();\n *\n * // Register modules\n * loader.registerModule(itemsModule);\n * loader.registerModule(usersModule);\n *\n * // Enable modules\n * await loader.enableModule('items');\n * await loader.enableModule('users');\n *\n * // Get Hono app\n * export default loader.getApp();\n * ```\n */\nexport class ModuleLoader {\n private app: HonoApp;\n private db: DatabaseAdapter;\n private enabledModules: Set<string> = new Set();\n private moduleRegistry: Map<string, ModuleManifest> = new Map();\n private logger: Logger;\n private config: ServerConfig;\n private cookiePrefix: string | undefined;\n private initialized = false;\n\n constructor(options: ModuleLoaderOptions) {\n this.config = options.config;\n this.db = options.config.database;\n this.cookiePrefix = options.cookiePrefix;\n this.logger = options.logger ?? createLogger({ name: \"ModuleLoader\" });\n\n // Create Hono app with typed context\n this.app = new Hono<{ Variables: ServerContextVariables }>();\n\n this.setupMiddleware();\n this.setupCoreRoutes();\n }\n\n /**\n * Initialize the module loader\n * Checks database connection\n */\n async initialize(): Promise<void> {\n if (this.initialized) {\n this.logger.warn(\"ModuleLoader already initialized\");\n return;\n }\n\n this.logger.info(\"Initializing ModuleLoader...\");\n\n // Check database connection\n try {\n if (this.db.ping) {\n const ok = await this.db.ping();\n if (!ok) throw new Error(\"Database ping returned false\");\n } else {\n await this.db.execute(\"SELECT 1\");\n }\n this.logger.info(\"Database connection: OK\");\n } catch (error) {\n this.logger.error(\"Database connection failed\", error);\n throw new Error(\"Database connection failed\");\n }\n\n this.initialized = true;\n this.logger.info(\"ModuleLoader initialized successfully\");\n }\n\n /**\n * Setup core middleware\n */\n private setupMiddleware(): void {\n // Request ID and logging\n this.app.use(\"*\", async (c, next) => {\n const requestId = generateRequestId();\n const requestLogger = this.logger.child({ requestId });\n\n // Set context variables\n c.set(\"db\", this.db);\n c.set(\"config\", this.config);\n c.set(\"enabledModules\", this.enabledModules);\n c.set(\"logger\", requestLogger);\n c.set(\"requestId\", requestId);\n c.set(\"cookiePrefix\", this.cookiePrefix);\n c.set(\"custom\", this.config.custom ?? {});\n c.set(\"user\", undefined);\n c.set(\"tenant\", undefined);\n\n const start = Date.now();\n\n await next();\n\n const duration = Date.now() - start;\n requestLogger.debug(\"Request completed\", {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n durationMs: duration,\n });\n });\n\n // CORS\n if (this.config.cors) {\n this.app.use(\"*\", cors(this.normalizeCorsConfig(this.config.cors)));\n }\n }\n\n /**\n * Normalize CORS config for Hono\n */\n private normalizeCorsConfig(config: CorsConfig): Parameters<typeof cors>[0] {\n const result: Parameters<typeof cors>[0] = {\n origin: typeof config.origin === \"function\"\n ? (origin) => (config.origin as (origin: string) => boolean)(origin) ? origin : null\n : config.origin,\n };\n\n if (config.credentials !== undefined) {\n result.credentials = config.credentials;\n }\n if (config.methods !== undefined) {\n result.allowMethods = config.methods;\n }\n if (config.allowedHeaders !== undefined) {\n result.allowHeaders = config.allowedHeaders;\n }\n if (config.exposedHeaders !== undefined) {\n result.exposeHeaders = config.exposedHeaders;\n }\n if (config.maxAge !== undefined) {\n result.maxAge = config.maxAge;\n }\n\n return result;\n }\n\n /**\n * Setup core routes\n */\n private setupCoreRoutes(): void {\n const basePath = this.config.basePath ?? \"/api/v1\";\n\n // Health check\n this.app.get(\"/health\", (c) => {\n return c.json({\n status: \"ok\",\n timestamp: new Date().toISOString(),\n });\n });\n\n // Health check with details\n this.app.get(\"/health/details\", async (c) => {\n let dbStatus = \"unknown\";\n try {\n if (this.db.ping) {\n dbStatus = (await this.db.ping()) ? \"ok\" : \"error\";\n } else {\n await this.db.execute(\"SELECT 1\");\n dbStatus = \"ok\";\n }\n } catch {\n dbStatus = \"error\";\n }\n\n return c.json({\n status: dbStatus === \"ok\" ? \"ok\" : \"degraded\",\n timestamp: new Date().toISOString(),\n services: {\n database: dbStatus,\n },\n modules: {\n enabled: Array.from(this.enabledModules),\n registered: Array.from(this.moduleRegistry.keys()),\n },\n });\n });\n\n // API info\n this.app.get(basePath, (c) => {\n const endpoints: Record<string, string> = {\n health: \"/health\",\n features: `${basePath}/features`,\n };\n\n // Add module endpoints\n for (const moduleName of this.enabledModules) {\n endpoints[moduleName] = `${basePath}/${moduleName}`;\n }\n\n return c.json({\n name: \"Pars API\",\n version: \"1.0.0\",\n endpoints,\n });\n });\n\n // Feature discovery\n this.app.get(`${basePath}/features`, (c) => {\n const features = Array.from(this.enabledModules).map((name) => {\n const module = this.moduleRegistry.get(name);\n return {\n name,\n version: module?.version ?? \"1.0.0\",\n description: module?.description ?? \"\",\n permissions: module?.permissions ?? {},\n };\n });\n\n return c.json({\n enabled: Array.from(this.enabledModules),\n features,\n });\n });\n }\n\n /**\n * Register a module\n */\n registerModule(manifest: ModuleManifest): void {\n if (this.moduleRegistry.has(manifest.name)) {\n this.logger.warn(`Module already registered: ${manifest.name}`);\n return;\n }\n\n this.moduleRegistry.set(manifest.name, manifest);\n this.logger.info(`Registered module: ${manifest.name}`);\n }\n\n /**\n * Enable a registered module\n */\n async enableModule(moduleName: string): Promise<boolean> {\n const module = this.moduleRegistry.get(moduleName);\n\n if (!module) {\n this.logger.error(`Module not found: ${moduleName}`);\n return false;\n }\n\n if (this.enabledModules.has(moduleName)) {\n this.logger.warn(`Module already enabled: ${moduleName}`);\n return true;\n }\n\n // Check dependencies\n if (module.dependencies) {\n for (const dep of module.dependencies) {\n if (!this.enabledModules.has(dep)) {\n this.logger.error(\n `Module ${moduleName} requires ${dep} to be enabled first`\n );\n return false;\n }\n }\n }\n\n try {\n this.logger.info(`Enabling module: ${moduleName}`);\n\n // Run onEnable hook\n if (module.onEnable) {\n await module.onEnable();\n }\n\n // Register routes\n module.registerRoutes(this.app);\n\n // Mark as enabled\n this.enabledModules.add(moduleName);\n\n this.logger.info(`Enabled module: ${moduleName}`);\n return true;\n } catch (error) {\n this.logger.error(`Failed to enable module ${moduleName}`, error);\n return false;\n }\n }\n\n /**\n * Disable an enabled module\n */\n async disableModule(moduleName: string): Promise<boolean> {\n if (!this.enabledModules.has(moduleName)) {\n this.logger.warn(`Module not enabled: ${moduleName}`);\n return true;\n }\n\n const module = this.moduleRegistry.get(moduleName);\n if (!module) {\n this.logger.error(`Module not found: ${moduleName}`);\n return false;\n }\n\n // Check if other modules depend on this one\n for (const [name, m] of this.moduleRegistry) {\n if (this.enabledModules.has(name) && m.dependencies?.includes(moduleName)) {\n this.logger.error(\n `Cannot disable ${moduleName}: ${name} depends on it`\n );\n return false;\n }\n }\n\n try {\n // Run onDisable hook\n if (module.onDisable) {\n await module.onDisable();\n }\n\n // Note: Routes cannot be easily unregistered in Hono\n // The module will remain registered but marked as disabled\n this.enabledModules.delete(moduleName);\n\n this.logger.info(`Disabled module: ${moduleName}`);\n return true;\n } catch (error) {\n this.logger.error(`Failed to disable module ${moduleName}`, error);\n return false;\n }\n }\n\n /**\n * Get the Hono app instance\n */\n getApp(): HonoApp {\n return this.app;\n }\n\n /**\n * Get enabled modules\n */\n getEnabledModules(): string[] {\n return Array.from(this.enabledModules);\n }\n\n /**\n * Get registered modules\n */\n getRegisteredModules(): string[] {\n return Array.from(this.moduleRegistry.keys());\n }\n\n /**\n * Check if module is enabled\n */\n isModuleEnabled(moduleName: string): boolean {\n return this.enabledModules.has(moduleName);\n }\n\n /**\n * Check if module is registered\n */\n isModuleRegistered(moduleName: string): boolean {\n return this.moduleRegistry.has(moduleName);\n }\n\n /**\n * Get database adapter\n */\n getDatabase(): DatabaseAdapter {\n return this.db;\n }\n\n /**\n * Get logger\n */\n getLogger(): Logger {\n return this.logger;\n }\n}\n\n/**\n * Create a module loader\n */\nexport function createModuleLoader(options: ModuleLoaderOptions): ModuleLoader {\n return new ModuleLoader(options);\n}\n\n/**\n * Create a module manifest helper\n */\nexport function defineModule(manifest: ModuleManifest): ModuleManifest {\n return manifest;\n}\n","/**\n * @parsrun/server - Server Context\n * Type definitions for server context and configuration\n */\n\nimport type { Logger } from \"@parsrun/core\";\n\n/**\n * Database adapter interface\n * Implement this for your database (Drizzle, Prisma, etc.)\n */\nexport interface DatabaseAdapter {\n /** Execute raw SQL query */\n execute(sql: string): Promise<unknown>;\n /** Check connection */\n ping?(): Promise<boolean>;\n}\n\n/**\n * Module manifest for registering modules\n */\nexport interface ModuleManifest {\n /** Unique module name */\n name: string;\n /** Module version */\n version: string;\n /** Module description */\n description: string;\n /** Required permissions for this module */\n permissions: Record<string, string[]>;\n /** Module dependencies (other module names) */\n dependencies?: string[];\n /** Register routes for this module */\n registerRoutes: (app: HonoApp) => void;\n /** Called when module is enabled */\n onEnable?: () => Promise<void>;\n /** Called when module is disabled */\n onDisable?: () => Promise<void>;\n}\n\n/**\n * Server configuration\n */\nexport interface ServerConfig {\n /** Database adapter */\n database: DatabaseAdapter;\n /** CORS configuration */\n cors?: CorsConfig;\n /** Base path for API */\n basePath?: string;\n /** Cookie prefix for all cookies */\n cookiePrefix?: string;\n /** Logger instance */\n logger?: Logger;\n /** Custom context data */\n custom?: Record<string, unknown>;\n}\n\n/**\n * CORS configuration\n */\nexport interface CorsConfig {\n /** Allowed origins */\n origin: string | string[] | ((origin: string) => boolean);\n /** Allow credentials */\n credentials?: boolean;\n /** Allowed methods */\n methods?: string[];\n /** Allowed headers */\n allowedHeaders?: string[];\n /** Exposed headers */\n exposedHeaders?: string[];\n /** Max age in seconds */\n maxAge?: number;\n}\n\n/**\n * User information in context\n */\nexport interface ContextUser {\n id: string;\n email: string | undefined;\n tenantId: string | undefined;\n role: string | undefined;\n permissions: string[];\n}\n\n/**\n * Tenant information in context\n */\nexport interface ContextTenant {\n id: string;\n slug: string | undefined;\n name: string | undefined;\n status: string;\n}\n\n/**\n * Server context variables\n * Available in Hono context via c.get()\n */\nexport interface ServerContextVariables {\n /** Database adapter */\n db: DatabaseAdapter;\n /** Server configuration */\n config: ServerConfig;\n /** Enabled modules set */\n enabledModules: Set<string>;\n /** Current user (if authenticated) */\n user: ContextUser | undefined;\n /** Current tenant (if resolved) */\n tenant: ContextTenant | undefined;\n /** Request logger */\n logger: Logger;\n /** Request ID */\n requestId: string;\n /** Cookie prefix */\n cookiePrefix: string | undefined;\n /** Custom context data */\n custom: Record<string, unknown>;\n}\n\n/**\n * Hono app type with server context\n */\nexport type HonoApp = import(\"hono\").Hono<{ Variables: ServerContextVariables }>;\n\n/**\n * Hono context type with server context\n */\nexport type HonoContext = import(\"hono\").Context<{ Variables: ServerContextVariables }>;\n\n/**\n * Middleware next function\n */\nexport type HonoNext = () => Promise<void>;\n\n/**\n * Middleware function type\n */\nexport type Middleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\n/**\n * Route handler function type\n */\nexport type RouteHandler = (c: HonoContext) => Promise<Response> | Response;\n\n/**\n * Permission check input\n */\nexport interface PermissionCheck {\n /** Resource name (e.g., \"users\", \"items\") */\n resource: string;\n /** Action name (e.g., \"read\", \"create\", \"update\", \"delete\") */\n action: string;\n /** Permission scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Permission definition\n */\nexport interface PermissionDefinition {\n /** Permission name (e.g., \"users:read\") */\n name: string;\n /** Resource part */\n resource: string;\n /** Action part */\n action: string;\n /** Description */\n description?: string;\n /** Scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Role definition\n */\nexport interface RoleDefinition {\n /** Role name */\n name: string;\n /** Display name */\n displayName?: string;\n /** Description */\n description?: string;\n /** Permissions assigned to this role */\n permissions: string[];\n /** Is this a system role */\n isSystem?: boolean;\n}\n\n/**\n * Standard API response structure\n */\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: {\n code: string;\n message: string;\n details?: Record<string, unknown> | undefined;\n };\n meta?: {\n page?: number | undefined;\n limit?: number | undefined;\n total?: number | undefined;\n requestId?: string | undefined;\n } | undefined;\n}\n\n/**\n * Create a success response\n */\nexport function success<T>(data: T, meta?: ApiResponse[\"meta\"]): ApiResponse<T> {\n return {\n success: true,\n data,\n meta: meta ?? undefined,\n };\n}\n\n/**\n * Create an error response\n */\nexport function error(\n code: string,\n message: string,\n details?: Record<string, unknown>\n): ApiResponse<never> {\n return {\n success: false,\n error: { code, message, details: details ?? undefined },\n };\n}\n\n/**\n * Generate a request ID\n */\nexport function generateRequestId(): string {\n return crypto.randomUUID();\n}\n"],"mappings":"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","names":[]}
|
package/dist/rbac.d.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { PermissionCheck, Middleware, PermissionDefinition, RoleDefinition } from './context.js';
|
|
2
|
+
import 'hono';
|
|
3
|
+
import '@parsrun/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @parsrun/server - Role-Based Access Control (RBAC)
|
|
7
|
+
* Permission and role management with middleware
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* RBAC Permission Checker interface
|
|
12
|
+
* Implement this for database-backed permission checks
|
|
13
|
+
*/
|
|
14
|
+
interface PermissionChecker {
|
|
15
|
+
/** Get user's permissions in a tenant */
|
|
16
|
+
getUserPermissions(userId: string, tenantId?: string): Promise<string[]>;
|
|
17
|
+
/** Get user's roles in a tenant */
|
|
18
|
+
getUserRoles(userId: string, tenantId?: string): Promise<string[]>;
|
|
19
|
+
/** Check if user has specific permission */
|
|
20
|
+
hasPermission(userId: string, check: PermissionCheck, tenantId?: string): Promise<boolean>;
|
|
21
|
+
/** Check if user is member of tenant */
|
|
22
|
+
isTenantMember(userId: string, tenantId: string): Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* In-memory RBAC service
|
|
26
|
+
* For simple use cases or testing
|
|
27
|
+
*/
|
|
28
|
+
declare class InMemoryRBAC implements PermissionChecker {
|
|
29
|
+
private userPermissions;
|
|
30
|
+
private userRoles;
|
|
31
|
+
private rolePermissions;
|
|
32
|
+
private tenantMembers;
|
|
33
|
+
/**
|
|
34
|
+
* Grant permission to user
|
|
35
|
+
*/
|
|
36
|
+
grantPermission(userId: string, permission: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Revoke permission from user
|
|
39
|
+
*/
|
|
40
|
+
revokePermission(userId: string, permission: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Assign role to user
|
|
43
|
+
*/
|
|
44
|
+
assignRole(userId: string, role: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Remove role from user
|
|
47
|
+
*/
|
|
48
|
+
removeRole(userId: string, role: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Define role with permissions
|
|
51
|
+
*/
|
|
52
|
+
defineRole(roleName: string, permissions: string[]): void;
|
|
53
|
+
/**
|
|
54
|
+
* Add user to tenant
|
|
55
|
+
*/
|
|
56
|
+
addTenantMember(tenantId: string, userId: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* Remove user from tenant
|
|
59
|
+
*/
|
|
60
|
+
removeTenantMember(tenantId: string, userId: string): void;
|
|
61
|
+
getUserPermissions(userId: string, _tenantId?: string): Promise<string[]>;
|
|
62
|
+
getUserRoles(userId: string, _tenantId?: string): Promise<string[]>;
|
|
63
|
+
hasPermission(userId: string, check: PermissionCheck, tenantId?: string): Promise<boolean>;
|
|
64
|
+
isTenantMember(userId: string, tenantId: string): Promise<boolean>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* RBAC Service for authorization checks
|
|
68
|
+
*/
|
|
69
|
+
declare class RBACService {
|
|
70
|
+
private checker;
|
|
71
|
+
constructor(checker: PermissionChecker);
|
|
72
|
+
/**
|
|
73
|
+
* Get user's permissions
|
|
74
|
+
*/
|
|
75
|
+
getUserPermissions(userId: string, tenantId?: string): Promise<string[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Get user's roles
|
|
78
|
+
*/
|
|
79
|
+
getUserRoles(userId: string, tenantId?: string): Promise<string[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Check if user has specific permission
|
|
82
|
+
*/
|
|
83
|
+
hasPermission(userId: string, check: PermissionCheck, tenantId?: string): Promise<boolean>;
|
|
84
|
+
/**
|
|
85
|
+
* Check if user has any of the specified permissions
|
|
86
|
+
*/
|
|
87
|
+
hasAnyPermission(userId: string, checks: PermissionCheck[], tenantId?: string): Promise<boolean>;
|
|
88
|
+
/**
|
|
89
|
+
* Check if user has all specified permissions
|
|
90
|
+
*/
|
|
91
|
+
hasAllPermissions(userId: string, checks: PermissionCheck[], tenantId?: string): Promise<boolean>;
|
|
92
|
+
/**
|
|
93
|
+
* Check if user is member of tenant
|
|
94
|
+
*/
|
|
95
|
+
isTenantMember(userId: string, tenantId: string): Promise<boolean>;
|
|
96
|
+
/**
|
|
97
|
+
* Check if user has specific role
|
|
98
|
+
*/
|
|
99
|
+
hasRole(userId: string, role: string, tenantId?: string): Promise<boolean>;
|
|
100
|
+
/**
|
|
101
|
+
* Check if user has any of the specified roles
|
|
102
|
+
*/
|
|
103
|
+
hasAnyRole(userId: string, roles: string[], tenantId?: string): Promise<boolean>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create RBAC service with in-memory checker
|
|
107
|
+
*/
|
|
108
|
+
declare function createInMemoryRBAC(): {
|
|
109
|
+
rbac: RBACService;
|
|
110
|
+
checker: InMemoryRBAC;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Create RBAC service with custom checker
|
|
114
|
+
*/
|
|
115
|
+
declare function createRBACService(checker: PermissionChecker): RBACService;
|
|
116
|
+
/**
|
|
117
|
+
* Require authentication middleware
|
|
118
|
+
*/
|
|
119
|
+
declare function requireAuth(): Middleware;
|
|
120
|
+
/**
|
|
121
|
+
* Require specific permission middleware
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* app.get('/items', requirePermission('items', 'read'), handler);
|
|
126
|
+
* app.post('/items', requirePermission('items', 'create'), handler);
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
declare function requirePermission(resource: string, action: string, options?: {
|
|
130
|
+
scope?: "tenant" | "global" | "own";
|
|
131
|
+
checker?: PermissionChecker;
|
|
132
|
+
}): Middleware;
|
|
133
|
+
/**
|
|
134
|
+
* Require any of specified permissions middleware
|
|
135
|
+
*/
|
|
136
|
+
declare function requireAnyPermission(permissions: Array<{
|
|
137
|
+
resource: string;
|
|
138
|
+
action: string;
|
|
139
|
+
}>, options?: {
|
|
140
|
+
checker?: PermissionChecker;
|
|
141
|
+
}): Middleware;
|
|
142
|
+
/**
|
|
143
|
+
* Require specific role middleware
|
|
144
|
+
*/
|
|
145
|
+
declare function requireRole(role: string): Middleware;
|
|
146
|
+
/**
|
|
147
|
+
* Require any of specified roles middleware
|
|
148
|
+
*/
|
|
149
|
+
declare function requireAnyRole(roles: string[]): Middleware;
|
|
150
|
+
/**
|
|
151
|
+
* Require tenant membership middleware
|
|
152
|
+
*/
|
|
153
|
+
declare function requireTenantMember(requiredRole?: string): Middleware;
|
|
154
|
+
/**
|
|
155
|
+
* Parse permission string to PermissionCheck
|
|
156
|
+
*/
|
|
157
|
+
declare function parsePermission(permission: string): PermissionCheck;
|
|
158
|
+
/**
|
|
159
|
+
* Create permission string from parts
|
|
160
|
+
*/
|
|
161
|
+
declare function createPermission(resource: string, action: string): string;
|
|
162
|
+
/**
|
|
163
|
+
* Standard CRUD permissions for a resource
|
|
164
|
+
*/
|
|
165
|
+
declare function crudPermissions(resource: string): PermissionDefinition[];
|
|
166
|
+
/**
|
|
167
|
+
* Standard roles
|
|
168
|
+
*/
|
|
169
|
+
declare const StandardRoles: Record<string, RoleDefinition>;
|
|
170
|
+
|
|
171
|
+
export { InMemoryRBAC, type PermissionChecker, RBACService, StandardRoles, createInMemoryRBAC, createPermission, createRBACService, crudPermissions, parsePermission, requireAnyPermission, requireAnyRole, requireAuth, requirePermission, requireRole, requireTenantMember };
|
package/dist/rbac.js
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
// src/rbac.ts
|
|
2
|
+
import { ForbiddenError, UnauthorizedError } from "@parsrun/core";
|
|
3
|
+
var InMemoryRBAC = class {
|
|
4
|
+
userPermissions = /* @__PURE__ */ new Map();
|
|
5
|
+
userRoles = /* @__PURE__ */ new Map();
|
|
6
|
+
rolePermissions = /* @__PURE__ */ new Map();
|
|
7
|
+
tenantMembers = /* @__PURE__ */ new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Grant permission to user
|
|
10
|
+
*/
|
|
11
|
+
grantPermission(userId, permission) {
|
|
12
|
+
if (!this.userPermissions.has(userId)) {
|
|
13
|
+
this.userPermissions.set(userId, /* @__PURE__ */ new Set());
|
|
14
|
+
}
|
|
15
|
+
this.userPermissions.get(userId).add(permission);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Revoke permission from user
|
|
19
|
+
*/
|
|
20
|
+
revokePermission(userId, permission) {
|
|
21
|
+
this.userPermissions.get(userId)?.delete(permission);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Assign role to user
|
|
25
|
+
*/
|
|
26
|
+
assignRole(userId, role) {
|
|
27
|
+
if (!this.userRoles.has(userId)) {
|
|
28
|
+
this.userRoles.set(userId, /* @__PURE__ */ new Set());
|
|
29
|
+
}
|
|
30
|
+
this.userRoles.get(userId).add(role);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Remove role from user
|
|
34
|
+
*/
|
|
35
|
+
removeRole(userId, role) {
|
|
36
|
+
this.userRoles.get(userId)?.delete(role);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Define role with permissions
|
|
40
|
+
*/
|
|
41
|
+
defineRole(roleName, permissions) {
|
|
42
|
+
this.rolePermissions.set(roleName, new Set(permissions));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Add user to tenant
|
|
46
|
+
*/
|
|
47
|
+
addTenantMember(tenantId, userId) {
|
|
48
|
+
if (!this.tenantMembers.has(tenantId)) {
|
|
49
|
+
this.tenantMembers.set(tenantId, /* @__PURE__ */ new Set());
|
|
50
|
+
}
|
|
51
|
+
this.tenantMembers.get(tenantId).add(userId);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Remove user from tenant
|
|
55
|
+
*/
|
|
56
|
+
removeTenantMember(tenantId, userId) {
|
|
57
|
+
this.tenantMembers.get(tenantId)?.delete(userId);
|
|
58
|
+
}
|
|
59
|
+
// PermissionChecker implementation
|
|
60
|
+
async getUserPermissions(userId, _tenantId) {
|
|
61
|
+
const permissions = /* @__PURE__ */ new Set();
|
|
62
|
+
const direct = this.userPermissions.get(userId);
|
|
63
|
+
if (direct) {
|
|
64
|
+
direct.forEach((p) => permissions.add(p));
|
|
65
|
+
}
|
|
66
|
+
const roles = this.userRoles.get(userId);
|
|
67
|
+
if (roles) {
|
|
68
|
+
roles.forEach((role) => {
|
|
69
|
+
const rolePerms = this.rolePermissions.get(role);
|
|
70
|
+
if (rolePerms) {
|
|
71
|
+
rolePerms.forEach((p) => permissions.add(p));
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return Array.from(permissions);
|
|
76
|
+
}
|
|
77
|
+
async getUserRoles(userId, _tenantId) {
|
|
78
|
+
return Array.from(this.userRoles.get(userId) ?? []);
|
|
79
|
+
}
|
|
80
|
+
async hasPermission(userId, check, tenantId) {
|
|
81
|
+
const permissions = await this.getUserPermissions(userId, tenantId);
|
|
82
|
+
const permissionName = `${check.resource}:${check.action}`;
|
|
83
|
+
if (permissions.includes(permissionName)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
for (const perm of permissions) {
|
|
87
|
+
if (perm === "*") return true;
|
|
88
|
+
if (perm === `${check.resource}:*`) return true;
|
|
89
|
+
if (perm === `*:${check.action}`) return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
async isTenantMember(userId, tenantId) {
|
|
94
|
+
return this.tenantMembers.get(tenantId)?.has(userId) ?? false;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var RBACService = class {
|
|
98
|
+
constructor(checker) {
|
|
99
|
+
this.checker = checker;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get user's permissions
|
|
103
|
+
*/
|
|
104
|
+
async getUserPermissions(userId, tenantId) {
|
|
105
|
+
return this.checker.getUserPermissions(userId, tenantId);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get user's roles
|
|
109
|
+
*/
|
|
110
|
+
async getUserRoles(userId, tenantId) {
|
|
111
|
+
return this.checker.getUserRoles(userId, tenantId);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if user has specific permission
|
|
115
|
+
*/
|
|
116
|
+
async hasPermission(userId, check, tenantId) {
|
|
117
|
+
return this.checker.hasPermission(userId, check, tenantId);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if user has any of the specified permissions
|
|
121
|
+
*/
|
|
122
|
+
async hasAnyPermission(userId, checks, tenantId) {
|
|
123
|
+
for (const check of checks) {
|
|
124
|
+
if (await this.hasPermission(userId, check, tenantId)) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Check if user has all specified permissions
|
|
132
|
+
*/
|
|
133
|
+
async hasAllPermissions(userId, checks, tenantId) {
|
|
134
|
+
for (const check of checks) {
|
|
135
|
+
if (!await this.hasPermission(userId, check, tenantId)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Check if user is member of tenant
|
|
143
|
+
*/
|
|
144
|
+
async isTenantMember(userId, tenantId) {
|
|
145
|
+
return this.checker.isTenantMember(userId, tenantId);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check if user has specific role
|
|
149
|
+
*/
|
|
150
|
+
async hasRole(userId, role, tenantId) {
|
|
151
|
+
const roles = await this.getUserRoles(userId, tenantId);
|
|
152
|
+
return roles.includes(role);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Check if user has any of the specified roles
|
|
156
|
+
*/
|
|
157
|
+
async hasAnyRole(userId, roles, tenantId) {
|
|
158
|
+
const userRoles = await this.getUserRoles(userId, tenantId);
|
|
159
|
+
return roles.some((role) => userRoles.includes(role));
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
function createInMemoryRBAC() {
|
|
163
|
+
const checker = new InMemoryRBAC();
|
|
164
|
+
const rbac = new RBACService(checker);
|
|
165
|
+
return { rbac, checker };
|
|
166
|
+
}
|
|
167
|
+
function createRBACService(checker) {
|
|
168
|
+
return new RBACService(checker);
|
|
169
|
+
}
|
|
170
|
+
function requireAuth() {
|
|
171
|
+
return async (c, next) => {
|
|
172
|
+
const user = c.get("user");
|
|
173
|
+
if (!user) {
|
|
174
|
+
throw new UnauthorizedError("Authentication required");
|
|
175
|
+
}
|
|
176
|
+
return next();
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function requirePermission(resource, action, options = {}) {
|
|
180
|
+
return async (c, next) => {
|
|
181
|
+
const user = c.get("user");
|
|
182
|
+
if (!user) {
|
|
183
|
+
throw new UnauthorizedError("Authentication required");
|
|
184
|
+
}
|
|
185
|
+
const check = {
|
|
186
|
+
resource,
|
|
187
|
+
action,
|
|
188
|
+
scope: options.scope ?? "tenant"
|
|
189
|
+
};
|
|
190
|
+
let hasPermission = false;
|
|
191
|
+
if (options.checker) {
|
|
192
|
+
hasPermission = await options.checker.hasPermission(user.id, check, user.tenantId);
|
|
193
|
+
} else {
|
|
194
|
+
hasPermission = checkUserPermission(user, check);
|
|
195
|
+
}
|
|
196
|
+
if (!hasPermission) {
|
|
197
|
+
throw new ForbiddenError(`Permission denied: ${resource}:${action}`);
|
|
198
|
+
}
|
|
199
|
+
return next();
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function requireAnyPermission(permissions, options = {}) {
|
|
203
|
+
return async (c, next) => {
|
|
204
|
+
const user = c.get("user");
|
|
205
|
+
if (!user) {
|
|
206
|
+
throw new UnauthorizedError("Authentication required");
|
|
207
|
+
}
|
|
208
|
+
let hasAny = false;
|
|
209
|
+
for (const perm of permissions) {
|
|
210
|
+
const check = { resource: perm.resource, action: perm.action };
|
|
211
|
+
if (options.checker) {
|
|
212
|
+
if (await options.checker.hasPermission(user.id, check, user.tenantId)) {
|
|
213
|
+
hasAny = true;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
if (checkUserPermission(user, check)) {
|
|
218
|
+
hasAny = true;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!hasAny) {
|
|
224
|
+
throw new ForbiddenError("Insufficient permissions");
|
|
225
|
+
}
|
|
226
|
+
return next();
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function requireRole(role) {
|
|
230
|
+
return async (c, next) => {
|
|
231
|
+
const user = c.get("user");
|
|
232
|
+
if (!user) {
|
|
233
|
+
throw new UnauthorizedError("Authentication required");
|
|
234
|
+
}
|
|
235
|
+
if (user.role !== role) {
|
|
236
|
+
throw new ForbiddenError(`Role required: ${role}`);
|
|
237
|
+
}
|
|
238
|
+
return next();
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function requireAnyRole(roles) {
|
|
242
|
+
return async (c, next) => {
|
|
243
|
+
const user = c.get("user");
|
|
244
|
+
if (!user) {
|
|
245
|
+
throw new UnauthorizedError("Authentication required");
|
|
246
|
+
}
|
|
247
|
+
if (!user.role || !roles.includes(user.role)) {
|
|
248
|
+
throw new ForbiddenError(`One of these roles required: ${roles.join(", ")}`);
|
|
249
|
+
}
|
|
250
|
+
return next();
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function requireTenantMember(requiredRole) {
|
|
254
|
+
return async (c, next) => {
|
|
255
|
+
const user = c.get("user");
|
|
256
|
+
const tenant = c.get("tenant");
|
|
257
|
+
if (!user) {
|
|
258
|
+
throw new UnauthorizedError("Authentication required");
|
|
259
|
+
}
|
|
260
|
+
if (!tenant) {
|
|
261
|
+
throw new ForbiddenError("Tenant context required");
|
|
262
|
+
}
|
|
263
|
+
if (user.tenantId !== tenant.id) {
|
|
264
|
+
throw new ForbiddenError("Not a member of this tenant");
|
|
265
|
+
}
|
|
266
|
+
if (requiredRole && user.role !== requiredRole) {
|
|
267
|
+
throw new ForbiddenError(`Role required in tenant: ${requiredRole}`);
|
|
268
|
+
}
|
|
269
|
+
return next();
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function checkUserPermission(user, check) {
|
|
273
|
+
const permissionName = `${check.resource}:${check.action}`;
|
|
274
|
+
for (const perm of user.permissions) {
|
|
275
|
+
if (perm === permissionName) return true;
|
|
276
|
+
if (perm === "*") return true;
|
|
277
|
+
if (perm === `${check.resource}:*`) return true;
|
|
278
|
+
if (perm === `*:${check.action}`) return true;
|
|
279
|
+
}
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
function parsePermission(permission) {
|
|
283
|
+
const [resource, action] = permission.split(":");
|
|
284
|
+
if (!resource || !action) {
|
|
285
|
+
throw new Error(`Invalid permission format: ${permission}`);
|
|
286
|
+
}
|
|
287
|
+
return { resource, action };
|
|
288
|
+
}
|
|
289
|
+
function createPermission(resource, action) {
|
|
290
|
+
return `${resource}:${action}`;
|
|
291
|
+
}
|
|
292
|
+
function crudPermissions(resource) {
|
|
293
|
+
return [
|
|
294
|
+
{ name: `${resource}:create`, resource, action: "create" },
|
|
295
|
+
{ name: `${resource}:read`, resource, action: "read" },
|
|
296
|
+
{ name: `${resource}:update`, resource, action: "update" },
|
|
297
|
+
{ name: `${resource}:delete`, resource, action: "delete" },
|
|
298
|
+
{ name: `${resource}:list`, resource, action: "list" }
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
var StandardRoles = {
|
|
302
|
+
OWNER: {
|
|
303
|
+
name: "owner",
|
|
304
|
+
displayName: "Owner",
|
|
305
|
+
description: "Full access to all resources",
|
|
306
|
+
permissions: ["*"],
|
|
307
|
+
isSystem: true
|
|
308
|
+
},
|
|
309
|
+
ADMIN: {
|
|
310
|
+
name: "admin",
|
|
311
|
+
displayName: "Administrator",
|
|
312
|
+
description: "Administrative access",
|
|
313
|
+
permissions: ["*:read", "*:create", "*:update", "*:list"],
|
|
314
|
+
isSystem: true
|
|
315
|
+
},
|
|
316
|
+
MEMBER: {
|
|
317
|
+
name: "member",
|
|
318
|
+
displayName: "Member",
|
|
319
|
+
description: "Standard member access",
|
|
320
|
+
permissions: ["*:read", "*:list"],
|
|
321
|
+
isSystem: true
|
|
322
|
+
},
|
|
323
|
+
VIEWER: {
|
|
324
|
+
name: "viewer",
|
|
325
|
+
displayName: "Viewer",
|
|
326
|
+
description: "Read-only access",
|
|
327
|
+
permissions: ["*:read", "*:list"],
|
|
328
|
+
isSystem: true
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
export {
|
|
332
|
+
InMemoryRBAC,
|
|
333
|
+
RBACService,
|
|
334
|
+
StandardRoles,
|
|
335
|
+
createInMemoryRBAC,
|
|
336
|
+
createPermission,
|
|
337
|
+
createRBACService,
|
|
338
|
+
crudPermissions,
|
|
339
|
+
parsePermission,
|
|
340
|
+
requireAnyPermission,
|
|
341
|
+
requireAnyRole,
|
|
342
|
+
requireAuth,
|
|
343
|
+
requirePermission,
|
|
344
|
+
requireRole,
|
|
345
|
+
requireTenantMember
|
|
346
|
+
};
|
|
347
|
+
//# sourceMappingURL=rbac.js.map
|
package/dist/rbac.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rbac.ts"],"sourcesContent":["/**\n * @parsrun/server - Role-Based Access Control (RBAC)\n * Permission and role management with middleware\n */\n\nimport { ForbiddenError, UnauthorizedError } from \"@parsrun/core\";\nimport type {\n ContextUser,\n HonoContext,\n HonoNext,\n Middleware,\n PermissionCheck,\n PermissionDefinition,\n RoleDefinition,\n} from \"./context.js\";\n\n/**\n * RBAC Permission Checker interface\n * Implement this for database-backed permission checks\n */\nexport interface PermissionChecker {\n /** Get user's permissions in a tenant */\n getUserPermissions(userId: string, tenantId?: string): Promise<string[]>;\n /** Get user's roles in a tenant */\n getUserRoles(userId: string, tenantId?: string): Promise<string[]>;\n /** Check if user has specific permission */\n hasPermission(userId: string, check: PermissionCheck, tenantId?: string): Promise<boolean>;\n /** Check if user is member of tenant */\n isTenantMember(userId: string, tenantId: string): Promise<boolean>;\n}\n\n/**\n * In-memory RBAC service\n * For simple use cases or testing\n */\nexport class InMemoryRBAC implements PermissionChecker {\n private userPermissions: Map<string, Set<string>> = new Map();\n private userRoles: Map<string, Set<string>> = new Map();\n private rolePermissions: Map<string, Set<string>> = new Map();\n private tenantMembers: Map<string, Set<string>> = new Map();\n\n /**\n * Grant permission to user\n */\n grantPermission(userId: string, permission: string): void {\n if (!this.userPermissions.has(userId)) {\n this.userPermissions.set(userId, new Set());\n }\n this.userPermissions.get(userId)!.add(permission);\n }\n\n /**\n * Revoke permission from user\n */\n revokePermission(userId: string, permission: string): void {\n this.userPermissions.get(userId)?.delete(permission);\n }\n\n /**\n * Assign role to user\n */\n assignRole(userId: string, role: string): void {\n if (!this.userRoles.has(userId)) {\n this.userRoles.set(userId, new Set());\n }\n this.userRoles.get(userId)!.add(role);\n }\n\n /**\n * Remove role from user\n */\n removeRole(userId: string, role: string): void {\n this.userRoles.get(userId)?.delete(role);\n }\n\n /**\n * Define role with permissions\n */\n defineRole(roleName: string, permissions: string[]): void {\n this.rolePermissions.set(roleName, new Set(permissions));\n }\n\n /**\n * Add user to tenant\n */\n addTenantMember(tenantId: string, userId: string): void {\n if (!this.tenantMembers.has(tenantId)) {\n this.tenantMembers.set(tenantId, new Set());\n }\n this.tenantMembers.get(tenantId)!.add(userId);\n }\n\n /**\n * Remove user from tenant\n */\n removeTenantMember(tenantId: string, userId: string): void {\n this.tenantMembers.get(tenantId)?.delete(userId);\n }\n\n // PermissionChecker implementation\n\n async getUserPermissions(userId: string, _tenantId?: string): Promise<string[]> {\n const permissions = new Set<string>();\n\n // Direct permissions\n const direct = this.userPermissions.get(userId);\n if (direct) {\n direct.forEach((p) => permissions.add(p));\n }\n\n // Role-based permissions\n const roles = this.userRoles.get(userId);\n if (roles) {\n roles.forEach((role) => {\n const rolePerms = this.rolePermissions.get(role);\n if (rolePerms) {\n rolePerms.forEach((p) => permissions.add(p));\n }\n });\n }\n\n return Array.from(permissions);\n }\n\n async getUserRoles(userId: string, _tenantId?: string): Promise<string[]> {\n return Array.from(this.userRoles.get(userId) ?? []);\n }\n\n async hasPermission(\n userId: string,\n check: PermissionCheck,\n tenantId?: string\n ): Promise<boolean> {\n const permissions = await this.getUserPermissions(userId, tenantId);\n const permissionName = `${check.resource}:${check.action}`;\n\n // Check exact match\n if (permissions.includes(permissionName)) {\n return true;\n }\n\n // Check wildcard patterns\n // users:* matches users:read, users:create, etc.\n // *:read matches items:read, users:read, etc.\n // * matches everything\n for (const perm of permissions) {\n if (perm === \"*\") return true;\n if (perm === `${check.resource}:*`) return true;\n if (perm === `*:${check.action}`) return true;\n }\n\n return false;\n }\n\n async isTenantMember(userId: string, tenantId: string): Promise<boolean> {\n return this.tenantMembers.get(tenantId)?.has(userId) ?? false;\n }\n}\n\n/**\n * RBAC Service for authorization checks\n */\nexport class RBACService {\n constructor(private checker: PermissionChecker) {}\n\n /**\n * Get user's permissions\n */\n async getUserPermissions(userId: string, tenantId?: string): Promise<string[]> {\n return this.checker.getUserPermissions(userId, tenantId);\n }\n\n /**\n * Get user's roles\n */\n async getUserRoles(userId: string, tenantId?: string): Promise<string[]> {\n return this.checker.getUserRoles(userId, tenantId);\n }\n\n /**\n * Check if user has specific permission\n */\n async hasPermission(\n userId: string,\n check: PermissionCheck,\n tenantId?: string\n ): Promise<boolean> {\n return this.checker.hasPermission(userId, check, tenantId);\n }\n\n /**\n * Check if user has any of the specified permissions\n */\n async hasAnyPermission(\n userId: string,\n checks: PermissionCheck[],\n tenantId?: string\n ): Promise<boolean> {\n for (const check of checks) {\n if (await this.hasPermission(userId, check, tenantId)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Check if user has all specified permissions\n */\n async hasAllPermissions(\n userId: string,\n checks: PermissionCheck[],\n tenantId?: string\n ): Promise<boolean> {\n for (const check of checks) {\n if (!(await this.hasPermission(userId, check, tenantId))) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Check if user is member of tenant\n */\n async isTenantMember(userId: string, tenantId: string): Promise<boolean> {\n return this.checker.isTenantMember(userId, tenantId);\n }\n\n /**\n * Check if user has specific role\n */\n async hasRole(userId: string, role: string, tenantId?: string): Promise<boolean> {\n const roles = await this.getUserRoles(userId, tenantId);\n return roles.includes(role);\n }\n\n /**\n * Check if user has any of the specified roles\n */\n async hasAnyRole(userId: string, roles: string[], tenantId?: string): Promise<boolean> {\n const userRoles = await this.getUserRoles(userId, tenantId);\n return roles.some((role) => userRoles.includes(role));\n }\n}\n\n/**\n * Create RBAC service with in-memory checker\n */\nexport function createInMemoryRBAC(): { rbac: RBACService; checker: InMemoryRBAC } {\n const checker = new InMemoryRBAC();\n const rbac = new RBACService(checker);\n return { rbac, checker };\n}\n\n/**\n * Create RBAC service with custom checker\n */\nexport function createRBACService(checker: PermissionChecker): RBACService {\n return new RBACService(checker);\n}\n\n// ============================================\n// MIDDLEWARE FACTORIES\n// ============================================\n\n/**\n * Require authentication middleware\n */\nexport function requireAuth(): Middleware {\n return async (c: HonoContext, next: HonoNext): Promise<Response | void> => {\n const user = c.get(\"user\");\n\n if (!user) {\n throw new UnauthorizedError(\"Authentication required\");\n }\n\n return next();\n };\n}\n\n/**\n * Require specific permission middleware\n *\n * @example\n * ```typescript\n * app.get('/items', requirePermission('items', 'read'), handler);\n * app.post('/items', requirePermission('items', 'create'), handler);\n * ```\n */\nexport function requirePermission(\n resource: string,\n action: string,\n options: { scope?: \"tenant\" | \"global\" | \"own\"; checker?: PermissionChecker } = {}\n): Middleware {\n return async (c: HonoContext, next: HonoNext): Promise<Response | void> => {\n const user = c.get(\"user\");\n\n if (!user) {\n throw new UnauthorizedError(\"Authentication required\");\n }\n\n const check: PermissionCheck = {\n resource,\n action,\n scope: options.scope ?? \"tenant\",\n };\n\n // Use custom checker or check from user permissions\n let hasPermission = false;\n\n if (options.checker) {\n hasPermission = await options.checker.hasPermission(user.id, check, user.tenantId);\n } else {\n // Check from user's loaded permissions\n hasPermission = checkUserPermission(user, check);\n }\n\n if (!hasPermission) {\n throw new ForbiddenError(`Permission denied: ${resource}:${action}`);\n }\n\n return next();\n };\n}\n\n/**\n * Require any of specified permissions middleware\n */\nexport function requireAnyPermission(\n permissions: Array<{ resource: string; action: string }>,\n options: { checker?: PermissionChecker } = {}\n): Middleware {\n return async (c: HonoContext, next: HonoNext): Promise<Response | void> => {\n const user = c.get(\"user\");\n\n if (!user) {\n throw new UnauthorizedError(\"Authentication required\");\n }\n\n let hasAny = false;\n\n for (const perm of permissions) {\n const check: PermissionCheck = { resource: perm.resource, action: perm.action };\n\n if (options.checker) {\n if (await options.checker.hasPermission(user.id, check, user.tenantId)) {\n hasAny = true;\n break;\n }\n } else {\n if (checkUserPermission(user, check)) {\n hasAny = true;\n break;\n }\n }\n }\n\n if (!hasAny) {\n throw new ForbiddenError(\"Insufficient permissions\");\n }\n\n return next();\n };\n}\n\n/**\n * Require specific role middleware\n */\nexport function requireRole(role: string): Middleware {\n return async (c: HonoContext, next: HonoNext): Promise<Response | void> => {\n const user = c.get(\"user\");\n\n if (!user) {\n throw new UnauthorizedError(\"Authentication required\");\n }\n\n if (user.role !== role) {\n throw new ForbiddenError(`Role required: ${role}`);\n }\n\n return next();\n };\n}\n\n/**\n * Require any of specified roles middleware\n */\nexport function requireAnyRole(roles: string[]): Middleware {\n return async (c: HonoContext, next: HonoNext): Promise<Response | void> => {\n const user = c.get(\"user\");\n\n if (!user) {\n throw new UnauthorizedError(\"Authentication required\");\n }\n\n if (!user.role || !roles.includes(user.role)) {\n throw new ForbiddenError(`One of these roles required: ${roles.join(\", \")}`);\n }\n\n return next();\n };\n}\n\n/**\n * Require tenant membership middleware\n */\nexport function requireTenantMember(requiredRole?: string): Middleware {\n return async (c: HonoContext, next: HonoNext): Promise<Response | void> => {\n const user = c.get(\"user\");\n const tenant = c.get(\"tenant\");\n\n if (!user) {\n throw new UnauthorizedError(\"Authentication required\");\n }\n\n if (!tenant) {\n throw new ForbiddenError(\"Tenant context required\");\n }\n\n if (user.tenantId !== tenant.id) {\n throw new ForbiddenError(\"Not a member of this tenant\");\n }\n\n if (requiredRole && user.role !== requiredRole) {\n throw new ForbiddenError(`Role required in tenant: ${requiredRole}`);\n }\n\n return next();\n };\n}\n\n/**\n * Check user permission from loaded permissions array\n */\nfunction checkUserPermission(user: ContextUser, check: PermissionCheck): boolean {\n const permissionName = `${check.resource}:${check.action}`;\n\n for (const perm of user.permissions) {\n if (perm === permissionName) return true;\n if (perm === \"*\") return true;\n if (perm === `${check.resource}:*`) return true;\n if (perm === `*:${check.action}`) return true;\n }\n\n return false;\n}\n\n// ============================================\n// PERMISSION UTILITIES\n// ============================================\n\n/**\n * Parse permission string to PermissionCheck\n */\nexport function parsePermission(permission: string): PermissionCheck {\n const [resource, action] = permission.split(\":\");\n if (!resource || !action) {\n throw new Error(`Invalid permission format: ${permission}`);\n }\n return { resource, action };\n}\n\n/**\n * Create permission string from parts\n */\nexport function createPermission(resource: string, action: string): string {\n return `${resource}:${action}`;\n}\n\n/**\n * Standard CRUD permissions for a resource\n */\nexport function crudPermissions(resource: string): PermissionDefinition[] {\n return [\n { name: `${resource}:create`, resource, action: \"create\" },\n { name: `${resource}:read`, resource, action: \"read\" },\n { name: `${resource}:update`, resource, action: \"update\" },\n { name: `${resource}:delete`, resource, action: \"delete\" },\n { name: `${resource}:list`, resource, action: \"list\" },\n ];\n}\n\n/**\n * Standard roles\n */\nexport const StandardRoles: Record<string, RoleDefinition> = {\n OWNER: {\n name: \"owner\",\n displayName: \"Owner\",\n description: \"Full access to all resources\",\n permissions: [\"*\"],\n isSystem: true,\n },\n ADMIN: {\n name: \"admin\",\n displayName: \"Administrator\",\n description: \"Administrative access\",\n permissions: [\"*:read\", \"*:create\", \"*:update\", \"*:list\"],\n isSystem: true,\n },\n MEMBER: {\n name: \"member\",\n displayName: \"Member\",\n description: \"Standard member access\",\n permissions: [\"*:read\", \"*:list\"],\n isSystem: true,\n },\n VIEWER: {\n name: \"viewer\",\n displayName: \"Viewer\",\n description: \"Read-only access\",\n permissions: [\"*:read\", \"*:list\"],\n isSystem: true,\n },\n};\n"],"mappings":"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","names":[]}
|