@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,171 @@
1
+ import { Db, Collection } from 'mongodb';
2
+ import { IAuthzkitStore } from './IAuthzkitStore';
3
+ import { Permission } from '../interfaces/Permission';
4
+ import { Role } from '../interfaces/Role';
5
+ import { MongoConnection } from '../drivers/mongodb/mongo-connection';
6
+
7
+ interface MongoPermission extends Permission {}
8
+
9
+ interface MongoRole extends Role {}
10
+
11
+ interface MongoUserRole {
12
+ userId: string | number;
13
+ roleName: string;
14
+ }
15
+
16
+ interface MongoUserPermission {
17
+ userId: string | number;
18
+ permissionName: string;
19
+ }
20
+
21
+ export class MongoAuthzkitStore implements IAuthzkitStore {
22
+ private db: Db;
23
+ private permissionsCollection: Collection<MongoPermission>;
24
+ private rolesCollection: Collection<MongoRole>;
25
+ private userRolesCollection: Collection<MongoUserRole>;
26
+ private userPermissionsCollection: Collection<MongoUserPermission>;
27
+
28
+ constructor(mongoConnection: MongoConnection) {
29
+ this.db = mongoConnection.getDb();
30
+ this.permissionsCollection = this.db.collection(
31
+ mongoConnection.getCollectionName('permissions')
32
+ );
33
+ this.rolesCollection = this.db.collection(mongoConnection.getCollectionName('roles'));
34
+ this.userRolesCollection = this.db.collection(mongoConnection.getCollectionName('user_roles'));
35
+ this.userPermissionsCollection = this.db.collection(
36
+ mongoConnection.getCollectionName('user_permissions')
37
+ );
38
+
39
+ // Ensure unique indexes
40
+ void this.permissionsCollection.createIndex({ name: 1 }, { unique: true }).catch(console.error);
41
+ void this.rolesCollection.createIndex({ name: 1 }, { unique: true }).catch(console.error);
42
+ void this.userRolesCollection
43
+ .createIndex({ userId: 1, roleName: 1 }, { unique: true })
44
+ .catch(console.error);
45
+ void this.userPermissionsCollection
46
+ .createIndex({ userId: 1, permissionName: 1 }, { unique: true })
47
+ .catch(console.error);
48
+ }
49
+
50
+ async getPermission(name: string): Promise<Permission | undefined> {
51
+ const perm = await this.permissionsCollection.findOne({ name });
52
+ return perm ? { name: perm.name, guard_name: perm.guard_name } : undefined;
53
+ }
54
+
55
+ async getPermissions(): Promise<Permission[]> {
56
+ const perms = await this.permissionsCollection.find({}).toArray();
57
+ return perms.map((perm) => ({ name: perm.name, guard_name: perm.guard_name }));
58
+ }
59
+
60
+ async setPermission(permission: Permission): Promise<void> {
61
+ await this.permissionsCollection.updateOne(
62
+ { name: permission.name },
63
+ { $set: permission },
64
+ { upsert: true }
65
+ );
66
+ }
67
+
68
+ async hasPermission(name: string): Promise<boolean> {
69
+ const count = await this.permissionsCollection.countDocuments({ name });
70
+ return count > 0;
71
+ }
72
+
73
+ async deletePermission(name: string): Promise<void> {
74
+ await this.permissionsCollection.deleteOne({ name });
75
+ }
76
+
77
+ async getRole(name: string): Promise<Role | undefined> {
78
+ const role = await this.rolesCollection.findOne({ name });
79
+ return role
80
+ ? { name: role.name, guard_name: role.guard_name, permissions: role.permissions }
81
+ : undefined;
82
+ }
83
+
84
+ async getRoles(): Promise<Role[]> {
85
+ const roles = await this.rolesCollection.find({}).toArray();
86
+ return roles.map((role) => ({
87
+ name: role.name,
88
+ guard_name: role.guard_name,
89
+ permissions: role.permissions,
90
+ }));
91
+ }
92
+
93
+ async setRole(role: Role): Promise<void> {
94
+ await this.rolesCollection.updateOne(
95
+ { name: role.name },
96
+ { $set: { name: role.name, guard_name: role.guard_name, permissions: role.permissions } },
97
+ { upsert: true }
98
+ );
99
+ }
100
+
101
+ async hasRole(name: string): Promise<boolean> {
102
+ const count = await this.rolesCollection.countDocuments({ name });
103
+ return count > 0;
104
+ }
105
+
106
+ async deleteRole(name: string): Promise<void> {
107
+ await this.rolesCollection.deleteOne({ name });
108
+ await this.userRolesCollection.deleteMany({ roleName: name }); // Remove all assignments for this role
109
+ }
110
+
111
+ async getUserRoles(userId: string | number): Promise<Set<string>> {
112
+ const userRoles = await this.userRolesCollection.find({ userId }).toArray();
113
+ return new Set(userRoles.map((ur) => ur.roleName));
114
+ }
115
+
116
+ async addUserRole(userId: string | number, roleName: string): Promise<void> {
117
+ try {
118
+ await this.userRolesCollection.insertOne({ userId, roleName });
119
+ } catch (error: any) {
120
+ if (error.code === 11000) {
121
+ // Duplicate key error
122
+ // Role already assigned, ignore
123
+ } else {
124
+ throw error;
125
+ }
126
+ }
127
+ }
128
+
129
+ async removeUserRole(userId: string | number, roleName: string): Promise<void> {
130
+ await this.userRolesCollection.deleteOne({ userId, roleName });
131
+ }
132
+
133
+ async hasUserRole(userId: string | number, roleName: string): Promise<boolean> {
134
+ const count = await this.userRolesCollection.countDocuments({ userId, roleName });
135
+ return count > 0;
136
+ }
137
+
138
+ async getUserPermissions(userId: string | number): Promise<Set<string>> {
139
+ const userPermissions = await this.userPermissionsCollection.find({ userId }).toArray();
140
+ return new Set(userPermissions.map((up) => up.permissionName));
141
+ }
142
+
143
+ async addUserPermission(userId: string | number, permissionName: string): Promise<void> {
144
+ try {
145
+ await this.userPermissionsCollection.insertOne({ userId, permissionName });
146
+ } catch (error: any) {
147
+ if (error.code === 11000) {
148
+ // Duplicate key error
149
+ // Permission already assigned, ignore
150
+ } else {
151
+ throw error;
152
+ }
153
+ }
154
+ }
155
+
156
+ async removeUserPermission(userId: string | number, permissionName: string): Promise<void> {
157
+ await this.userPermissionsCollection.deleteOne({ userId, permissionName });
158
+ }
159
+
160
+ async hasUserPermission(userId: string | number, permissionName: string): Promise<boolean> {
161
+ const count = await this.userPermissionsCollection.countDocuments({ userId, permissionName });
162
+ return count > 0;
163
+ }
164
+
165
+ async reset(): Promise<void> {
166
+ await this.permissionsCollection.deleteMany({});
167
+ await this.rolesCollection.deleteMany({});
168
+ await this.userRolesCollection.deleteMany({});
169
+ await this.userPermissionsCollection.deleteMany({});
170
+ }
171
+ }
@@ -0,0 +1,191 @@
1
+ import { IAuthzkitStore } from './IAuthzkitStore';
2
+ import { Permission } from '../interfaces/Permission';
3
+ import { Role } from '../interfaces/Role';
4
+ import { PgConnection } from '../drivers/postgres/pg-connection';
5
+
6
+ export class PgAuthzkitStore implements IAuthzkitStore {
7
+ private pgConnection: PgConnection;
8
+ private permissionsTable: string;
9
+ private rolesTable: string;
10
+ private userRolesTable: string;
11
+ private userPermissionsTable: string;
12
+
13
+ constructor(pgConnection: PgConnection) {
14
+ this.pgConnection = pgConnection;
15
+ this.permissionsTable = this.pgConnection.getTableName('permissions');
16
+ this.rolesTable = this.pgConnection.getTableName('roles');
17
+ this.userRolesTable = this.pgConnection.getTableName('user_roles');
18
+ this.userPermissionsTable = this.pgConnection.getTableName('user_permissions');
19
+ }
20
+
21
+ async getPermission(name: string): Promise<Permission | undefined> {
22
+ const res = await this.pgConnection.query(
23
+ `SELECT name, guard_name FROM ${this.permissionsTable} WHERE name = $1`,
24
+ [name]
25
+ );
26
+ if (res.rows.length > 0) {
27
+ return res.rows[0];
28
+ }
29
+ return undefined;
30
+ }
31
+
32
+ async getPermissions(): Promise<Permission[]> {
33
+ const res = await this.pgConnection.query(
34
+ `SELECT name, guard_name FROM ${this.permissionsTable}`
35
+ );
36
+ return res.rows.map((row) => ({ name: row.name, guard_name: row.guard_name }));
37
+ }
38
+
39
+ async setPermission(permission: Permission): Promise<void> {
40
+ await this.pgConnection.query(
41
+ `INSERT INTO ${this.permissionsTable} (name, guard_name) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET guard_name = $2`,
42
+ [permission.name, permission.guard_name]
43
+ );
44
+ }
45
+
46
+ async hasPermission(name: string): Promise<boolean> {
47
+ const res = await this.pgConnection.query(
48
+ `SELECT 1 FROM ${this.permissionsTable} WHERE name = $1`,
49
+ [name]
50
+ );
51
+ return res.rows.length > 0;
52
+ }
53
+
54
+ async deletePermission(name: string): Promise<void> {
55
+ await this.pgConnection.query(`DELETE FROM ${this.permissionsTable} WHERE name = $1`, [name]);
56
+ }
57
+
58
+ async getRole(name: string): Promise<Role | undefined> {
59
+ const res = await this.pgConnection.query(
60
+ `SELECT name, guard_name, permissions FROM ${this.rolesTable} WHERE name = $1`,
61
+ [name]
62
+ );
63
+ if (res.rows.length > 0) {
64
+ // Ensure permissions are correctly deserialized if stored as string array
65
+ const roleData = res.rows[0];
66
+ return {
67
+ name: roleData.name,
68
+ guard_name: roleData.guard_name,
69
+ permissions: Array.isArray(roleData.permissions) ? roleData.permissions : [],
70
+ };
71
+ }
72
+ return undefined;
73
+ }
74
+
75
+ async getRoles(): Promise<Role[]> {
76
+ const res = await this.pgConnection.query(
77
+ `SELECT name, guard_name, permissions FROM ${this.rolesTable}`
78
+ );
79
+ return res.rows.map((roleData) => ({
80
+ name: roleData.name,
81
+ guard_name: roleData.guard_name,
82
+ permissions: Array.isArray(roleData.permissions) ? roleData.permissions : [],
83
+ }));
84
+ }
85
+
86
+ async setRole(role: Role): Promise<void> {
87
+ await this.pgConnection.query(
88
+ `INSERT INTO ${this.rolesTable} (name, guard_name, permissions) VALUES ($1, $2, $3) ON CONFLICT (name) DO UPDATE SET guard_name = $2, permissions = $3`,
89
+ [role.name, role.guard_name, role.permissions]
90
+ );
91
+ }
92
+
93
+ async hasRole(name: string): Promise<boolean> {
94
+ const res = await this.pgConnection.query(`SELECT 1 FROM ${this.rolesTable} WHERE name = $1`, [
95
+ name,
96
+ ]);
97
+ return res.rows.length > 0;
98
+ }
99
+
100
+ async deleteRole(name: string): Promise<void> {
101
+ await this.pgConnection.query(`DELETE FROM ${this.rolesTable} WHERE name = $1`, [name]);
102
+ // CASCADE DELETE should handle user_roles table if set up correctly via foreign keys
103
+ }
104
+
105
+ async getUserRoles(userId: string | number): Promise<Set<string>> {
106
+ const res = await this.pgConnection.query(
107
+ `SELECT role_name FROM ${this.userRolesTable} WHERE user_id = $1`,
108
+ [userId.toString()]
109
+ );
110
+ return new Set(res.rows.map((row) => row.role_name));
111
+ }
112
+
113
+ async addUserRole(userId: string | number, roleName: string): Promise<void> {
114
+ try {
115
+ await this.pgConnection.query(
116
+ `INSERT INTO ${this.userRolesTable} (user_id, role_name) VALUES ($1, $2)`,
117
+ [userId.toString(), roleName]
118
+ );
119
+ } catch (error: any) {
120
+ if (error.code === '23505') {
121
+ // Unique violation error
122
+ // Role already assigned, ignore
123
+ } else {
124
+ throw error;
125
+ }
126
+ }
127
+ }
128
+
129
+ async removeUserRole(userId: string | number, roleName: string): Promise<void> {
130
+ await this.pgConnection.query(
131
+ `DELETE FROM ${this.userRolesTable} WHERE user_id = $1 AND role_name = $2`,
132
+ [userId.toString(), roleName]
133
+ );
134
+ }
135
+
136
+ async hasUserRole(userId: string | number, roleName: string): Promise<boolean> {
137
+ const res = await this.pgConnection.query(
138
+ `SELECT 1 FROM ${this.userRolesTable} WHERE user_id = $1 AND role_name = $2`,
139
+ [userId.toString(), roleName]
140
+ );
141
+ return res.rows.length > 0;
142
+ }
143
+
144
+ async getUserPermissions(userId: string | number): Promise<Set<string>> {
145
+ const res = await this.pgConnection.query(
146
+ `SELECT permission_name FROM ${this.userPermissionsTable} WHERE user_id = $1`,
147
+ [userId.toString()]
148
+ );
149
+ return new Set(res.rows.map((row) => row.permission_name));
150
+ }
151
+
152
+ async addUserPermission(userId: string | number, permissionName: string): Promise<void> {
153
+ try {
154
+ await this.pgConnection.query(
155
+ `INSERT INTO ${this.userPermissionsTable} (user_id, permission_name) VALUES ($1, $2)`,
156
+ [userId.toString(), permissionName]
157
+ );
158
+ } catch (error: any) {
159
+ if (error.code === '23505') {
160
+ // Unique violation error
161
+ // Permission already assigned, ignore
162
+ } else {
163
+ throw error;
164
+ }
165
+ }
166
+ }
167
+
168
+ async removeUserPermission(userId: string | number, permissionName: string): Promise<void> {
169
+ await this.pgConnection.query(
170
+ `DELETE FROM ${this.userPermissionsTable} WHERE user_id = $1 AND permission_name = $2`,
171
+ [userId.toString(), permissionName]
172
+ );
173
+ }
174
+
175
+ async hasUserPermission(userId: string | number, permissionName: string): Promise<boolean> {
176
+ const count = await this.pgConnection.query(
177
+ `SELECT 1 FROM ${this.userPermissionsTable} WHERE user_id = $1 AND permission_name = $2`,
178
+ [userId.toString(), permissionName]
179
+ );
180
+ return count.rows.length > 0;
181
+ }
182
+
183
+ async reset(): Promise<void> {
184
+ try {
185
+ await this.pgConnection.truncateTables();
186
+ } catch (error) {
187
+ // If truncation fails (e.g., tables don't exist), try initializing the schema
188
+ await this.pgConnection.initSchema();
189
+ }
190
+ }
191
+ }
@@ -0,0 +1,70 @@
1
+ import { IAuthzkitConfig } from '../interfaces/IAuthzkitConfig';
2
+
3
+ export function loadConfigFromEnv(): IAuthzkitConfig | null {
4
+ const type = process.env.AUTHZKIT_CONNECTION_TYPE;
5
+ const uri = process.env.AUTHZKIT_CONNECTION_URI;
6
+ const dbName = process.env.AUTHZKIT_DB_NAME;
7
+
8
+ if (!type && !uri) {
9
+ return null; // No config found in env
10
+ }
11
+
12
+ const models = {
13
+ users: process.env.AUTHZKIT_MODEL_USERS,
14
+ roles: process.env.AUTHZKIT_MODEL_ROLES,
15
+ permissions: process.env.AUTHZKIT_MODEL_PERMISSIONS,
16
+ user_roles: process.env.AUTHZKIT_MODEL_USER_ROLES,
17
+ user_permissions: process.env.AUTHZKIT_MODEL_USER_PERMISSIONS,
18
+ };
19
+
20
+ // Filter undefined models
21
+ const cleanModels: any = {};
22
+ for (const [key, value] of Object.entries(models)) {
23
+ if (value) cleanModels[key] = value;
24
+ }
25
+
26
+ if (type === 'mongodb') {
27
+ return {
28
+ connection: {
29
+ type: 'mongodb',
30
+ uri: uri || '',
31
+ database: dbName,
32
+ },
33
+ models: Object.keys(cleanModels).length > 0 ? cleanModels : undefined,
34
+ };
35
+ } else if (type === 'postgres') {
36
+ return {
37
+ connection: {
38
+ type: 'postgres',
39
+ uri: uri || '',
40
+ database: dbName, // Optional for PG connection string usually includes it
41
+ },
42
+ models: Object.keys(cleanModels).length > 0 ? cleanModels : undefined,
43
+ };
44
+ }
45
+
46
+ // Default fallback if type isn't explicit but URI is present, could try to guess, but stricter is better.
47
+ if (uri) {
48
+ if (uri.startsWith('mongodb')) {
49
+ return {
50
+ connection: {
51
+ type: 'mongodb',
52
+ uri: uri,
53
+ database: dbName,
54
+ },
55
+ models: Object.keys(cleanModels).length > 0 ? cleanModels : undefined,
56
+ };
57
+ }
58
+ if (uri.startsWith('postgres')) {
59
+ return {
60
+ connection: {
61
+ type: 'postgres',
62
+ uri: uri,
63
+ },
64
+ models: Object.keys(cleanModels).length > 0 ? cleanModels : undefined,
65
+ };
66
+ }
67
+ }
68
+
69
+ return null;
70
+ }
@@ -0,0 +1,157 @@
1
+ import { Authzkit } from '../src/classes/Authzkit';
2
+ import { Authorizable } from '../src/interfaces/Authorizable';
3
+ import { InMemoryAuthzkitStore } from '../src/stores/InMemoryAuthzkitStore';
4
+
5
+ describe('Authzkit', () => {
6
+ let authzkit: Authzkit;
7
+ let user: Authorizable;
8
+
9
+ beforeEach(async () => {
10
+ // Made beforeEach async
11
+ // Each test gets a fresh Authzkit instance with a new InMemory store
12
+ const inMemoryStore = new InMemoryAuthzkitStore();
13
+ Authzkit.getInstance(inMemoryStore); // Initialize with new store
14
+ authzkit = Authzkit.getInstance(); // Get the instance, ensure it's the one we just set
15
+ await authzkit.reset(); // Ensure a clean state for each test
16
+
17
+ // Define some permissions and roles
18
+ await authzkit.definePermission('create_post');
19
+ await authzkit.definePermission('edit_post');
20
+ await authzkit.definePermission('delete_post');
21
+ await authzkit.definePermission('view_dashboard');
22
+
23
+ await authzkit.defineRole('admin', [
24
+ 'create_post',
25
+ 'edit_post',
26
+ 'delete_post',
27
+ 'view_dashboard',
28
+ ]);
29
+ await authzkit.defineRole('editor', ['create_post', 'edit_post']);
30
+ await authzkit.defineRole('viewer', ['view_dashboard']);
31
+
32
+ user = { id: 1, roles: [], permissions: [] };
33
+ });
34
+
35
+ // --- Permission Definition Tests ---
36
+ test('should define a permission', async () => {
37
+ const perm = await authzkit.definePermission('new_permission');
38
+ expect(perm).toEqual({ name: 'new_permission', guard_name: undefined });
39
+ await expect(authzkit.definePermission('new_permission')).rejects.toThrow(
40
+ "Permission 'new_permission' already exists."
41
+ );
42
+ });
43
+
44
+ // --- Role Definition Tests ---
45
+ test('should define a role with permissions', async () => {
46
+ // Made test async
47
+ const role = await authzkit.defineRole('moderator', ['edit_post']);
48
+ expect(role).toEqual({ name: 'moderator', permissions: ['edit_post'], guard_name: undefined });
49
+ await expect(authzkit.defineRole('admin')).rejects.toThrow("Role 'admin' already exists.");
50
+ });
51
+
52
+ test('should throw error if role is defined with non-existent permission', async () => {
53
+ await expect(authzkit.defineRole('bad_role', ['non_existent_permission'])).rejects.toThrow(
54
+ "Permission 'non_existent_permission' not found when defining role 'bad_role'."
55
+ );
56
+ });
57
+
58
+ // --- Assignment Tests ---
59
+ test('should assign a role to a user', async () => {
60
+ await authzkit.assignRole(user, 'editor');
61
+ expect(await authzkit.hasRole(user, 'editor')).toBe(true);
62
+ expect(await authzkit.hasRole(user, 'admin')).toBe(false);
63
+ });
64
+
65
+ test('should assign a direct permission to a user', async () => {
66
+ await authzkit.assignPermission(user, 'create_post');
67
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(true);
68
+ expect(await authzkit.hasPermission(user, 'edit_post')).toBe(false);
69
+ });
70
+
71
+ test('should throw error if assigning non-existent role', async () => {
72
+ await expect(authzkit.assignRole(user, 'non_existent_role')).rejects.toThrow(
73
+ "Role 'non_existent_role' not found."
74
+ );
75
+ });
76
+
77
+ test('should throw error if assigning non-existent permission', async () => {
78
+ await expect(authzkit.assignPermission(user, 'non_existent_permission')).rejects.toThrow(
79
+ "Permission 'non_existent_permission' not found."
80
+ );
81
+ });
82
+
83
+ // --- Revocation Tests ---
84
+ test('should revoke a role from a user', async () => {
85
+ await authzkit.assignRole(user, 'editor');
86
+ expect(await authzkit.hasRole(user, 'editor')).toBe(true);
87
+ await authzkit.revokeRole(user, 'editor');
88
+ expect(await authzkit.hasRole(user, 'editor')).toBe(false);
89
+ });
90
+
91
+ test('should revoke a direct permission from a user', async () => {
92
+ await authzkit.assignPermission(user, 'create_post');
93
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(true);
94
+ await authzkit.revokePermission(user, 'create_post');
95
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(false);
96
+ });
97
+
98
+ // --- Has Role/Permission Tests ---
99
+ test('user with role should have role', async () => {
100
+ await authzkit.assignRole(user, 'admin');
101
+ expect(await authzkit.hasRole(user, 'admin')).toBe(true);
102
+ });
103
+
104
+ test('user without role should not have role', async () => {
105
+ expect(await authzkit.hasRole(user, 'admin')).toBe(false);
106
+ });
107
+
108
+ test('user with direct permission should have permission', async () => {
109
+ await authzkit.assignPermission(user, 'edit_post');
110
+ expect(await authzkit.hasPermission(user, 'edit_post')).toBe(true);
111
+ });
112
+
113
+ test('user with role having permission should have permission', async () => {
114
+ await authzkit.assignRole(user, 'editor'); // editor has create_post, edit_post
115
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(true);
116
+ expect(await authzkit.hasPermission(user, 'edit_post')).toBe(true);
117
+ expect(await authzkit.hasPermission(user, 'delete_post')).toBe(false);
118
+ });
119
+
120
+ test('user with admin role should have all admin permissions', async () => {
121
+ await authzkit.assignRole(user, 'admin');
122
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(true);
123
+ expect(await authzkit.hasPermission(user, 'edit_post')).toBe(true);
124
+ expect(await authzkit.hasPermission(user, 'delete_post')).toBe(true);
125
+ expect(await authzkit.hasPermission(user, 'view_dashboard')).toBe(true);
126
+ });
127
+
128
+ test('user with no permissions/roles should not have any permissions', async () => {
129
+ expect(await authzkit.hasPermission(user, 'create_post')).toBe(false);
130
+ expect(await authzkit.hasRole(user, 'admin')).toBe(false);
131
+ });
132
+
133
+ test('roleHasPermission should work correctly', async () => {
134
+ expect(await authzkit.roleHasPermission('admin', 'create_post')).toBe(true);
135
+ expect(await authzkit.roleHasPermission('editor', 'delete_post')).toBe(false);
136
+ expect(await authzkit.roleHasPermission('viewer', 'view_dashboard')).toBe(true);
137
+ expect(await authzkit.roleHasPermission('non_existent_role', 'some_permission')).toBe(false);
138
+ });
139
+
140
+ // --- Reset Tests ---
141
+ test('reset should clear all permissions, roles, and assignments', async () => {
142
+ await authzkit.definePermission('test_perm');
143
+ await authzkit.defineRole('test_role', ['test_perm']);
144
+ await authzkit.assignRole(user, 'test_role');
145
+ await authzkit.assignPermission(user, 'test_perm');
146
+
147
+ await authzkit.reset();
148
+
149
+ // After reset, defining again should not throw an error (as they were cleared)
150
+ await expect(authzkit.definePermission('test_perm')).resolves.not.toThrow();
151
+ await expect(authzkit.defineRole('test_role', ['test_perm'])).resolves.not.toThrow();
152
+
153
+ // User should no longer have the role/permission
154
+ expect(await authzkit.hasRole(user, 'test_role')).toBe(false);
155
+ expect(await authzkit.hasPermission(user, 'test_perm')).toBe(false);
156
+ });
157
+ });