@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,204 @@
1
+ import { Authzkit } from '../src/classes/Authzkit';
2
+ import { Authorizable } from '../src/interfaces/Authorizable';
3
+ import { MongoConnection } from '../src/drivers/mongodb/mongo-connection';
4
+ import { MongoAuthzkitStore } from '../src/stores/MongoAuthzkitStore';
5
+ import { MongoMemoryServer } from 'mongodb-memory-server';
6
+ import { MongoClient } from 'mongodb';
7
+ import { IAuthzkitConfig } from '../src/interfaces/IAuthzkitConfig';
8
+
9
+ // Increase Jest timeout for MongoDB operations
10
+ jest.setTimeout(30000); // 30 seconds
11
+
12
+ describe('MongoAuthzkitStore', () => {
13
+ let mongod: MongoMemoryServer;
14
+ let mongoClient: MongoClient;
15
+ let mongoConnection: MongoConnection;
16
+ let mongoAuthzkitStore: MongoAuthzkitStore;
17
+ let authzkit: Authzkit;
18
+ let user: Authorizable;
19
+
20
+ beforeAll(async () => {
21
+ mongod = await MongoMemoryServer.create();
22
+ const uri = mongod.getUri();
23
+ const dbName = 'authzkit_test_db';
24
+
25
+ // Connect MongoClient and pass it to MongoConnection
26
+ mongoClient = new MongoClient(uri);
27
+ await mongoClient.connect();
28
+
29
+ const config: IAuthzkitConfig = {
30
+ connection: {
31
+ type: 'mongodb',
32
+ uri: uri,
33
+ database: dbName,
34
+ },
35
+ };
36
+
37
+ mongoConnection = MongoConnection.getInstance(config, mongoClient);
38
+ await mongoConnection.connect(); // This should now just set the db instance
39
+
40
+ // Ensure the database is clean before running tests
41
+ await mongoConnection.getDb().dropDatabase();
42
+ });
43
+
44
+ beforeEach(async () => {
45
+ // Re-initialize store and authzkit for each test to ensure isolation
46
+ mongoAuthzkitStore = new MongoAuthzkitStore(mongoConnection);
47
+ Authzkit.getInstance(mongoAuthzkitStore);
48
+ authzkit = Authzkit.getInstance();
49
+ await authzkit.reset(); // Clear all data for current test
50
+
51
+ // Define some permissions and roles
52
+ await authzkit.definePermission('create_post');
53
+ await authzkit.definePermission('edit_post');
54
+ await authzkit.definePermission('delete_post');
55
+ await authzkit.definePermission('view_dashboard');
56
+
57
+ await authzkit.defineRole('admin', [
58
+ 'create_post',
59
+ 'edit_post',
60
+ 'delete_post',
61
+ 'view_dashboard',
62
+ ]);
63
+ await authzkit.defineRole('editor', ['create_post', 'edit_post']);
64
+ await authzkit.defineRole('viewer', ['view_dashboard']);
65
+
66
+ user = { id: 'user1', roles: [], permissions: [] }; // Changed to string for consistency with MongoDB IDs
67
+ });
68
+
69
+ afterAll(async () => {
70
+ await mongoConnection.getDb().dropDatabase(); // Clean up database after all tests
71
+ await mongoClient.close(); // Close the MongoClient connection
72
+ await mongod.stop(); // Stop the in-memory MongoDB server
73
+ });
74
+
75
+ // --- Permission Definition Tests ---
76
+ test('should define a permission', async () => {
77
+ const perm = await authzkit.definePermission('new_mongo_permission');
78
+ expect(perm).toEqual({ name: 'new_mongo_permission', guard_name: undefined });
79
+ await expect(authzkit.definePermission('new_mongo_permission')).rejects.toThrow(
80
+ "Permission 'new_mongo_permission' already exists."
81
+ );
82
+ });
83
+
84
+ // --- Role Definition Tests ---
85
+ test('should define a role with permissions', async () => {
86
+ const role = await authzkit.defineRole('mongo_moderator', ['edit_post']);
87
+ expect(role).toEqual({
88
+ name: 'mongo_moderator',
89
+ permissions: ['edit_post'],
90
+ guard_name: undefined,
91
+ });
92
+ await expect(authzkit.defineRole('admin')).rejects.toThrow("Role 'admin' already exists.");
93
+ });
94
+
95
+ test('should throw error if role is defined with non-existent permission', async () => {
96
+ await expect(
97
+ authzkit.defineRole('bad_mongo_role', ['non_existent_mongo_permission'])
98
+ ).rejects.toThrow(
99
+ "Permission 'non_existent_mongo_permission' not found when defining role 'bad_mongo_role'."
100
+ );
101
+ });
102
+
103
+ // --- Assignment Tests ---
104
+ test('should assign a role to a user', async () => {
105
+ await authzkit.assignRole(user, 'editor');
106
+ expect(await authzkit.hasRole(user, 'editor')).toBe(true);
107
+ expect(await authzkit.hasRole(user, 'admin')).toBe(false);
108
+ });
109
+
110
+ test('should assign a direct permission to a user', async () => {
111
+ await authzkit.assignPermission(user, 'create_post');
112
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(true);
113
+ expect(await authzkit.hasPermission(user, 'edit_post')).toBe(false);
114
+ });
115
+
116
+ test('should throw error if assigning non-existent role', async () => {
117
+ await expect(authzkit.assignRole(user, 'non_existent_mongo_role')).rejects.toThrow(
118
+ "Role 'non_existent_mongo_role' not found."
119
+ );
120
+ });
121
+
122
+ test('should throw error if assigning non-existent permission', async () => {
123
+ await expect(authzkit.assignPermission(user, 'non_existent_mongo_permission')).rejects.toThrow(
124
+ "Permission 'non_existent_mongo_permission' not found."
125
+ );
126
+ });
127
+
128
+ // --- Revocation Tests ---
129
+ test('should revoke a role from a user', async () => {
130
+ await authzkit.assignRole(user, 'editor');
131
+ expect(await authzkit.hasRole(user, 'editor')).toBe(true);
132
+ await authzkit.revokeRole(user, 'editor');
133
+ expect(await authzkit.hasRole(user, 'editor')).toBe(false);
134
+ });
135
+
136
+ test('should revoke a direct permission from a user', async () => {
137
+ await authzkit.assignPermission(user, 'create_post');
138
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(true);
139
+ await authzkit.revokePermission(user, 'create_post');
140
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(false);
141
+ });
142
+
143
+ // --- Has Role/Permission Tests ---
144
+ test('user with role should have role', async () => {
145
+ await authzkit.assignRole(user, 'admin');
146
+ expect(await authzkit.hasRole(user, 'admin')).toBe(true);
147
+ });
148
+
149
+ test('user without role should not have role', async () => {
150
+ expect(await authzkit.hasRole(user, 'admin')).toBe(false);
151
+ });
152
+
153
+ test('user with direct permission should have permission', async () => {
154
+ await authzkit.assignPermission(user, 'edit_post');
155
+ expect(await authzkit.hasPermission(user, 'edit_post')).toBe(true);
156
+ });
157
+
158
+ test('user with role having permission should have permission', async () => {
159
+ await authzkit.assignRole(user, 'editor'); // editor has create_post, edit_post
160
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(true);
161
+ expect(await authzkit.hasPermission(user, 'edit_post')).toBe(true);
162
+ expect(await authzkit.hasPermission(user, 'delete_post')).toBe(false);
163
+ });
164
+
165
+ test('user with admin role should have all admin permissions', async () => {
166
+ await authzkit.assignRole(user, 'admin');
167
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(true);
168
+ expect(await authzkit.hasPermission(user, 'edit_post')).toBe(true);
169
+ expect(await authzkit.hasPermission(user, 'delete_post')).toBe(true);
170
+ expect(await authzkit.hasPermission(user, 'view_dashboard')).toBe(true);
171
+ });
172
+
173
+ test('user with no permissions/roles should not have any permissions', async () => {
174
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(false);
175
+ expect(await authzkit.hasRole(user, 'admin')).toBe(false);
176
+ });
177
+
178
+ test('roleHasPermission should work correctly', async () => {
179
+ expect(await authzkit.roleHasPermission('admin', 'create_post')).toBe(true);
180
+ expect(await authzkit.roleHasPermission('editor', 'delete_post')).toBe(false);
181
+ expect(await authzkit.roleHasPermission('viewer', 'view_dashboard')).toBe(true);
182
+ expect(await authzkit.roleHasPermission('non_existent_role', 'some_permission')).toBe(false);
183
+ });
184
+
185
+ // --- Reset Tests ---
186
+ test('reset should clear all permissions, roles, and assignments', async () => {
187
+ await authzkit.definePermission('test_mongo_perm');
188
+ await authzkit.defineRole('test_mongo_role', ['test_mongo_perm']);
189
+ await authzkit.assignRole(user, 'test_mongo_role');
190
+ await authzkit.assignPermission(user, 'test_mongo_perm');
191
+
192
+ await authzkit.reset();
193
+
194
+ // After reset, defining again should not throw an error (as they were cleared)
195
+ await expect(authzkit.definePermission('test_mongo_perm')).resolves.not.toThrow();
196
+ await expect(
197
+ authzkit.defineRole('test_mongo_role', ['test_mongo_perm'])
198
+ ).resolves.not.toThrow();
199
+
200
+ // User should no longer have the role/permission
201
+ expect(await authzkit.hasRole(user, 'test_mongo_role')).toBe(false);
202
+ expect(await authzkit.hasPermission(user, 'test_mongo_perm')).toBe(false);
203
+ });
204
+ });
@@ -0,0 +1,75 @@
1
+ import { Authzkit } from '../src/classes/Authzkit';
2
+ import { MongoConnection } from '../src/drivers/mongodb/mongo-connection';
3
+ import { MongoAuthzkitStore } from '../src/stores/MongoAuthzkitStore';
4
+ import { MongoMemoryServer } from 'mongodb-memory-server';
5
+ import { MongoClient } from 'mongodb';
6
+ import { IAuthzkitConfig } from '../src/interfaces/IAuthzkitConfig';
7
+
8
+ // Increase Jest timeout for MongoDB operations
9
+ jest.setTimeout(30000); // 30 seconds
10
+
11
+ describe('MongoAuthzkitStore (Custom Collections)', () => {
12
+ let mongod: MongoMemoryServer;
13
+ let mongoClient: MongoClient;
14
+ let mongoConnection: MongoConnection;
15
+ let mongoAuthzkitStore: MongoAuthzkitStore;
16
+ let authzkit: Authzkit;
17
+
18
+ beforeAll(async () => {
19
+ mongod = await MongoMemoryServer.create();
20
+ const uri = mongod.getUri();
21
+ const dbName = 'authzkit_test_custom_db';
22
+
23
+ // Connect MongoClient and pass it to MongoConnection
24
+ mongoClient = new MongoClient(uri);
25
+ await mongoClient.connect();
26
+
27
+ const config: IAuthzkitConfig = {
28
+ connection: {
29
+ type: 'mongodb',
30
+ uri: uri,
31
+ database: dbName,
32
+ },
33
+ models: {
34
+ users: 'custom_users',
35
+ roles: 'custom_roles',
36
+ permissions: 'custom_permissions',
37
+ user_roles: 'custom_user_roles',
38
+ user_permissions: 'custom_user_permissions',
39
+ },
40
+ };
41
+
42
+ mongoConnection = MongoConnection.getInstance(config, mongoClient);
43
+ await mongoConnection.connect(); // This should now just set the db instance
44
+
45
+ // Ensure the database is clean before running tests
46
+ await mongoConnection.getDb().dropDatabase();
47
+ });
48
+
49
+ beforeEach(async () => {
50
+ // Re-initialize store and authzkit for each test to ensure isolation
51
+ mongoAuthzkitStore = new MongoAuthzkitStore(mongoConnection);
52
+ Authzkit.getInstance(mongoAuthzkitStore);
53
+ authzkit = Authzkit.getInstance();
54
+ await authzkit.reset(); // Clear all data for current test
55
+
56
+ await authzkit.definePermission('custom_perm');
57
+ });
58
+
59
+ afterAll(async () => {
60
+ await mongoConnection.getDb().dropDatabase(); // Clean up database after all tests
61
+ await mongoClient.close(); // Close the MongoClient connection
62
+ await mongod.stop(); // Stop the in-memory MongoDB server
63
+ });
64
+
65
+ test('should store data in custom collections', async () => {
66
+ const db = mongoConnection.getDb();
67
+ // Verify data is in 'custom_permissions' collection
68
+ const count = await db.collection('custom_permissions').countDocuments({ name: 'custom_perm' });
69
+ expect(count).toBe(1);
70
+
71
+ // Verify default collection is empty
72
+ const defaultCount = await db.collection('permissions').countDocuments({ name: 'custom_perm' });
73
+ expect(defaultCount).toBe(0);
74
+ });
75
+ });
@@ -0,0 +1,207 @@
1
+ import { Authzkit } from '../src/classes/Authzkit';
2
+ import { Authorizable } from '../src/interfaces/Authorizable';
3
+ import { PgConnection } from '../src/drivers/postgres/pg-connection';
4
+ import { newDb } from 'pg-mem';
5
+ import { PgAuthzkitStore } from '../src/stores/PgAuthzkitStore';
6
+ import { IAuthzkitConfig } from '../src/interfaces/IAuthzkitConfig';
7
+
8
+ // Increase Jest timeout for PostgreSQL operations
9
+ jest.setTimeout(30000); // 30 seconds
10
+
11
+ describe('PgAuthzkitStore', () => {
12
+ let pgMemDb: any; // Use any because IMemoryDb doesn't have clone method directly
13
+ let pgConnection: PgConnection;
14
+ let pgAuthzkitStore: PgAuthzkitStore;
15
+ let authzkit: Authzkit;
16
+ let user: Authorizable;
17
+
18
+ // No actual connection string needed for in-memory db
19
+ const CONNECTION_STRING = 'in-memory-db';
20
+
21
+ beforeAll(async () => {
22
+ // pg-mem's newDb() method creates a fresh, isolated database instance.
23
+ // We create one for the whole test suite as pg-mem can be reset efficiently.
24
+ pgMemDb = newDb();
25
+
26
+ const config: IAuthzkitConfig = {
27
+ connection: {
28
+ type: 'postgres',
29
+ uri: CONNECTION_STRING,
30
+ },
31
+ };
32
+
33
+ pgConnection = PgConnection.getInstance(config, pgMemDb);
34
+ // No need to call pgConnection.connect() for pg-mem, as it's mocked
35
+ await pgConnection.initSchema(); // Initialize schema for tests
36
+ });
37
+
38
+ beforeEach(async () => {
39
+ // For pg-mem, pgMemDb.public.query('TRUNCATE ...') is the way to reset data.
40
+ // This is handled by authzkit.reset() which calls pgAuthzkitStore.reset().
41
+ // We need to re-initialize PgConnection for each test to ensure its internal state is clean.
42
+
43
+ const config: IAuthzkitConfig = {
44
+ connection: {
45
+ type: 'postgres',
46
+ uri: CONNECTION_STRING + Math.random(),
47
+ },
48
+ };
49
+
50
+ pgConnection = PgConnection.getInstance(config, pgMemDb); // Unique connectionString for singleton instance tracking
51
+ pgAuthzkitStore = new PgAuthzkitStore(pgConnection);
52
+ Authzkit.getInstance(pgAuthzkitStore); // Initialize Authzkit with the Pg store
53
+ authzkit = Authzkit.getInstance(); // Get the instance, ensure it's the one we just set
54
+ await authzkit.reset(); // Clear all data for current test using the clean schema
55
+
56
+ // Define some permissions and roles
57
+ await authzkit.definePermission('create_pg_post');
58
+ await authzkit.definePermission('edit_pg_post');
59
+ await authzkit.definePermission('delete_pg_post');
60
+ await authzkit.definePermission('view_pg_dashboard');
61
+
62
+ await authzkit.defineRole('pg_admin', [
63
+ 'create_pg_post',
64
+ 'edit_pg_post',
65
+ 'delete_pg_post',
66
+ 'view_pg_dashboard',
67
+ ]);
68
+ await authzkit.defineRole('pg_editor', ['create_pg_post', 'edit_pg_post']);
69
+ await authzkit.defineRole('pg_viewer', ['view_pg_dashboard']);
70
+
71
+ user = { id: 'pg_user1', roles: [], permissions: [] }; // Using string ID
72
+ });
73
+
74
+ afterAll(async () => {
75
+ // No explicit disconnect needed for pg-mem
76
+ });
77
+
78
+ // --- Permission Definition Tests ---
79
+ test('should define a permission', async () => {
80
+ const perm = await authzkit.definePermission('new_pg_permission');
81
+ expect(perm).toEqual({ name: 'new_pg_permission', guard_name: undefined });
82
+ await expect(authzkit.definePermission('new_pg_permission')).rejects.toThrow(
83
+ /Permission 'new_pg_permission' already exists./
84
+ );
85
+ });
86
+
87
+ // --- Role Definition Tests ---
88
+ test('should define a role with permissions', async () => {
89
+ const role = await authzkit.defineRole('pg_moderator', ['edit_pg_post']);
90
+ expect(role).toEqual({
91
+ name: 'pg_moderator',
92
+ permissions: ['edit_pg_post'],
93
+ guard_name: undefined,
94
+ });
95
+ await expect(authzkit.defineRole('pg_admin')).rejects.toThrow(
96
+ /Role 'pg_admin' already exists./
97
+ );
98
+ });
99
+
100
+ test('should throw error if role is defined with non-existent permission', async () => {
101
+ await expect(
102
+ authzkit.defineRole('bad_pg_role', ['non_existent_pg_permission'])
103
+ ).rejects.toThrow(
104
+ /Permission 'non_existent_pg_permission' not found when defining role 'bad_pg_role'./
105
+ );
106
+ });
107
+
108
+ // --- Assignment Tests ---
109
+ test('should assign a role to a user', async () => {
110
+ await authzkit.assignRole(user, 'pg_editor');
111
+ expect(await authzkit.hasRole(user, 'pg_editor')).toBe(true);
112
+ expect(await authzkit.hasRole(user, 'pg_admin')).toBe(false);
113
+ });
114
+
115
+ test('should assign a direct permission to a user', async () => {
116
+ await authzkit.assignPermission(user, 'create_pg_post');
117
+ expect(await authzkit.hasPermission(user, 'create_pg_post')).toBe(true);
118
+ expect(await authzkit.hasPermission(user, 'edit_pg_post')).toBe(false);
119
+ });
120
+
121
+ test('should throw error if assigning non-existent role', async () => {
122
+ await expect(authzkit.assignRole(user, 'non_existent_pg_role')).rejects.toThrow(
123
+ /Role 'non_existent_pg_role' not found./
124
+ );
125
+ });
126
+
127
+ test('should throw error if assigning non-existent permission', async () => {
128
+ await expect(authzkit.assignPermission(user, 'non_existent_pg_permission')).rejects.toThrow(
129
+ /Permission 'non_existent_pg_permission' not found./
130
+ );
131
+ });
132
+
133
+ // --- Revocation Tests ---
134
+ test('should revoke a role from a user', async () => {
135
+ await authzkit.assignRole(user, 'pg_editor');
136
+ expect(await authzkit.hasRole(user, 'pg_editor')).toBe(true);
137
+ await authzkit.revokeRole(user, 'pg_editor');
138
+ expect(await authzkit.hasRole(user, 'pg_editor')).toBe(false);
139
+ });
140
+
141
+ test('should revoke a direct permission from a user', async () => {
142
+ await authzkit.assignPermission(user, 'create_pg_post');
143
+ expect(await authzkit.hasPermission(user, 'create_pg_post')).toBe(true);
144
+ await authzkit.revokePermission(user, 'create_pg_post');
145
+ expect(await authzkit.hasPermission(user, 'create_pg_post')).toBe(false);
146
+ });
147
+
148
+ // --- Has Role/Permission Tests ---
149
+ test('user with role should have role', async () => {
150
+ await authzkit.assignRole(user, 'pg_admin');
151
+ expect(await authzkit.hasRole(user, 'pg_admin')).toBe(true);
152
+ });
153
+
154
+ test('user without role should not have role', async () => {
155
+ expect(await authzkit.hasRole(user, 'pg_admin')).toBe(false);
156
+ });
157
+
158
+ test('user with direct permission should have permission', async () => {
159
+ await authzkit.assignPermission(user, 'edit_pg_post');
160
+ expect(await authzkit.hasPermission(user, 'edit_pg_post')).toBe(true);
161
+ });
162
+
163
+ test('user with role having permission should have permission', async () => {
164
+ await authzkit.assignRole(user, 'pg_editor'); // pg_editor has create_pg_post, edit_pg_post
165
+ expect(await authzkit.hasPermission(user, 'create_pg_post')).toBe(true);
166
+ expect(await authzkit.hasPermission(user, 'edit_pg_post')).toBe(true);
167
+ expect(await authzkit.hasPermission(user, 'delete_pg_post')).toBe(false);
168
+ });
169
+
170
+ test('user with admin role should have all admin permissions', async () => {
171
+ await authzkit.assignRole(user, 'pg_admin');
172
+ expect(await authzkit.hasPermission(user, 'create_pg_post')).toBe(true);
173
+ expect(await authzkit.hasPermission(user, 'edit_pg_post')).toBe(true);
174
+ expect(await authzkit.hasPermission(user, 'delete_pg_post')).toBe(true);
175
+ expect(await authzkit.hasPermission(user, 'view_pg_dashboard')).toBe(true);
176
+ });
177
+
178
+ test('user with no permissions/roles should not have any permissions', async () => {
179
+ expect(await authzkit.hasPermission(user, 'create_pg_post')).toBe(false);
180
+ expect(await authzkit.hasRole(user, 'pg_admin')).toBe(false);
181
+ });
182
+
183
+ test('roleHasPermission should work correctly', async () => {
184
+ expect(await authzkit.roleHasPermission('pg_admin', 'create_pg_post')).toBe(true);
185
+ expect(await authzkit.roleHasPermission('pg_editor', 'delete_pg_post')).toBe(false);
186
+ expect(await authzkit.roleHasPermission('pg_viewer', 'view_pg_dashboard')).toBe(true);
187
+ expect(await authzkit.roleHasPermission('non_existent_pg_role', 'some_permission')).toBe(false);
188
+ });
189
+
190
+ // --- Reset Tests ---
191
+ test('reset should clear all permissions, roles, and assignments', async () => {
192
+ await authzkit.definePermission('test_pg_perm');
193
+ await authzkit.defineRole('test_pg_role', ['test_pg_perm']);
194
+ await authzkit.assignRole(user, 'test_pg_role');
195
+ await authzkit.assignPermission(user, 'test_pg_perm');
196
+
197
+ await authzkit.reset();
198
+
199
+ // After reset, defining again should not throw an error (as they were cleared)
200
+ await expect(authzkit.definePermission('test_pg_perm')).resolves.not.toThrow();
201
+ await expect(authzkit.defineRole('test_pg_role', ['test_pg_perm'])).resolves.not.toThrow();
202
+
203
+ // User should no longer have the role/permission
204
+ expect(await authzkit.hasRole(user, 'test_pg_role')).toBe(false);
205
+ expect(await authzkit.hasPermission(user, 'test_pg_perm')).toBe(false);
206
+ });
207
+ });
@@ -0,0 +1,90 @@
1
+ import { Authzkit } from '../src/classes/Authzkit';
2
+ import { PgConnection } from '../src/drivers/postgres/pg-connection';
3
+ import { newDb } from 'pg-mem';
4
+ import { PgAuthzkitStore } from '../src/stores/PgAuthzkitStore';
5
+ import { IAuthzkitConfig } from '../src/interfaces/IAuthzkitConfig';
6
+
7
+ // Increase Jest timeout
8
+ jest.setTimeout(30000);
9
+
10
+ describe('PgAuthzkitStore (Custom Tables)', () => {
11
+ let pgMemDb: any;
12
+ let pgConnection: PgConnection;
13
+ let pgAuthzkitStore: PgAuthzkitStore;
14
+ let authzkit: Authzkit;
15
+
16
+ const CONNECTION_STRING = 'in-memory-db-custom';
17
+
18
+ beforeAll(async () => {
19
+ pgMemDb = newDb();
20
+
21
+ // We need to initialize schema with custom names
22
+ const config: IAuthzkitConfig = {
23
+ connection: {
24
+ type: 'postgres',
25
+ uri: CONNECTION_STRING,
26
+ },
27
+ models: {
28
+ users: 'custom_users',
29
+ roles: 'custom_roles',
30
+ permissions: 'custom_permissions',
31
+ user_roles: 'custom_user_roles',
32
+ user_permissions: 'custom_user_permissions',
33
+ },
34
+ };
35
+
36
+ pgConnection = PgConnection.getInstance(config, pgMemDb);
37
+ await pgConnection.initSchema();
38
+ });
39
+
40
+ beforeEach(async () => {
41
+ // Config matching the one used in beforeAll (for singleton to return same instance)
42
+ // Actually, if we pass same config object (by value/content), getInstance should return same instance.
43
+ // But we used random string in other test. Here we can stick to one instance since we just want to verify table names.
44
+ // Or we can create a new instance if we want, but we need to pass pgMemDb again.
45
+
46
+ const config: IAuthzkitConfig = {
47
+ connection: {
48
+ type: 'postgres',
49
+ uri: CONNECTION_STRING,
50
+ },
51
+ models: {
52
+ users: 'custom_users',
53
+ roles: 'custom_roles',
54
+ permissions: 'custom_permissions',
55
+ user_roles: 'custom_user_roles',
56
+ user_permissions: 'custom_user_permissions',
57
+ },
58
+ };
59
+
60
+ // We reuse the instance created in beforeAll
61
+ pgConnection = PgConnection.getInstance(config, pgMemDb);
62
+ pgAuthzkitStore = new PgAuthzkitStore(pgConnection);
63
+ Authzkit.getInstance(pgAuthzkitStore);
64
+ authzkit = Authzkit.getInstance();
65
+
66
+ // We don't call reset() here because it might truncate tables we just created?
67
+ // Actually reset() calls truncateTables(). PgConnection.truncateTables() uses getTableName().
68
+ // So if config is correct, it should truncate custom tables.
69
+ await authzkit.reset();
70
+
71
+ await authzkit.definePermission('custom_pg_perm');
72
+ });
73
+
74
+ test('should store data in custom tables', async () => {
75
+ // Query pg-mem directly to see if data is in custom table
76
+ // pg-mem exposes the DB state via pgMemDb
77
+
78
+ // we can use pgConnection.query acting as a backdoor too, but let's try to verify via "direct" SQL on the connection
79
+
80
+ const res = await pgConnection.query(
81
+ 'SELECT count(*) FROM custom_permissions WHERE name = $1',
82
+ ['custom_pg_perm']
83
+ );
84
+ expect(res.rows[0].count).toBe(1);
85
+
86
+ // Verify default table does not exist or is empty
87
+ // In pg-mem, querying non-existent table throws error.
88
+ await expect(pgConnection.query('SELECT count(*) FROM permissions')).rejects.toThrow();
89
+ });
90
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "module": "commonjs",
5
+ "rootDir": ".",
6
+ "outDir": "./dist",
7
+ "baseUrl": ".",
8
+ "paths": {
9
+ "@zakyyudha/node-authzkit": [
10
+ "src/index.ts"
11
+ ]
12
+ },
13
+ "lib": [
14
+ "es2022",
15
+ "dom"
16
+ ],
17
+ "types": [
18
+ "jest"
19
+ ],
20
+ "esModuleInterop": true,
21
+ "forceConsistentCasingInFileNames": true,
22
+ "strict": true,
23
+ "skipLibCheck": true,
24
+ "declaration": true,
25
+ "sourceMap": true
26
+ },
27
+ "include": [
28
+ "src/**/*",
29
+ "tests/**/*"
30
+ ],
31
+ "exclude": [
32
+ "node_modules",
33
+ "node_modules",
34
+ "dist",
35
+ "src/dashboard/web"
36
+ ]
37
+ }