lapeeh 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/.env.example +14 -0
- package/LICENSE +21 -0
- package/bin/index.js +934 -0
- package/doc/en/ARCHITECTURE_GUIDE.md +79 -0
- package/doc/en/CHANGELOG.md +203 -0
- package/doc/en/CHEATSHEET.md +90 -0
- package/doc/en/CLI.md +111 -0
- package/doc/en/CONTRIBUTING.md +119 -0
- package/doc/en/DEPLOYMENT.md +171 -0
- package/doc/en/FAQ.md +69 -0
- package/doc/en/FEATURES.md +99 -0
- package/doc/en/GETTING_STARTED.md +84 -0
- package/doc/en/INTRODUCTION.md +62 -0
- package/doc/en/PACKAGES.md +63 -0
- package/doc/en/PERFORMANCE.md +98 -0
- package/doc/en/ROADMAP.md +104 -0
- package/doc/en/SECURITY.md +95 -0
- package/doc/en/STRUCTURE.md +79 -0
- package/doc/en/TUTORIAL.md +145 -0
- package/doc/id/ARCHITECTURE_GUIDE.md +76 -0
- package/doc/id/CHANGELOG.md +203 -0
- package/doc/id/CHEATSHEET.md +90 -0
- package/doc/id/CLI.md +139 -0
- package/doc/id/CONTRIBUTING.md +119 -0
- package/doc/id/DEPLOYMENT.md +171 -0
- package/doc/id/FAQ.md +69 -0
- package/doc/id/FEATURES.md +169 -0
- package/doc/id/GETTING_STARTED.md +91 -0
- package/doc/id/INTRODUCTION.md +62 -0
- package/doc/id/PACKAGES.md +63 -0
- package/doc/id/PERFORMANCE.md +100 -0
- package/doc/id/ROADMAP.md +107 -0
- package/doc/id/SECURITY.md +94 -0
- package/doc/id/STRUCTURE.md +79 -0
- package/doc/id/TUTORIAL.md +145 -0
- package/docker-compose.yml +24 -0
- package/ecosystem.config.js +17 -0
- package/eslint.config.mjs +26 -0
- package/gitignore.template +30 -0
- package/lib/bootstrap.ts +210 -0
- package/lib/core/realtime.ts +34 -0
- package/lib/core/redis.ts +139 -0
- package/lib/core/serializer.ts +63 -0
- package/lib/core/server.ts +70 -0
- package/lib/core/store.ts +116 -0
- package/lib/middleware/auth.ts +63 -0
- package/lib/middleware/error.ts +50 -0
- package/lib/middleware/multipart.ts +13 -0
- package/lib/middleware/rateLimit.ts +14 -0
- package/lib/middleware/requestLogger.ts +27 -0
- package/lib/middleware/visitor.ts +178 -0
- package/lib/utils/logger.ts +100 -0
- package/lib/utils/pagination.ts +56 -0
- package/lib/utils/response.ts +88 -0
- package/lib/utils/validator.ts +394 -0
- package/nodemon.json +6 -0
- package/package.json +126 -0
- package/readme.md +357 -0
- package/scripts/check-update.js +92 -0
- package/scripts/config-clear.js +45 -0
- package/scripts/generate-jwt-secret.js +38 -0
- package/scripts/init-project.js +84 -0
- package/scripts/make-module.js +89 -0
- package/scripts/release.js +494 -0
- package/scripts/seed-json.js +158 -0
- package/scripts/verify-rbac-functional.js +187 -0
- package/src/config/app.ts +9 -0
- package/src/config/cors.ts +5 -0
- package/src/modules/Auth/auth.controller.ts +519 -0
- package/src/modules/Rbac/rbac.controller.ts +533 -0
- package/src/routes/auth.ts +74 -0
- package/src/routes/index.ts +7 -0
- package/src/routes/rbac.ts +42 -0
- package/storage/logs/.gitkeep +0 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const bcrypt = require('bcryptjs');
|
|
5
|
+
const { v4: uuidv4 } = require('uuid');
|
|
6
|
+
|
|
7
|
+
const DB_PATH = path.join(__dirname, '../database.json');
|
|
8
|
+
|
|
9
|
+
async function seed() {
|
|
10
|
+
console.log('š± Seeding database.json...');
|
|
11
|
+
|
|
12
|
+
let store = {
|
|
13
|
+
users: [],
|
|
14
|
+
roles: [],
|
|
15
|
+
permissions: [],
|
|
16
|
+
user_roles: [],
|
|
17
|
+
role_permissions: [],
|
|
18
|
+
user_permissions: []
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Load existing if any (optional, but we'll overwrite for seed)
|
|
22
|
+
// if (fs.existsSync(DB_PATH)) {
|
|
23
|
+
// store = JSON.parse(fs.readFileSync(DB_PATH, 'utf-8'));
|
|
24
|
+
// }
|
|
25
|
+
|
|
26
|
+
const passwordHash = await bcrypt.hash('password123', 10);
|
|
27
|
+
|
|
28
|
+
// 1. Seed Users
|
|
29
|
+
const usersData = [
|
|
30
|
+
{ name: "Super Admin", email: "sa@sa.com", roleSlug: "super_admin" },
|
|
31
|
+
{ name: "Admin User", email: "a@a.com", roleSlug: "admin" },
|
|
32
|
+
{ name: "Regular User", email: "u@u.com", roleSlug: "user" }
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
for (const u of usersData) {
|
|
36
|
+
const user = {
|
|
37
|
+
id: (store.users.length + 1).toString(),
|
|
38
|
+
uuid: uuidv4(),
|
|
39
|
+
name: u.name,
|
|
40
|
+
email: u.email,
|
|
41
|
+
password: passwordHash,
|
|
42
|
+
created_at: new Date(),
|
|
43
|
+
updated_at: new Date(),
|
|
44
|
+
avatar: null,
|
|
45
|
+
avatar_url: null,
|
|
46
|
+
email_verified_at: null,
|
|
47
|
+
remember_token: null
|
|
48
|
+
};
|
|
49
|
+
store.users.push(user);
|
|
50
|
+
console.log(`Created user: ${user.name}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. Seed Roles
|
|
54
|
+
const rolesData = [
|
|
55
|
+
{ name: "Super Admin", slug: "super_admin", description: "Full access to all resources" },
|
|
56
|
+
{ name: "Admin", slug: "admin", description: "Administrator" },
|
|
57
|
+
{ name: "User", slug: "user", description: "Regular user" }
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
for (const r of rolesData) {
|
|
61
|
+
const role = {
|
|
62
|
+
id: (store.roles.length + 1).toString(),
|
|
63
|
+
name: r.name,
|
|
64
|
+
slug: r.slug,
|
|
65
|
+
description: r.description,
|
|
66
|
+
created_at: new Date(),
|
|
67
|
+
updated_at: new Date()
|
|
68
|
+
};
|
|
69
|
+
store.roles.push(role);
|
|
70
|
+
console.log(`Created role: ${role.name}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 3. Seed Permissions
|
|
74
|
+
const permissionsData = [
|
|
75
|
+
{ name: "Manage Users", slug: "users.manage", description: "Create, update, and delete users" },
|
|
76
|
+
{ name: "Create Users", slug: "users.create", description: "Create users" },
|
|
77
|
+
{ name: "View Users", slug: "users.view", description: "View users" },
|
|
78
|
+
{ name: "Update Users", slug: "users.update", description: "Update users" },
|
|
79
|
+
{ name: "Delete Users", slug: "users.delete", description: "Delete users" },
|
|
80
|
+
{ name: "Manage Roles", slug: "roles.manage", description: "Create, update, and delete roles" },
|
|
81
|
+
{ name: "Manage Permissions", slug: "permissions.manage", description: "Create, update, and delete permissions" },
|
|
82
|
+
{ name: "Manage Profiles", slug: "profiles.manage", description: "Manage user profiles" }
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const p of permissionsData) {
|
|
86
|
+
const perm = {
|
|
87
|
+
id: (store.permissions.length + 1).toString(),
|
|
88
|
+
name: p.name,
|
|
89
|
+
slug: p.slug,
|
|
90
|
+
description: p.description,
|
|
91
|
+
created_at: new Date(),
|
|
92
|
+
updated_at: new Date()
|
|
93
|
+
};
|
|
94
|
+
store.permissions.push(perm);
|
|
95
|
+
}
|
|
96
|
+
console.log(`Seeded ${store.permissions.length} permissions`);
|
|
97
|
+
|
|
98
|
+
// 4. Link Users to Roles
|
|
99
|
+
for (const u of usersData) {
|
|
100
|
+
const user = store.users.find(x => x.email === u.email);
|
|
101
|
+
const role = store.roles.find(x => x.slug === u.roleSlug);
|
|
102
|
+
if (user && role) {
|
|
103
|
+
store.user_roles.push({
|
|
104
|
+
id: (store.user_roles.length + 1).toString(),
|
|
105
|
+
user_id: user.id,
|
|
106
|
+
role_id: role.id,
|
|
107
|
+
created_at: new Date()
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 5. Link Roles to Permissions
|
|
113
|
+
// Super Admin -> All Permissions
|
|
114
|
+
const superAdminRole = store.roles.find(r => r.slug === 'super_admin');
|
|
115
|
+
if (superAdminRole) {
|
|
116
|
+
for (const p of store.permissions) {
|
|
117
|
+
store.role_permissions.push({
|
|
118
|
+
id: (store.role_permissions.length + 1).toString(),
|
|
119
|
+
role_id: superAdminRole.id,
|
|
120
|
+
permission_id: p.id,
|
|
121
|
+
created_at: new Date()
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Admin -> Users.*
|
|
127
|
+
const adminRole = store.roles.find(r => r.slug === 'admin');
|
|
128
|
+
if (adminRole) {
|
|
129
|
+
const adminPerms = store.permissions.filter(p => p.slug.startsWith('users.'));
|
|
130
|
+
for (const p of adminPerms) {
|
|
131
|
+
store.role_permissions.push({
|
|
132
|
+
id: (store.role_permissions.length + 1).toString(),
|
|
133
|
+
role_id: adminRole.id,
|
|
134
|
+
permission_id: p.id,
|
|
135
|
+
created_at: new Date()
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// User -> profiles.manage
|
|
141
|
+
const userRole = store.roles.find(r => r.slug === 'user');
|
|
142
|
+
if (userRole) {
|
|
143
|
+
const userPerms = store.permissions.filter(p => p.slug === 'profiles.manage');
|
|
144
|
+
for (const p of userPerms) {
|
|
145
|
+
store.role_permissions.push({
|
|
146
|
+
id: (store.role_permissions.length + 1).toString(),
|
|
147
|
+
role_id: userRole.id,
|
|
148
|
+
permission_id: p.id,
|
|
149
|
+
created_at: new Date()
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fs.writeFileSync(DB_PATH, JSON.stringify(store, null, 2));
|
|
155
|
+
console.log('ā
Database seeded successfully!');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
seed().catch(console.error);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
|
|
2
|
+
// const fetch = require('node:fetch'); // Or native fetch in Node 18+
|
|
3
|
+
|
|
4
|
+
const BASE_URL = 'http://localhost:8000/api';
|
|
5
|
+
let TOKEN = '';
|
|
6
|
+
let USER_ID = '';
|
|
7
|
+
let ROLE_ID = '';
|
|
8
|
+
let PERMISSION_ID = '';
|
|
9
|
+
|
|
10
|
+
async function request(method, endpoint, body = null, token = null) {
|
|
11
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
12
|
+
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
13
|
+
|
|
14
|
+
const config = { method, headers };
|
|
15
|
+
if (body) config.body = JSON.stringify(body);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const res = await fetch(`${BASE_URL}${endpoint}`, config);
|
|
19
|
+
const data = await res.json();
|
|
20
|
+
return { status: res.status, data };
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(`Error requesting ${endpoint}:`, err.message);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function run() {
|
|
28
|
+
console.log('š Starting RBAC Functional Verification...');
|
|
29
|
+
|
|
30
|
+
// 0. Login as Super Admin
|
|
31
|
+
console.log('\n0ļøā£ Logging in as Super Admin...');
|
|
32
|
+
const saLogin = await request('POST', '/auth/login', {
|
|
33
|
+
email: 'sa@sa.com',
|
|
34
|
+
password: 'string'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let SA_TOKEN = '';
|
|
38
|
+
if (saLogin?.status === 200) {
|
|
39
|
+
console.log('ā
SA Login successful');
|
|
40
|
+
SA_TOKEN = saLogin.data.data.token;
|
|
41
|
+
} else {
|
|
42
|
+
console.error('ā SA Login failed:', JSON.stringify(saLogin?.data));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 1. Register User
|
|
47
|
+
console.log('\n1ļøā£ Registering User (Tester)...');
|
|
48
|
+
const regRes = await request('POST', '/auth/register', {
|
|
49
|
+
email: 'testrbac@example.com',
|
|
50
|
+
name: 'RBAC Tester',
|
|
51
|
+
password: 'password123',
|
|
52
|
+
confirmPassword: 'password123'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (regRes?.status === 200 || regRes?.status === 201) {
|
|
56
|
+
console.log('ā
Registered successfully');
|
|
57
|
+
USER_ID = regRes.data.data.id;
|
|
58
|
+
} else if (regRes?.status === 422 && regRes.data.message.includes('Validation error')) {
|
|
59
|
+
console.log('ā¹ļø User might already exist, trying login to get ID...');
|
|
60
|
+
// Login to get ID
|
|
61
|
+
const loginRes = await request('POST', '/auth/login', {
|
|
62
|
+
email: 'testrbac@example.com',
|
|
63
|
+
password: 'password123'
|
|
64
|
+
});
|
|
65
|
+
if (loginRes?.status === 200) {
|
|
66
|
+
const meRes = await request('GET', '/auth/me', null, loginRes.data.data.token);
|
|
67
|
+
USER_ID = meRes.data.data.id;
|
|
68
|
+
console.log('ā
Got existing User ID');
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
console.error('ā Registration failed:', JSON.stringify(regRes?.data));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 2. Login (as Tester) - to verify later
|
|
75
|
+
console.log('\n2ļøā£ Logging in as Tester...');
|
|
76
|
+
const loginRes = await request('POST', '/auth/login', {
|
|
77
|
+
email: 'testrbac@example.com',
|
|
78
|
+
password: 'password123'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (loginRes?.status === 200) {
|
|
82
|
+
console.log('ā
Login successful');
|
|
83
|
+
TOKEN = loginRes.data.data.token;
|
|
84
|
+
} else {
|
|
85
|
+
console.error('ā Login failed:', JSON.stringify(loginRes?.data));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 3. Create Role (Using SA Token)
|
|
90
|
+
console.log('\n3ļøā£ Creating Role "tester-role"...');
|
|
91
|
+
const roleRes = await request('POST', '/rbac/roles', {
|
|
92
|
+
name: 'Tester Role',
|
|
93
|
+
slug: 'tester-role',
|
|
94
|
+
description: 'Role for automated testing'
|
|
95
|
+
}, SA_TOKEN);
|
|
96
|
+
|
|
97
|
+
if (roleRes?.status === 201 || roleRes?.status === 200) {
|
|
98
|
+
console.log('ā
Role created');
|
|
99
|
+
ROLE_ID = roleRes.data.data.id;
|
|
100
|
+
} else if (roleRes?.status === 422) { // Duplicate?
|
|
101
|
+
console.log('ā¹ļø Role might already exist.');
|
|
102
|
+
const listRes = await request('GET', '/rbac/roles', null, SA_TOKEN);
|
|
103
|
+
const found = listRes.data.data.find(r => r.slug === 'tester-role');
|
|
104
|
+
if (found) {
|
|
105
|
+
ROLE_ID = found.id;
|
|
106
|
+
console.log('ā
Found existing role');
|
|
107
|
+
} else {
|
|
108
|
+
console.error('ā Could not create or find role');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
console.error('ā Create role failed:', JSON.stringify(roleRes?.data));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 4. Create Permission (Using SA Token)
|
|
117
|
+
console.log('\n4ļøā£ Creating Permission "test.exec"...');
|
|
118
|
+
const permRes = await request('POST', '/rbac/permissions', {
|
|
119
|
+
name: 'Test Execute',
|
|
120
|
+
slug: 'test.exec',
|
|
121
|
+
description: 'Permission to execute tests'
|
|
122
|
+
}, SA_TOKEN);
|
|
123
|
+
|
|
124
|
+
if (permRes?.status === 201 || permRes?.status === 200) {
|
|
125
|
+
console.log('ā
Permission created');
|
|
126
|
+
PERMISSION_ID = permRes.data.data.id;
|
|
127
|
+
} else if (permRes?.status === 422) {
|
|
128
|
+
const listRes = await request('GET', '/rbac/permissions', null, SA_TOKEN);
|
|
129
|
+
const found = listRes.data.data.find(p => p.slug === 'test.exec');
|
|
130
|
+
if (found) {
|
|
131
|
+
PERMISSION_ID = found.id;
|
|
132
|
+
console.log('ā
Found existing permission');
|
|
133
|
+
} else {
|
|
134
|
+
console.error('ā Could not create or find permission');
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
console.error('ā Create permission failed:', JSON.stringify(permRes?.data));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 5. Assign Permission to Role (Using SA Token)
|
|
143
|
+
console.log('\n5ļøā£ Assigning Permission to Role...');
|
|
144
|
+
const assignP2R = await request('POST', '/rbac/roles/assign-permission', {
|
|
145
|
+
roleId: ROLE_ID,
|
|
146
|
+
permissionId: PERMISSION_ID
|
|
147
|
+
}, SA_TOKEN);
|
|
148
|
+
|
|
149
|
+
if (assignP2R?.status === 200) {
|
|
150
|
+
console.log('ā
Permission assigned to role');
|
|
151
|
+
} else {
|
|
152
|
+
console.error('ā Assign permission failed:', JSON.stringify(assignP2R?.data));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 6. Assign Role to User (Using SA Token)
|
|
156
|
+
console.log('\n6ļøā£ Assigning Role to User...');
|
|
157
|
+
const assignR2U = await request('POST', '/rbac/users/assign-role', {
|
|
158
|
+
userId: USER_ID,
|
|
159
|
+
roleId: ROLE_ID
|
|
160
|
+
}, SA_TOKEN);
|
|
161
|
+
|
|
162
|
+
if (assignR2U?.status === 200) {
|
|
163
|
+
console.log('ā
Role assigned to user');
|
|
164
|
+
} else {
|
|
165
|
+
console.error('ā Assign role failed:', JSON.stringify(assignR2U?.data));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 7. Verify via Profile (Using Tester Token)
|
|
169
|
+
console.log('\n7ļøā£ Verifying Profile...');
|
|
170
|
+
const profile = await request('GET', '/auth/me', null, TOKEN);
|
|
171
|
+
console.log('š¤ User Profile:', JSON.stringify(profile.data.data));
|
|
172
|
+
|
|
173
|
+
// 8. Cleanup (Using SA Token)
|
|
174
|
+
console.log('\n8ļøā£ Cleaning up...');
|
|
175
|
+
|
|
176
|
+
const delRole = await request('DELETE', `/rbac/roles/${ROLE_ID}`, null, SA_TOKEN);
|
|
177
|
+
if (delRole?.status === 200) console.log('ā
Role deleted');
|
|
178
|
+
else console.error('ā Delete role failed', JSON.stringify(delRole?.data));
|
|
179
|
+
|
|
180
|
+
const delPerm = await request('DELETE', `/rbac/permissions/${PERMISSION_ID}`, null, SA_TOKEN);
|
|
181
|
+
if (delPerm?.status === 200) console.log('ā
Permission deleted');
|
|
182
|
+
else console.error('ā Delete permission failed');
|
|
183
|
+
|
|
184
|
+
console.log('\nš Verification Complete!');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
run();
|