@zakyyudha/node-authzkit 1.0.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.
Files changed (120) hide show
  1. package/.eslintrc.cjs +17 -0
  2. package/.prettierrc.json +10 -0
  3. package/.release-it.json +24 -0
  4. package/README.md +221 -0
  5. package/dist/src/classes/Authzkit.d.ts +110 -0
  6. package/dist/src/classes/Authzkit.js +189 -0
  7. package/dist/src/classes/Authzkit.js.map +1 -0
  8. package/dist/src/dashboard/router.d.ts +19 -0
  9. package/dist/src/dashboard/router.js +89 -0
  10. package/dist/src/dashboard/router.js.map +1 -0
  11. package/dist/src/dashboard/routes/permissions.d.ts +3 -0
  12. package/dist/src/dashboard/routes/permissions.js +39 -0
  13. package/dist/src/dashboard/routes/permissions.js.map +1 -0
  14. package/dist/src/dashboard/routes/roles.d.ts +3 -0
  15. package/dist/src/dashboard/routes/roles.js +39 -0
  16. package/dist/src/dashboard/routes/roles.js.map +1 -0
  17. package/dist/src/dashboard/routes/users.d.ts +3 -0
  18. package/dist/src/dashboard/routes/users.js +81 -0
  19. package/dist/src/dashboard/routes/users.js.map +1 -0
  20. package/dist/src/drivers/mongodb/mongo-connection.d.ts +15 -0
  21. package/dist/src/drivers/mongodb/mongo-connection.js +89 -0
  22. package/dist/src/drivers/mongodb/mongo-connection.js.map +1 -0
  23. package/dist/src/drivers/postgres/pg-connection.d.ts +17 -0
  24. package/dist/src/drivers/postgres/pg-connection.js +145 -0
  25. package/dist/src/drivers/postgres/pg-connection.js.map +1 -0
  26. package/dist/src/index.d.ts +19 -0
  27. package/dist/src/index.js +36 -0
  28. package/dist/src/index.js.map +1 -0
  29. package/dist/src/interfaces/Authorizable.d.ts +7 -0
  30. package/dist/src/interfaces/Authorizable.js +3 -0
  31. package/dist/src/interfaces/Authorizable.js.map +1 -0
  32. package/dist/src/interfaces/IAuthzkitConfig.d.ts +18 -0
  33. package/dist/src/interfaces/IAuthzkitConfig.js +3 -0
  34. package/dist/src/interfaces/IAuthzkitConfig.js.map +1 -0
  35. package/dist/src/interfaces/Permission.d.ts +4 -0
  36. package/dist/src/interfaces/Permission.js +3 -0
  37. package/dist/src/interfaces/Permission.js.map +1 -0
  38. package/dist/src/interfaces/Role.d.ts +5 -0
  39. package/dist/src/interfaces/Role.js +3 -0
  40. package/dist/src/interfaces/Role.js.map +1 -0
  41. package/dist/src/middleware/authzMiddleware.d.ts +17 -0
  42. package/dist/src/middleware/authzMiddleware.js +52 -0
  43. package/dist/src/middleware/authzMiddleware.js.map +1 -0
  44. package/dist/src/stores/IAuthzkitStore.d.ts +23 -0
  45. package/dist/src/stores/IAuthzkitStore.js +3 -0
  46. package/dist/src/stores/IAuthzkitStore.js.map +1 -0
  47. package/dist/src/stores/InMemoryAuthzkitStore.d.ts +28 -0
  48. package/dist/src/stores/InMemoryAuthzkitStore.js +83 -0
  49. package/dist/src/stores/InMemoryAuthzkitStore.js.map +1 -0
  50. package/dist/src/stores/MongoAuthzkitStore.d.ts +31 -0
  51. package/dist/src/stores/MongoAuthzkitStore.js +127 -0
  52. package/dist/src/stores/MongoAuthzkitStore.js.map +1 -0
  53. package/dist/src/stores/PgAuthzkitStore.d.ts +31 -0
  54. package/dist/src/stores/PgAuthzkitStore.js +133 -0
  55. package/dist/src/stores/PgAuthzkitStore.js.map +1 -0
  56. package/dist/src/utils/envConfig.d.ts +2 -0
  57. package/dist/src/utils/envConfig.js +68 -0
  58. package/dist/src/utils/envConfig.js.map +1 -0
  59. package/dist/tests/Authzkit.test.d.ts +1 -0
  60. package/dist/tests/Authzkit.test.js +126 -0
  61. package/dist/tests/Authzkit.test.js.map +1 -0
  62. package/dist/tests/MongoAuthzkitStore.test.d.ts +1 -0
  63. package/dist/tests/MongoAuthzkitStore.test.js +161 -0
  64. package/dist/tests/MongoAuthzkitStore.test.js.map +1 -0
  65. package/dist/tests/MongoAuthzkitStoreCustom.test.d.ts +1 -0
  66. package/dist/tests/MongoAuthzkitStoreCustom.test.js +65 -0
  67. package/dist/tests/MongoAuthzkitStoreCustom.test.js.map +1 -0
  68. package/dist/tests/PgAuthzkitStore.test.d.ts +1 -0
  69. package/dist/tests/PgAuthzkitStore.test.js +163 -0
  70. package/dist/tests/PgAuthzkitStore.test.js.map +1 -0
  71. package/dist/tests/PgAuthzkitStoreCustom.test.d.ts +1 -0
  72. package/dist/tests/PgAuthzkitStoreCustom.test.js +74 -0
  73. package/dist/tests/PgAuthzkitStoreCustom.test.js.map +1 -0
  74. package/examples/express-app.ts +65 -0
  75. package/jest.config.js +9 -0
  76. package/package.json +57 -0
  77. package/src/classes/Authzkit.ts +214 -0
  78. package/src/dashboard/router.ts +79 -0
  79. package/src/dashboard/routes/permissions.ts +38 -0
  80. package/src/dashboard/routes/roles.ts +38 -0
  81. package/src/dashboard/routes/users.ts +81 -0
  82. package/src/dashboard/web/README.md +73 -0
  83. package/src/dashboard/web/eslint.config.js +23 -0
  84. package/src/dashboard/web/index.html +13 -0
  85. package/src/dashboard/web/package.json +31 -0
  86. package/src/dashboard/web/pnpm-lock.yaml +2094 -0
  87. package/src/dashboard/web/public/vite.svg +1 -0
  88. package/src/dashboard/web/src/App.css +42 -0
  89. package/src/dashboard/web/src/App.tsx +26 -0
  90. package/src/dashboard/web/src/assets/react.svg +1 -0
  91. package/src/dashboard/web/src/components/Navbar.tsx +53 -0
  92. package/src/dashboard/web/src/index.css +138 -0
  93. package/src/dashboard/web/src/main.tsx +10 -0
  94. package/src/dashboard/web/src/pages/PermissionsPage.tsx +87 -0
  95. package/src/dashboard/web/src/pages/RolesPage.tsx +98 -0
  96. package/src/dashboard/web/src/pages/UsersPage.tsx +146 -0
  97. package/src/dashboard/web/src/services/api.ts +59 -0
  98. package/src/dashboard/web/tsconfig.app.json +28 -0
  99. package/src/dashboard/web/tsconfig.json +7 -0
  100. package/src/dashboard/web/tsconfig.node.json +26 -0
  101. package/src/dashboard/web/vite.config.ts +8 -0
  102. package/src/drivers/mongodb/mongo-connection.ts +98 -0
  103. package/src/drivers/postgres/pg-connection.ts +159 -0
  104. package/src/index.ts +19 -0
  105. package/src/interfaces/Authorizable.ts +8 -0
  106. package/src/interfaces/IAuthzkitConfig.ts +19 -0
  107. package/src/interfaces/Permission.ts +4 -0
  108. package/src/interfaces/Role.ts +5 -0
  109. package/src/middleware/authzMiddleware.ts +60 -0
  110. package/src/stores/IAuthzkitStore.ts +33 -0
  111. package/src/stores/InMemoryAuthzkitStore.ts +101 -0
  112. package/src/stores/MongoAuthzkitStore.ts +171 -0
  113. package/src/stores/PgAuthzkitStore.ts +191 -0
  114. package/src/utils/envConfig.ts +70 -0
  115. package/tests/Authzkit.test.ts +157 -0
  116. package/tests/MongoAuthzkitStore.test.ts +204 -0
  117. package/tests/MongoAuthzkitStoreCustom.test.ts +75 -0
  118. package/tests/PgAuthzkitStore.test.ts +207 -0
  119. package/tests/PgAuthzkitStoreCustom.test.ts +90 -0
  120. package/tsconfig.json +37 -0
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "erasableSyntaxOnly": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noUncheckedSideEffectImports": true
26
+ },
27
+ "include": ["src"]
28
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": ["vite.config.ts"]
26
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ base: './', // Ensure relative paths for assets
8
+ });
@@ -0,0 +1,98 @@
1
+ import { MongoClient, Db } from 'mongodb';
2
+ import { IAuthzkitConfig } from '../../interfaces/IAuthzkitConfig';
3
+
4
+ export class MongoConnection {
5
+ private static instance: MongoConnection | null = null;
6
+ private client: MongoClient | null = null;
7
+ private db: Db | null = null;
8
+ private config: IAuthzkitConfig;
9
+
10
+ private constructor(config: IAuthzkitConfig) {
11
+ this.config = config;
12
+ }
13
+
14
+ public static getInstance(config: IAuthzkitConfig, client?: MongoClient): MongoConnection {
15
+ // If an existing instance exists and same URI/DB, return it.
16
+ if (
17
+ MongoConnection.instance &&
18
+ MongoConnection.instance.config.connection.uri === config.connection.uri &&
19
+ MongoConnection.instance.config.connection.database === config.connection.database &&
20
+ !client
21
+ ) {
22
+ return MongoConnection.instance;
23
+ }
24
+
25
+ // Create a new instance
26
+ MongoConnection.instance = new MongoConnection(config);
27
+ if (client) {
28
+ MongoConnection.instance.client = client;
29
+ if (config.connection.database) {
30
+ MongoConnection.instance.db = client.db(config.connection.database);
31
+ }
32
+ }
33
+ return MongoConnection.instance;
34
+ }
35
+
36
+ public async connect(): Promise<void> {
37
+ const dbName = this.config.connection.database;
38
+ if (!dbName) {
39
+ throw new Error('Database name not provided in configuration.');
40
+ }
41
+
42
+ // If client is already set (e.g., from mongodb-memory-server), just ensure db is set
43
+ if (this.client) {
44
+ // Assuming client options might not directly expose dbName in a standard way across versions,
45
+ // relying on the configured dbName.
46
+ if (!this.db) {
47
+ this.db = this.client.db(dbName);
48
+ }
49
+ console.log('MongoDB client already provided/connected.');
50
+ return;
51
+ }
52
+
53
+ if (!this.config.connection.uri) {
54
+ throw new Error('MongoDB URI not provided in configuration.');
55
+ }
56
+
57
+ try {
58
+ this.client = await MongoClient.connect(this.config.connection.uri);
59
+ this.db = this.client.db(dbName);
60
+ console.log('Connected to MongoDB successfully.');
61
+ } catch (error) {
62
+ console.error('Failed to connect to MongoDB', error);
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ public async disconnect(): Promise<void> {
68
+ if (this.client) {
69
+ await this.client.close();
70
+ this.client = null;
71
+ this.db = null;
72
+ console.log('Disconnected from MongoDB.');
73
+ }
74
+ }
75
+
76
+ public getDb(): Db {
77
+ if (!this.db) {
78
+ throw new Error('Database not connected. Call connect() first.');
79
+ }
80
+ return this.db;
81
+ }
82
+
83
+ public getConfig(): IAuthzkitConfig {
84
+ return this.config;
85
+ }
86
+
87
+ public getCollectionName(name: keyof NonNullable<IAuthzkitConfig['models']>): string {
88
+ const models = this.config.models || {};
89
+ const defaults: Record<string, string> = {
90
+ users: 'users',
91
+ roles: 'roles',
92
+ permissions: 'permissions',
93
+ user_roles: 'user_roles',
94
+ user_permissions: 'user_permissions',
95
+ };
96
+ return models[name] || defaults[name] || name;
97
+ }
98
+ }
@@ -0,0 +1,159 @@
1
+ import { Pool, QueryResult, Client as PgClient } from 'pg';
2
+ import { IMemoryDb } from 'pg-mem';
3
+ import { IAuthzkitConfig } from '../../interfaces/IAuthzkitConfig';
4
+
5
+ export class PgConnection {
6
+ private static instance: PgConnection | null = null;
7
+ private poolOrClient: Pool | PgClient | null = null;
8
+ private config: IAuthzkitConfig;
9
+
10
+ private constructor(config: IAuthzkitConfig) {
11
+ this.config = config;
12
+ }
13
+
14
+ public static getInstance(config: IAuthzkitConfig, inMemoryDb?: IMemoryDb): PgConnection {
15
+ // Check if instance exists and config matches (simplified)
16
+ if (
17
+ PgConnection.instance &&
18
+ PgConnection.instance.config.connection.uri === config.connection.uri &&
19
+ !inMemoryDb
20
+ ) {
21
+ return PgConnection.instance;
22
+ }
23
+
24
+ const instance = new PgConnection(config);
25
+ if (inMemoryDb) {
26
+ // Use pg-mem's adapter
27
+ instance.poolOrClient = new (inMemoryDb.adapters.createPg().Client)() as PgClient;
28
+ }
29
+ PgConnection.instance = instance;
30
+ return PgConnection.instance;
31
+ }
32
+
33
+ public async connect(): Promise<void> {
34
+ if (
35
+ this.poolOrClient &&
36
+ (this.poolOrClient instanceof Pool || this.poolOrClient instanceof PgClient)
37
+ ) {
38
+ console.log('Already connected to PostgreSQL.');
39
+ return;
40
+ }
41
+ if (this.poolOrClient) {
42
+ console.log('In-memory PostgreSQL client already provided.');
43
+ return;
44
+ }
45
+
46
+ try {
47
+ // Create pool using config
48
+ const poolConfig: any = {};
49
+ if (this.config.connection.uri) {
50
+ poolConfig.connectionString = this.config.connection.uri;
51
+ } else {
52
+ poolConfig.host = this.config.connection.host;
53
+ poolConfig.port = this.config.connection.port;
54
+ poolConfig.user = this.config.connection.user;
55
+ poolConfig.password = this.config.connection.password;
56
+ poolConfig.database = this.config.connection.database;
57
+ }
58
+
59
+ this.poolOrClient = new Pool(poolConfig);
60
+ await (this.poolOrClient as Pool).query('SELECT 1');
61
+ console.log('Connected to PostgreSQL successfully.');
62
+ } catch (error) {
63
+ console.error('Failed to connect to PostgreSQL', error);
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ public async disconnect(): Promise<void> {
69
+ if (this.poolOrClient && this.poolOrClient instanceof Pool) {
70
+ await this.poolOrClient.end();
71
+ this.poolOrClient = null;
72
+ console.log('Disconnected from PostgreSQL.');
73
+ } else if (this.poolOrClient) {
74
+ console.log('In-memory PostgreSQL instance, no explicit disconnect needed for pool.');
75
+ this.poolOrClient = null;
76
+ }
77
+ }
78
+
79
+ public async query(text: string, params?: any[]): Promise<QueryResult> {
80
+ if (!this.poolOrClient) {
81
+ throw new Error('Database not connected. Call connect() first.');
82
+ }
83
+ return (this.poolOrClient as Pool | PgClient).query(text, params);
84
+ }
85
+
86
+ public getTableName(name: keyof NonNullable<IAuthzkitConfig['models']>): string {
87
+ const models = this.config.models || {};
88
+ const defaults: Record<string, string> = {
89
+ users: 'users',
90
+ roles: 'roles',
91
+ permissions: 'permissions',
92
+ user_roles: 'user_roles',
93
+ user_permissions: 'user_permissions',
94
+ };
95
+ return models[name] || defaults[name] || name;
96
+ }
97
+
98
+ public async initSchema(): Promise<void> {
99
+ const permissionsTable = this.getTableName('permissions');
100
+ const rolesTable = this.getTableName('roles');
101
+ const userRolesTable = this.getTableName('user_roles');
102
+ const userPermissionsTable = this.getTableName('user_permissions');
103
+
104
+ await this.query(`
105
+ CREATE TABLE IF NOT EXISTS ${permissionsTable} (
106
+ name VARCHAR(255) PRIMARY KEY,
107
+ guard_name VARCHAR(255)
108
+ );
109
+ `);
110
+ await this.query(`
111
+ CREATE TABLE IF NOT EXISTS ${rolesTable} (
112
+ name VARCHAR(255) PRIMARY KEY,
113
+ guard_name VARCHAR(255),
114
+ permissions TEXT[] DEFAULT '{}'
115
+ );
116
+ `);
117
+ await this.query(`
118
+ CREATE TABLE IF NOT EXISTS ${userRolesTable} (
119
+ user_id VARCHAR(255) NOT NULL,
120
+ role_name VARCHAR(255) REFERENCES ${rolesTable}(name) ON DELETE CASCADE,
121
+ PRIMARY KEY (user_id, role_name)
122
+ );
123
+ `);
124
+ await this.query(`
125
+ CREATE TABLE IF NOT EXISTS ${userPermissionsTable} (
126
+ user_id VARCHAR(255) NOT NULL,
127
+ permission_name VARCHAR(255) REFERENCES ${permissionsTable}(name) ON DELETE CASCADE,
128
+ PRIMARY KEY (user_id, permission_name)
129
+ );
130
+ `);
131
+ console.log('PostgreSQL schema initialized.');
132
+ }
133
+
134
+ public async truncateTables(): Promise<void> {
135
+ const permissionsTable = this.getTableName('permissions');
136
+ const rolesTable = this.getTableName('roles');
137
+ const userRolesTable = this.getTableName('user_roles');
138
+ const userPermissionsTable = this.getTableName('user_permissions');
139
+
140
+ await this.query(`TRUNCATE TABLE ${userPermissionsTable} CASCADE;`);
141
+ await this.query(`TRUNCATE TABLE ${userRolesTable} CASCADE;`);
142
+ await this.query(`TRUNCATE TABLE ${rolesTable} CASCADE;`);
143
+ await this.query(`TRUNCATE TABLE ${permissionsTable} CASCADE;`);
144
+ console.log('PostgreSQL tables truncated.');
145
+ }
146
+
147
+ public async dropSchema(): Promise<void> {
148
+ const permissionsTable = this.getTableName('permissions');
149
+ const rolesTable = this.getTableName('roles');
150
+ const userRolesTable = this.getTableName('user_roles');
151
+ const userPermissionsTable = this.getTableName('user_permissions');
152
+
153
+ await this.query(`DROP TABLE IF EXISTS ${userPermissionsTable} CASCADE;`);
154
+ await this.query(`DROP TABLE IF EXISTS ${userRolesTable} CASCADE;`);
155
+ await this.query(`DROP TABLE IF EXISTS ${rolesTable} CASCADE;`);
156
+ await this.query(`DROP TABLE IF EXISTS ${permissionsTable} CASCADE;`);
157
+ console.log('PostgreSQL schema dropped.');
158
+ }
159
+ }
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ export { Authzkit } from './classes/Authzkit';
2
+ export { Authorizable } from './interfaces/Authorizable';
3
+ export { Permission } from './interfaces/Permission';
4
+ export { Role } from './interfaces/Role';
5
+ export { MongoConnection } from './drivers/mongodb/mongo-connection';
6
+ export * from './interfaces/Authorizable';
7
+ export * from './interfaces/Permission';
8
+ export * from './interfaces/Role';
9
+ export * from './interfaces/IAuthzkitConfig';
10
+ export * from './stores/IAuthzkitStore';
11
+ export * from './stores/InMemoryAuthzkitStore';
12
+ export * from './stores/MongoAuthzkitStore';
13
+ export * from './stores/PgAuthzkitStore';
14
+ export * from './classes/Authzkit';
15
+ export * from './drivers/mongodb/mongo-connection';
16
+ export * from './drivers/postgres/pg-connection';
17
+ export * from './middleware/authzMiddleware';
18
+ export * from './dashboard/router';
19
+ export * from './utils/envConfig';
@@ -0,0 +1,8 @@
1
+ import { Role } from './Role';
2
+ import { Permission } from './Permission';
3
+
4
+ export interface Authorizable {
5
+ id: string | number;
6
+ roles: Role[];
7
+ permissions: Permission[];
8
+ }
@@ -0,0 +1,19 @@
1
+ export interface IAuthzkitConfig {
2
+ connection: {
3
+ type: 'mongodb' | 'postgres' | 'memory';
4
+ uri?: string; // For MongoDB and Postgres
5
+ database?: string; // For MongoDB
6
+ // Postgres specific
7
+ host?: string;
8
+ port?: number;
9
+ user?: string;
10
+ password?: string;
11
+ };
12
+ models?: {
13
+ users?: string; // Table/Collection name for users (default: 'users')
14
+ roles?: string; // Table/Collection name for roles (default: 'roles')
15
+ permissions?: string; // Table/Collection name for permissions (default: 'permissions')
16
+ user_roles?: string; // Table/Collection name for user-role relation (default: 'user_roles')
17
+ user_permissions?: string; // Table/Collection name for user-permission relation (default: 'user_permissions')
18
+ };
19
+ }
@@ -0,0 +1,4 @@
1
+ export interface Permission {
2
+ name: string;
3
+ guard_name?: string;
4
+ }
@@ -0,0 +1,5 @@
1
+ export interface Role {
2
+ name: string;
3
+ guard_name?: string;
4
+ permissions: string[]; // Names of permissions
5
+ }
@@ -0,0 +1,60 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { Authzkit } from '../classes/Authzkit';
3
+ import { Authorizable } from '../interfaces/Authorizable';
4
+
5
+ // Extend the Express Request type to include a user property
6
+ declare module 'express' {
7
+ interface Request {
8
+ user?: Authorizable;
9
+ }
10
+ }
11
+
12
+ /**
13
+ * Express middleware for role-based or permission-based authorization.
14
+ * Expects `req.user` to be an object conforming to `Authorizable` interface.
15
+ *
16
+ * @param requiredPermissionsAndRoles A single string or an array of strings representing
17
+ * the required roles or permissions.
18
+ * If multiple are provided, the user needs to have AT LEAST ONE of them.
19
+ * @returns An Express middleware function.
20
+ */
21
+ export const authorize = (requiredPermissionsAndRoles: string | string[]) => {
22
+ return async (req: Request, res: Response, next: NextFunction) => {
23
+ try {
24
+ const authzkit = Authzkit.getInstance(); // Get the singleton instance
25
+
26
+ const user = req.user;
27
+
28
+ if (!user) {
29
+ return res.status(401).send('Authentication required.');
30
+ }
31
+
32
+ const requirements = Array.isArray(requiredPermissionsAndRoles)
33
+ ? requiredPermissionsAndRoles
34
+ : [requiredPermissionsAndRoles];
35
+
36
+ // Check if the user has at least one of the required roles or permissions
37
+ let hasAccess = false;
38
+ for (const requirement of requirements) {
39
+ // First, check if it's a role
40
+ if (await authzkit.hasRole(user, requirement)) {
41
+ hasAccess = true;
42
+ break;
43
+ }
44
+ // If not a role, check if it's a permission
45
+ if (await authzkit.hasPermission(user, requirement)) {
46
+ hasAccess = true;
47
+ break;
48
+ }
49
+ }
50
+
51
+ if (hasAccess) {
52
+ next();
53
+ } else {
54
+ res.status(403).send('Forbidden: Insufficient permissions.');
55
+ }
56
+ } catch (error) {
57
+ next(error);
58
+ }
59
+ };
60
+ };
@@ -0,0 +1,33 @@
1
+ import { Permission } from '../interfaces/Permission';
2
+ import { Role } from '../interfaces/Role';
3
+
4
+ export interface IAuthzkitStore {
5
+ // Permissions
6
+ getPermission(name: string): Promise<Permission | undefined>;
7
+ getPermissions(): Promise<Permission[]>; // Added for dashboard
8
+ setPermission(permission: Permission): Promise<void>;
9
+ hasPermission(name: string): Promise<boolean>;
10
+ deletePermission(name: string): Promise<void>;
11
+
12
+ // Roles
13
+ getRole(name: string): Promise<Role | undefined>;
14
+ getRoles(): Promise<Role[]>; // Added for dashboard
15
+ setRole(role: Role): Promise<void>;
16
+ hasRole(name: string): Promise<boolean>;
17
+ deleteRole(name: string): Promise<void>;
18
+
19
+ // User Roles
20
+ getUserRoles(userId: string | number): Promise<Set<string>>;
21
+ addUserRole(userId: string | number, roleName: string): Promise<void>;
22
+ removeUserRole(userId: string | number, roleName: string): Promise<void>;
23
+ hasUserRole(userId: string | number, roleName: string): Promise<boolean>;
24
+
25
+ // User Permissions
26
+ getUserPermissions(userId: string | number): Promise<Set<string>>;
27
+ addUserPermission(userId: string | number, permissionName: string): Promise<void>;
28
+ removeUserPermission(userId: string | number, permissionName: string): Promise<void>;
29
+ hasUserPermission(userId: string | number, permissionName: string): Promise<boolean>;
30
+
31
+ // Reset
32
+ reset(): Promise<void>;
33
+ }
@@ -0,0 +1,101 @@
1
+ import { IAuthzkitStore } from './IAuthzkitStore';
2
+ import { Permission } from '../interfaces/Permission';
3
+ import { Role } from '../interfaces/Role';
4
+
5
+ export class InMemoryAuthzkitStore implements IAuthzkitStore {
6
+ private permissions: Map<string, Permission> = new Map();
7
+ private roles: Map<string, Role> = new Map();
8
+ private userRoles: Map<string | number, Set<string>> = new Map();
9
+ private userPermissions: Map<string | number, Set<string>> = new Map();
10
+
11
+ async getPermission(name: string): Promise<Permission | undefined> {
12
+ return this.permissions.get(name);
13
+ }
14
+
15
+ async getPermissions(): Promise<Permission[]> {
16
+ return Array.from(this.permissions.values());
17
+ }
18
+
19
+ async setPermission(permission: Permission): Promise<void> {
20
+ this.permissions.set(permission.name, permission);
21
+ }
22
+
23
+ async hasPermission(name: string): Promise<boolean> {
24
+ return this.permissions.has(name);
25
+ }
26
+
27
+ async deletePermission(name: string): Promise<void> {
28
+ this.permissions.delete(name);
29
+ }
30
+
31
+ async getRole(name: string): Promise<Role | undefined> {
32
+ return this.roles.get(name);
33
+ }
34
+
35
+ async getRoles(): Promise<Role[]> {
36
+ return Array.from(this.roles.values());
37
+ }
38
+
39
+ async setRole(role: Role): Promise<void> {
40
+ this.roles.set(role.name, role);
41
+ }
42
+
43
+ async hasRole(name: string): Promise<boolean> {
44
+ return this.roles.has(name);
45
+ }
46
+
47
+ async deleteRole(name: string): Promise<void> {
48
+ this.roles.delete(name);
49
+ }
50
+
51
+ async getUserRoles(userId: string | number): Promise<Set<string>> {
52
+ if (!this.userRoles.has(userId)) {
53
+ this.userRoles.set(userId, new Set());
54
+ }
55
+ return this.userRoles.get(userId)!;
56
+ }
57
+
58
+ async addUserRole(userId: string | number, roleName: string): Promise<void> {
59
+ (await this.getUserRoles(userId)).add(roleName);
60
+ }
61
+
62
+ async removeUserRole(userId: string | number, roleName: string): Promise<void> {
63
+ (await this.getUserRoles(userId)).delete(roleName);
64
+ if ((await this.getUserRoles(userId)).size === 0) {
65
+ this.userRoles.delete(userId);
66
+ }
67
+ }
68
+
69
+ async hasUserRole(userId: string | number, roleName: string): Promise<boolean> {
70
+ return (await this.getUserRoles(userId)).has(roleName);
71
+ }
72
+
73
+ async getUserPermissions(userId: string | number): Promise<Set<string>> {
74
+ if (!this.userPermissions.has(userId)) {
75
+ this.userPermissions.set(userId, new Set());
76
+ }
77
+ return this.userPermissions.get(userId)!;
78
+ }
79
+
80
+ async addUserPermission(userId: string | number, permissionName: string): Promise<void> {
81
+ (await this.getUserPermissions(userId)).add(permissionName);
82
+ }
83
+
84
+ async removeUserPermission(userId: string | number, permissionName: string): Promise<void> {
85
+ (await this.getUserPermissions(userId)).delete(permissionName);
86
+ if ((await this.getUserPermissions(userId)).size === 0) {
87
+ this.userPermissions.delete(userId);
88
+ }
89
+ }
90
+
91
+ async hasUserPermission(userId: string | number, permissionName: string): Promise<boolean> {
92
+ return (await this.getUserPermissions(userId)).has(permissionName);
93
+ }
94
+
95
+ async reset(): Promise<void> {
96
+ this.permissions.clear();
97
+ this.roles.clear();
98
+ this.userRoles.clear();
99
+ this.userPermissions.clear();
100
+ }
101
+ }