@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.
- package/.eslintrc.cjs +17 -0
- package/.prettierrc.json +10 -0
- package/.release-it.json +24 -0
- package/README.md +221 -0
- package/dist/src/classes/Authzkit.d.ts +110 -0
- package/dist/src/classes/Authzkit.js +189 -0
- package/dist/src/classes/Authzkit.js.map +1 -0
- package/dist/src/dashboard/router.d.ts +19 -0
- package/dist/src/dashboard/router.js +89 -0
- package/dist/src/dashboard/router.js.map +1 -0
- package/dist/src/dashboard/routes/permissions.d.ts +3 -0
- package/dist/src/dashboard/routes/permissions.js +39 -0
- package/dist/src/dashboard/routes/permissions.js.map +1 -0
- package/dist/src/dashboard/routes/roles.d.ts +3 -0
- package/dist/src/dashboard/routes/roles.js +39 -0
- package/dist/src/dashboard/routes/roles.js.map +1 -0
- package/dist/src/dashboard/routes/users.d.ts +3 -0
- package/dist/src/dashboard/routes/users.js +81 -0
- package/dist/src/dashboard/routes/users.js.map +1 -0
- package/dist/src/drivers/mongodb/mongo-connection.d.ts +15 -0
- package/dist/src/drivers/mongodb/mongo-connection.js +89 -0
- package/dist/src/drivers/mongodb/mongo-connection.js.map +1 -0
- package/dist/src/drivers/postgres/pg-connection.d.ts +17 -0
- package/dist/src/drivers/postgres/pg-connection.js +145 -0
- package/dist/src/drivers/postgres/pg-connection.js.map +1 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.js +36 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interfaces/Authorizable.d.ts +7 -0
- package/dist/src/interfaces/Authorizable.js +3 -0
- package/dist/src/interfaces/Authorizable.js.map +1 -0
- package/dist/src/interfaces/IAuthzkitConfig.d.ts +18 -0
- package/dist/src/interfaces/IAuthzkitConfig.js +3 -0
- package/dist/src/interfaces/IAuthzkitConfig.js.map +1 -0
- package/dist/src/interfaces/Permission.d.ts +4 -0
- package/dist/src/interfaces/Permission.js +3 -0
- package/dist/src/interfaces/Permission.js.map +1 -0
- package/dist/src/interfaces/Role.d.ts +5 -0
- package/dist/src/interfaces/Role.js +3 -0
- package/dist/src/interfaces/Role.js.map +1 -0
- package/dist/src/middleware/authzMiddleware.d.ts +17 -0
- package/dist/src/middleware/authzMiddleware.js +52 -0
- package/dist/src/middleware/authzMiddleware.js.map +1 -0
- package/dist/src/stores/IAuthzkitStore.d.ts +23 -0
- package/dist/src/stores/IAuthzkitStore.js +3 -0
- package/dist/src/stores/IAuthzkitStore.js.map +1 -0
- package/dist/src/stores/InMemoryAuthzkitStore.d.ts +28 -0
- package/dist/src/stores/InMemoryAuthzkitStore.js +83 -0
- package/dist/src/stores/InMemoryAuthzkitStore.js.map +1 -0
- package/dist/src/stores/MongoAuthzkitStore.d.ts +31 -0
- package/dist/src/stores/MongoAuthzkitStore.js +127 -0
- package/dist/src/stores/MongoAuthzkitStore.js.map +1 -0
- package/dist/src/stores/PgAuthzkitStore.d.ts +31 -0
- package/dist/src/stores/PgAuthzkitStore.js +133 -0
- package/dist/src/stores/PgAuthzkitStore.js.map +1 -0
- package/dist/src/utils/envConfig.d.ts +2 -0
- package/dist/src/utils/envConfig.js +68 -0
- package/dist/src/utils/envConfig.js.map +1 -0
- package/dist/tests/Authzkit.test.d.ts +1 -0
- package/dist/tests/Authzkit.test.js +126 -0
- package/dist/tests/Authzkit.test.js.map +1 -0
- package/dist/tests/MongoAuthzkitStore.test.d.ts +1 -0
- package/dist/tests/MongoAuthzkitStore.test.js +161 -0
- package/dist/tests/MongoAuthzkitStore.test.js.map +1 -0
- package/dist/tests/MongoAuthzkitStoreCustom.test.d.ts +1 -0
- package/dist/tests/MongoAuthzkitStoreCustom.test.js +65 -0
- package/dist/tests/MongoAuthzkitStoreCustom.test.js.map +1 -0
- package/dist/tests/PgAuthzkitStore.test.d.ts +1 -0
- package/dist/tests/PgAuthzkitStore.test.js +163 -0
- package/dist/tests/PgAuthzkitStore.test.js.map +1 -0
- package/dist/tests/PgAuthzkitStoreCustom.test.d.ts +1 -0
- package/dist/tests/PgAuthzkitStoreCustom.test.js +74 -0
- package/dist/tests/PgAuthzkitStoreCustom.test.js.map +1 -0
- package/examples/express-app.ts +65 -0
- package/jest.config.js +9 -0
- package/package.json +57 -0
- package/src/classes/Authzkit.ts +214 -0
- package/src/dashboard/router.ts +79 -0
- package/src/dashboard/routes/permissions.ts +38 -0
- package/src/dashboard/routes/roles.ts +38 -0
- package/src/dashboard/routes/users.ts +81 -0
- package/src/dashboard/web/README.md +73 -0
- package/src/dashboard/web/eslint.config.js +23 -0
- package/src/dashboard/web/index.html +13 -0
- package/src/dashboard/web/package.json +31 -0
- package/src/dashboard/web/pnpm-lock.yaml +2094 -0
- package/src/dashboard/web/public/vite.svg +1 -0
- package/src/dashboard/web/src/App.css +42 -0
- package/src/dashboard/web/src/App.tsx +26 -0
- package/src/dashboard/web/src/assets/react.svg +1 -0
- package/src/dashboard/web/src/components/Navbar.tsx +53 -0
- package/src/dashboard/web/src/index.css +138 -0
- package/src/dashboard/web/src/main.tsx +10 -0
- package/src/dashboard/web/src/pages/PermissionsPage.tsx +87 -0
- package/src/dashboard/web/src/pages/RolesPage.tsx +98 -0
- package/src/dashboard/web/src/pages/UsersPage.tsx +146 -0
- package/src/dashboard/web/src/services/api.ts +59 -0
- package/src/dashboard/web/tsconfig.app.json +28 -0
- package/src/dashboard/web/tsconfig.json +7 -0
- package/src/dashboard/web/tsconfig.node.json +26 -0
- package/src/dashboard/web/vite.config.ts +8 -0
- package/src/drivers/mongodb/mongo-connection.ts +98 -0
- package/src/drivers/postgres/pg-connection.ts +159 -0
- package/src/index.ts +19 -0
- package/src/interfaces/Authorizable.ts +8 -0
- package/src/interfaces/IAuthzkitConfig.ts +19 -0
- package/src/interfaces/Permission.ts +4 -0
- package/src/interfaces/Role.ts +5 -0
- package/src/middleware/authzMiddleware.ts +60 -0
- package/src/stores/IAuthzkitStore.ts +33 -0
- package/src/stores/InMemoryAuthzkitStore.ts +101 -0
- package/src/stores/MongoAuthzkitStore.ts +171 -0
- package/src/stores/PgAuthzkitStore.ts +191 -0
- package/src/utils/envConfig.ts +70 -0
- package/tests/Authzkit.test.ts +157 -0
- package/tests/MongoAuthzkitStore.test.ts +204 -0
- package/tests/MongoAuthzkitStoreCustom.test.ts +75 -0
- package/tests/PgAuthzkitStore.test.ts +207 -0
- package/tests/PgAuthzkitStoreCustom.test.ts +90 -0
- 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,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,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,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,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
|
+
}
|