@intranefr/superbackend 1.5.2 → 1.6.3
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/cookies.txt +6 -0
- package/cookies1.txt +6 -0
- package/cookies2.txt +6 -0
- package/cookies3.txt +6 -0
- package/cookies4.txt +5 -0
- package/cookies_old.txt +5 -0
- package/cookies_old_test.txt +6 -0
- package/cookies_super.txt +5 -0
- package/cookies_super_test.txt +6 -0
- package/cookies_test.txt +6 -0
- package/index.js +9 -0
- package/manage.js +745 -0
- package/package.json +6 -2
- package/plugins/core-waiting-list-migration/README.md +118 -0
- package/plugins/core-waiting-list-migration/index.js +438 -0
- package/plugins/global-settings-presets/index.js +20 -0
- package/plugins/hello-cli/index.js +17 -0
- package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
- package/plugins/ui-components-seeder/components/suiToast.js +186 -0
- package/plugins/ui-components-seeder/index.js +31 -0
- package/public/js/admin-ui-components-preview.js +281 -0
- package/public/js/admin-ui-components.js +408 -0
- package/public/js/llm-provider-model-picker.js +193 -0
- package/public/test-iframe-fix.html +63 -0
- package/public/test-iframe.html +14 -0
- package/src/admin/endpointRegistry.js +68 -0
- package/src/controllers/admin.controller.js +36 -10
- package/src/controllers/adminAgents.controller.js +37 -0
- package/src/controllers/adminDataCleanup.controller.js +45 -0
- package/src/controllers/adminLlm.controller.js +19 -8
- package/src/controllers/adminLogin.controller.js +269 -0
- package/src/controllers/adminMarkdowns.controller.js +157 -0
- package/src/controllers/adminPlugins.controller.js +55 -0
- package/src/controllers/adminRegistry.controller.js +106 -0
- package/src/controllers/adminScripts.controller.js +138 -0
- package/src/controllers/adminStats.controller.js +4 -4
- package/src/controllers/adminTelegram.controller.js +72 -0
- package/src/controllers/markdowns.controller.js +42 -0
- package/src/controllers/registry.controller.js +32 -0
- package/src/controllers/waitingList.controller.js +52 -74
- package/src/helpers/mongooseHelper.js +6 -6
- package/src/helpers/scriptBase.js +2 -2
- package/src/middleware/auth.js +71 -1
- package/src/middleware/rbac.js +62 -0
- package/src/middleware.js +584 -176
- package/src/models/Agent.js +105 -0
- package/src/models/AgentMessage.js +82 -0
- package/src/models/GlobalSetting.js +11 -1
- package/src/models/Markdown.js +75 -0
- package/src/models/ScriptRun.js +8 -0
- package/src/models/TelegramBot.js +42 -0
- package/src/models/UiComponent.js +2 -0
- package/src/models/User.js +1 -1
- package/src/routes/admin.routes.js +3 -3
- package/src/routes/adminAgents.routes.js +13 -0
- package/src/routes/adminAssets.routes.js +11 -11
- package/src/routes/adminBlog.routes.js +2 -2
- package/src/routes/adminBlogAi.routes.js +2 -2
- package/src/routes/adminBlogAutomation.routes.js +2 -2
- package/src/routes/adminCache.routes.js +2 -2
- package/src/routes/adminConsoleManager.routes.js +2 -2
- package/src/routes/adminCrons.routes.js +2 -2
- package/src/routes/adminDataCleanup.routes.js +26 -0
- package/src/routes/adminDbBrowser.routes.js +2 -2
- package/src/routes/adminEjsVirtual.routes.js +2 -2
- package/src/routes/adminFeatureFlags.routes.js +6 -6
- package/src/routes/adminHeadless.routes.js +2 -2
- package/src/routes/adminHealthChecks.routes.js +2 -2
- package/src/routes/adminI18n.routes.js +2 -2
- package/src/routes/adminJsonConfigs.routes.js +8 -8
- package/src/routes/adminLlm.routes.js +8 -7
- package/src/routes/adminLogin.routes.js +23 -0
- package/src/routes/adminMarkdowns.routes.js +10 -0
- package/src/routes/adminMigration.routes.js +12 -12
- package/src/routes/adminPages.routes.js +2 -2
- package/src/routes/adminPlugins.routes.js +15 -0
- package/src/routes/adminProxy.routes.js +2 -2
- package/src/routes/adminRateLimits.routes.js +8 -8
- package/src/routes/adminRbac.routes.js +2 -2
- package/src/routes/adminRegistry.routes.js +24 -0
- package/src/routes/adminScripts.routes.js +6 -3
- package/src/routes/adminSeoConfig.routes.js +10 -10
- package/src/routes/adminTelegram.routes.js +14 -0
- package/src/routes/adminTerminals.routes.js +2 -2
- package/src/routes/adminUiComponents.routes.js +2 -2
- package/src/routes/adminUploadNamespaces.routes.js +7 -7
- package/src/routes/blogInternal.routes.js +2 -2
- package/src/routes/experiments.routes.js +2 -2
- package/src/routes/formsAdmin.routes.js +6 -6
- package/src/routes/globalSettings.routes.js +8 -8
- package/src/routes/internalExperiments.routes.js +2 -2
- package/src/routes/markdowns.routes.js +16 -0
- package/src/routes/notificationAdmin.routes.js +7 -7
- package/src/routes/orgAdmin.routes.js +16 -16
- package/src/routes/pages.routes.js +3 -3
- package/src/routes/registry.routes.js +11 -0
- package/src/routes/stripeAdmin.routes.js +12 -12
- package/src/routes/userAdmin.routes.js +7 -7
- package/src/routes/waitingListAdmin.routes.js +2 -2
- package/src/routes/workflows.routes.js +3 -3
- package/src/services/agent.service.js +546 -0
- package/src/services/agentHistory.service.js +345 -0
- package/src/services/agentTools.service.js +578 -0
- package/src/services/dataCleanup.service.js +286 -0
- package/src/services/jsonConfigs.service.js +284 -10
- package/src/services/llm.service.js +219 -6
- package/src/services/markdowns.service.js +522 -0
- package/src/services/plugins.service.js +348 -0
- package/src/services/registry.service.js +452 -0
- package/src/services/scriptsRunner.service.js +328 -37
- package/src/services/telegram.service.js +130 -0
- package/src/services/uiComponents.service.js +180 -0
- package/src/services/waitingListJson.service.js +401 -0
- package/src/utils/rbac/rightsRegistry.js +118 -0
- package/test-access.js +63 -0
- package/test-iframe-fix.html +63 -0
- package/test-iframe.html +14 -0
- package/views/admin-403.ejs +92 -0
- package/views/admin-agents.ejs +273 -0
- package/views/admin-coolify-deploy.ejs +8 -8
- package/views/admin-dashboard-home.ejs +52 -2
- package/views/admin-dashboard.ejs +179 -7
- package/views/admin-data-cleanup.ejs +357 -0
- package/views/admin-experiments.ejs +1 -1
- package/views/admin-login.ejs +286 -0
- package/views/admin-markdowns.ejs +905 -0
- package/views/admin-plugins-system.ejs +223 -0
- package/views/admin-scripts.ejs +221 -4
- package/views/admin-telegram.ejs +269 -0
- package/views/admin-ui-components.ejs +82 -402
- package/views/admin-users.ejs +207 -11
- package/views/partials/dashboard/nav-items.ejs +5 -0
- package/views/partials/llm-provider-model-picker.ejs +0 -161
- package/analysis-only.skill +0 -0
|
@@ -415,6 +415,74 @@ const endpointRegistry = [
|
|
|
415
415
|
},
|
|
416
416
|
],
|
|
417
417
|
},
|
|
418
|
+
{
|
|
419
|
+
id: "open-registry",
|
|
420
|
+
title: "Open Registry",
|
|
421
|
+
endpoints: [
|
|
422
|
+
{
|
|
423
|
+
id: "registry-auth",
|
|
424
|
+
method: "GET",
|
|
425
|
+
path: "/registry/plugins/auth",
|
|
426
|
+
auth: "none|bearer",
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
id: "registry-list",
|
|
430
|
+
method: "GET",
|
|
431
|
+
path: "/registry/plugins/list?category=plugins&minimal=true",
|
|
432
|
+
auth: "none|bearer",
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
id: "admin-registries-list",
|
|
436
|
+
method: "GET",
|
|
437
|
+
path: "/api/admin/registries",
|
|
438
|
+
auth: "basic",
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
id: "admin-registries-create",
|
|
442
|
+
method: "POST",
|
|
443
|
+
path: "/api/admin/registries",
|
|
444
|
+
auth: "basic",
|
|
445
|
+
bodyExample: { id: "plugins", name: "Plugins Registry", public: true, categories: ["plugins"] },
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
id: "admin-registry-item-upsert",
|
|
449
|
+
method: "POST",
|
|
450
|
+
path: "/api/admin/registries/plugins/items",
|
|
451
|
+
auth: "basic",
|
|
452
|
+
bodyExample: { id: "hello-cli", name: "hello-cli", category: "plugins", version: 1, versions: [1], description: "Sample" },
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
id: "plugins-system",
|
|
458
|
+
title: "Plugins System",
|
|
459
|
+
endpoints: [
|
|
460
|
+
{
|
|
461
|
+
id: "plugins-list",
|
|
462
|
+
method: "GET",
|
|
463
|
+
path: "/api/admin/plugins",
|
|
464
|
+
auth: "basic",
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: "plugins-enable",
|
|
468
|
+
method: "POST",
|
|
469
|
+
path: "/api/admin/plugins/:id/enable",
|
|
470
|
+
auth: "basic",
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
id: "plugins-disable",
|
|
474
|
+
method: "POST",
|
|
475
|
+
path: "/api/admin/plugins/:id/disable",
|
|
476
|
+
auth: "basic",
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: "plugins-install",
|
|
480
|
+
method: "POST",
|
|
481
|
+
path: "/api/admin/plugins/:id/install",
|
|
482
|
+
auth: "basic",
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
},
|
|
418
486
|
];
|
|
419
487
|
|
|
420
488
|
module.exports = endpointRegistry;
|
|
@@ -7,6 +7,8 @@ const Notification = require('../models/Notification');
|
|
|
7
7
|
const Invite = require('../models/Invite');
|
|
8
8
|
const EmailLog = require('../models/EmailLog');
|
|
9
9
|
const FormSubmission = require('../models/FormSubmission');
|
|
10
|
+
const RbacRole = require('../models/RbacRole');
|
|
11
|
+
const RbacUserRole = require('../models/RbacUserRole');
|
|
10
12
|
const asyncHandler = require('../utils/asyncHandler');
|
|
11
13
|
const fs = require('fs');
|
|
12
14
|
const path = require('path');
|
|
@@ -56,10 +58,6 @@ const registerUser = asyncHandler(async (req, res) => {
|
|
|
56
58
|
return res.status(400).json({ error: 'Password must be at least 6 characters' });
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
if (!['user', 'admin'].includes(role)) {
|
|
60
|
-
return res.status(400).json({ error: 'Role must be either "user" or "admin"' });
|
|
61
|
-
}
|
|
62
|
-
|
|
63
61
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
64
62
|
if (!emailRegex.test(email)) {
|
|
65
63
|
return res.status(400).json({ error: 'Invalid email format' });
|
|
@@ -76,11 +74,33 @@ const registerUser = asyncHandler(async (req, res) => {
|
|
|
76
74
|
email: email.toLowerCase(),
|
|
77
75
|
passwordHash: password, // Will be hashed by pre-save hook
|
|
78
76
|
name: name || '',
|
|
79
|
-
role: role
|
|
77
|
+
role: role // Keep for backward compatibility
|
|
80
78
|
});
|
|
81
79
|
|
|
82
80
|
await user.save();
|
|
83
81
|
|
|
82
|
+
// Assign RBAC role if it's not 'user'
|
|
83
|
+
if (role !== 'user') {
|
|
84
|
+
try {
|
|
85
|
+
// Find the RBAC role
|
|
86
|
+
const rbacRole = await RbacRole.findOne({ key: role, status: 'active' });
|
|
87
|
+
if (rbacRole) {
|
|
88
|
+
// Create user-role assignment
|
|
89
|
+
const userRoleAssignment = new RbacUserRole({
|
|
90
|
+
userId: user._id,
|
|
91
|
+
roleId: rbacRole._id
|
|
92
|
+
});
|
|
93
|
+
await userRoleAssignment.save();
|
|
94
|
+
console.log(`Assigned RBAC role '${role}' to user ${user.email}`);
|
|
95
|
+
} else {
|
|
96
|
+
console.warn(`RBAC role '${role}' not found, user created without RBAC role assignment`);
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('Error assigning RBAC role:', error);
|
|
100
|
+
// Don't fail the user creation if RBAC assignment fails
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
84
104
|
// Log the admin action
|
|
85
105
|
console.log(`Admin registered new user: ${user.email} with role: ${user.role}`);
|
|
86
106
|
|
|
@@ -352,7 +372,7 @@ const getWebhookStats = asyncHandler(async (req, res) => {
|
|
|
352
372
|
const provisionCoolifyDeploy = asyncHandler(async (req, res) => {
|
|
353
373
|
try {
|
|
354
374
|
const { overwrite } = req.body;
|
|
355
|
-
const managePath = path.join(process.cwd(), "manage.
|
|
375
|
+
const managePath = path.join(process.cwd(), "manage.js");
|
|
356
376
|
const exists = fs.existsSync(managePath);
|
|
357
377
|
|
|
358
378
|
if (exists && !overwrite) {
|
|
@@ -363,13 +383,19 @@ const provisionCoolifyDeploy = asyncHandler(async (req, res) => {
|
|
|
363
383
|
});
|
|
364
384
|
}
|
|
365
385
|
|
|
366
|
-
//
|
|
367
|
-
|
|
386
|
+
// Copy the improved manage.js from polybot
|
|
387
|
+
const sourceManageJs = path.join(__dirname, "../../../manage.js");
|
|
388
|
+
if (fs.existsSync(sourceManageJs)) {
|
|
389
|
+
fs.copyFileSync(sourceManageJs, managePath);
|
|
390
|
+
// Make it executable
|
|
391
|
+
fs.chmodSync(managePath, '755');
|
|
392
|
+
}
|
|
393
|
+
|
|
368
394
|
res.json({
|
|
369
395
|
success: true,
|
|
370
396
|
message: exists
|
|
371
|
-
? "Coolify Headless Deploy script (manage.
|
|
372
|
-
: "Coolify Headless Deploy script (manage.
|
|
397
|
+
? "Coolify Headless Deploy script (manage.js) was updated."
|
|
398
|
+
: "Coolify Headless Deploy script (manage.js) is ready in the root directory.",
|
|
373
399
|
path: managePath,
|
|
374
400
|
});
|
|
375
401
|
} catch (error) {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const Agent = require('../models/Agent');
|
|
2
|
+
|
|
3
|
+
exports.listAgents = async (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const agents = await Agent.find().lean();
|
|
6
|
+
return res.json({ items: agents });
|
|
7
|
+
} catch (error) {
|
|
8
|
+
return res.status(500).json({ error: error.message });
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
exports.createAgent = async (req, res) => {
|
|
13
|
+
try {
|
|
14
|
+
const agent = await Agent.create(req.body);
|
|
15
|
+
return res.json(agent);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return res.status(500).json({ error: error.message });
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
exports.updateAgent = async (req, res) => {
|
|
22
|
+
try {
|
|
23
|
+
const agent = await Agent.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
|
24
|
+
return res.json(agent);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return res.status(500).json({ error: error.message });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
exports.deleteAgent = async (req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
await Agent.findByIdAndDelete(req.params.id);
|
|
33
|
+
return res.json({ success: true });
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return res.status(500).json({ error: error.message });
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const dataCleanup = require('../services/dataCleanup.service');
|
|
2
|
+
|
|
3
|
+
exports.getOverview = async (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const data = await dataCleanup.getOverviewStats();
|
|
6
|
+
return res.json(data);
|
|
7
|
+
} catch (err) {
|
|
8
|
+
const safe = dataCleanup.toSafeJsonError(err);
|
|
9
|
+
return res.status(safe.status).json(safe.body);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
exports.dryRun = async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const out = await dataCleanup.dryRunCollectionCleanup(req.body || {});
|
|
16
|
+
return res.json(out);
|
|
17
|
+
} catch (err) {
|
|
18
|
+
const safe = dataCleanup.toSafeJsonError(err);
|
|
19
|
+
return res.status(safe.status).json(safe.body);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
exports.execute = async (req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
const out = await dataCleanup.executeCollectionCleanup(req.body || {});
|
|
26
|
+
return res.json(out);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
const safe = dataCleanup.toSafeJsonError(err);
|
|
29
|
+
return res.status(safe.status).json(safe.body);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
exports.inferFields = async (req, res) => {
|
|
34
|
+
try {
|
|
35
|
+
const { collection } = req.query;
|
|
36
|
+
if (!collection) {
|
|
37
|
+
return res.status(400).json({ error: 'collection query parameter is required' });
|
|
38
|
+
}
|
|
39
|
+
const fields = await dataCleanup.inferCollectionFields(collection);
|
|
40
|
+
return res.json({ fields });
|
|
41
|
+
} catch (err) {
|
|
42
|
+
const safe = dataCleanup.toSafeJsonError(err);
|
|
43
|
+
return res.status(safe.status).json(safe.body);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -43,10 +43,6 @@ async function setStringSetting(key, value, description) {
|
|
|
43
43
|
await existing.save();
|
|
44
44
|
return existing;
|
|
45
45
|
}
|
|
46
|
-
// Ensure we never create a document with an undefined value
|
|
47
|
-
if (stringValue === undefined) {
|
|
48
|
-
throw new Error(`Cannot save GlobalSetting with undefined value for key: ${key}`);
|
|
49
|
-
}
|
|
50
46
|
const created = new GlobalSetting({
|
|
51
47
|
key,
|
|
52
48
|
value: stringValue,
|
|
@@ -79,10 +75,6 @@ async function setJsonSetting(key, value) {
|
|
|
79
75
|
await existing.save();
|
|
80
76
|
return existing;
|
|
81
77
|
}
|
|
82
|
-
// Ensure we never create a document with an empty value
|
|
83
|
-
if (!stringValue) {
|
|
84
|
-
throw new Error(`Cannot save GlobalSetting with empty value for key: ${key}`);
|
|
85
|
-
}
|
|
86
78
|
const created = new GlobalSetting({
|
|
87
79
|
key,
|
|
88
80
|
value: stringValue,
|
|
@@ -387,6 +379,24 @@ async function listCosts(req, res) {
|
|
|
387
379
|
}
|
|
388
380
|
}
|
|
389
381
|
|
|
382
|
+
async function listProviders(req, res) {
|
|
383
|
+
try {
|
|
384
|
+
const providers = await getJsonSetting(PROVIDERS_KEY, {});
|
|
385
|
+
const safeProviders = {};
|
|
386
|
+
if (providers && typeof providers === "object") {
|
|
387
|
+
for (const [key, value] of Object.entries(providers)) {
|
|
388
|
+
if (!value || typeof value !== "object") continue;
|
|
389
|
+
const { apiKey, ...rest } = value;
|
|
390
|
+
safeProviders[key] = rest;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
res.json({ providers: safeProviders });
|
|
394
|
+
} catch (error) {
|
|
395
|
+
console.error("[adminLlm] listProviders error", error);
|
|
396
|
+
res.status(500).json({ error: "Failed to load providers" });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
390
400
|
module.exports = {
|
|
391
401
|
getConfig,
|
|
392
402
|
saveConfig,
|
|
@@ -394,4 +404,5 @@ module.exports = {
|
|
|
394
404
|
listAudit,
|
|
395
405
|
listCosts,
|
|
396
406
|
listOpenRouterModels,
|
|
407
|
+
listProviders,
|
|
397
408
|
};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
const User = require('../models/User');
|
|
2
|
+
const asyncHandler = require('../utils/asyncHandler');
|
|
3
|
+
const rbacService = require('../services/rbac.service');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Auto-detect authentication type based on identifier format
|
|
7
|
+
* @param {string} identifier - Email or username
|
|
8
|
+
* @returns {string} 'iam' for email format, 'basic' for username format
|
|
9
|
+
*/
|
|
10
|
+
function detectAuthType(identifier) {
|
|
11
|
+
return identifier.includes('@') ? 'iam' : 'basic';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validate basic auth credentials against environment variables
|
|
16
|
+
* @param {string} username
|
|
17
|
+
* @param {string} password
|
|
18
|
+
* @returns {boolean} true if credentials are valid
|
|
19
|
+
*/
|
|
20
|
+
function validateBasicAuth(username, password) {
|
|
21
|
+
const adminUsername = process.env.ADMIN_USERNAME || "admin";
|
|
22
|
+
const adminPassword = process.env.ADMIN_PASSWORD || "admin";
|
|
23
|
+
|
|
24
|
+
return username === adminUsername && password === adminPassword;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Serve the admin login page
|
|
29
|
+
*/
|
|
30
|
+
const getLogin = asyncHandler(async (req, res) => {
|
|
31
|
+
// If already authenticated, redirect to admin dashboard
|
|
32
|
+
if (req.session && req.session.authenticated) {
|
|
33
|
+
return res.redirect(req.adminPath || '/admin');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const templatePath = require('path').join(__dirname, '..', '..', 'views', 'admin-login.ejs');
|
|
37
|
+
const fs = require('fs');
|
|
38
|
+
const ejs = require('ejs');
|
|
39
|
+
|
|
40
|
+
fs.readFile(templatePath, 'utf8', (err, template) => {
|
|
41
|
+
if (err) {
|
|
42
|
+
console.error('Error reading login template:', err);
|
|
43
|
+
return res.status(500).send('Error loading login page');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const html = ejs.render(template, {
|
|
48
|
+
baseUrl: req.baseUrl,
|
|
49
|
+
adminPath: req.adminPath || '/admin',
|
|
50
|
+
error: req.query.error || null,
|
|
51
|
+
success: req.query.success || null
|
|
52
|
+
}, {
|
|
53
|
+
filename: templatePath
|
|
54
|
+
});
|
|
55
|
+
res.send(html);
|
|
56
|
+
} catch (renderErr) {
|
|
57
|
+
console.error('Error rendering login template:', renderErr);
|
|
58
|
+
res.status(500).send('Error rendering login page');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Process login credentials (supports both basic auth and IAM)
|
|
65
|
+
*/
|
|
66
|
+
const postLogin = asyncHandler(async (req, res) => {
|
|
67
|
+
const { identifier, password } = req.body;
|
|
68
|
+
|
|
69
|
+
if (!identifier || !password) {
|
|
70
|
+
return res.redirect(`${req.adminPath || '/admin'}/login?error=Email/username and password are required`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const authType = detectAuthType(identifier);
|
|
74
|
+
let user = null;
|
|
75
|
+
let authData = {};
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
if (authType === 'iam') {
|
|
79
|
+
// IAM authentication - validate against User model
|
|
80
|
+
user = await User.findOne({ email: identifier.toLowerCase() });
|
|
81
|
+
|
|
82
|
+
if (!user) {
|
|
83
|
+
return res.redirect(`${req.adminPath || '/admin'}/login?error=Invalid credentials`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check RBAC permissions for admin panel access with backward compatibility
|
|
87
|
+
const RbacUserRole = require('../models/RbacUserRole');
|
|
88
|
+
const RbacRole = require('../models/RbacRole');
|
|
89
|
+
const RbacGrant = require('../models/RbacGrant');
|
|
90
|
+
const { matches } = require('../utils/rbac/engine');
|
|
91
|
+
|
|
92
|
+
let hasAdminAccess = false;
|
|
93
|
+
|
|
94
|
+
// Phase 1: Try RBAC assignment with pattern matching
|
|
95
|
+
const userRoleAssignment = await RbacUserRole.findOne({ userId: user._id });
|
|
96
|
+
if (userRoleAssignment) {
|
|
97
|
+
const userRole = await RbacRole.findById(userRoleAssignment.roleId);
|
|
98
|
+
if (userRole && userRole.status === 'active') {
|
|
99
|
+
// Get all grants for this role
|
|
100
|
+
const grants = await RbacGrant.find({
|
|
101
|
+
subjectType: 'role',
|
|
102
|
+
subjectId: userRole._id,
|
|
103
|
+
scopeType: 'global',
|
|
104
|
+
effect: 'allow'
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Check if any grant matches admin_panel__login using pattern matching
|
|
108
|
+
hasAdminAccess = grants.some(grant =>
|
|
109
|
+
matches('admin_panel__login', grant.right)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
console.log(`RBAC check for user ${user.email}: role=${userRole.key}, grants=${grants.length}, hasAccess=${hasAdminAccess}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Phase 2: Fallback to IAM role for backward compatibility
|
|
117
|
+
if (!hasAdminAccess && ['admin', 'superadmin'].includes(user.role)) {
|
|
118
|
+
console.log(`Fallback to IAM role for user ${user.email}: role=${user.role}`);
|
|
119
|
+
hasAdminAccess = true; // Admin and superadmin roles get panel access
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!hasAdminAccess) {
|
|
123
|
+
return res.redirect(`${req.adminPath || '/admin'}/login?error=Insufficient permissions - Admin panel access required`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Validate password
|
|
127
|
+
const isMatch = await user.comparePassword(password);
|
|
128
|
+
if (!isMatch) {
|
|
129
|
+
return res.redirect(`${req.adminPath || '/admin'}/login?error=Invalid credentials`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Get user's RBAC roles for session data
|
|
133
|
+
let rbacRoles = [];
|
|
134
|
+
try {
|
|
135
|
+
// Get role information directly
|
|
136
|
+
if (userRoleAssignment) {
|
|
137
|
+
const userRole = await RbacRole.findById(userRoleAssignment.roleId);
|
|
138
|
+
if (userRole) {
|
|
139
|
+
rbacRoles = [{
|
|
140
|
+
roleId: userRole._id,
|
|
141
|
+
roleKey: userRole.key,
|
|
142
|
+
roleName: userRole.name,
|
|
143
|
+
grants: [] // We could populate this if needed
|
|
144
|
+
}];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('Error fetching RBAC roles:', error);
|
|
149
|
+
// Continue without RBAC roles if there's an error
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Store IAM user session data with RBAC context
|
|
153
|
+
authData = {
|
|
154
|
+
authType: 'iam',
|
|
155
|
+
userId: user._id,
|
|
156
|
+
email: user.email,
|
|
157
|
+
name: user.name,
|
|
158
|
+
role: user.role, // Keep for backward compatibility
|
|
159
|
+
rbacRoles: rbacRoles, // New RBAC context
|
|
160
|
+
authenticated: true,
|
|
161
|
+
loginTime: new Date().toISOString()
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
} else {
|
|
165
|
+
// Basic auth authentication - validate against environment variables
|
|
166
|
+
const isValid = validateBasicAuth(identifier, password);
|
|
167
|
+
|
|
168
|
+
if (!isValid) {
|
|
169
|
+
return res.redirect(`${req.adminPath || '/admin'}/login?error=Invalid credentials`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Store basic auth session data
|
|
173
|
+
authData = {
|
|
174
|
+
authType: 'basic',
|
|
175
|
+
username: identifier,
|
|
176
|
+
role: 'admin', // Basic auth users have admin privileges
|
|
177
|
+
authenticated: true,
|
|
178
|
+
loginTime: new Date().toISOString()
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Create session
|
|
183
|
+
req.session = req.session || {};
|
|
184
|
+
Object.assign(req.session, authData);
|
|
185
|
+
|
|
186
|
+
// Regenerate session to prevent fixation
|
|
187
|
+
req.session.regenerate((err) => {
|
|
188
|
+
if (err) {
|
|
189
|
+
console.error('Error regenerating session:', err);
|
|
190
|
+
return res.redirect(`${req.adminPath || '/admin'}/login?error=Session error`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Store auth data in new session
|
|
194
|
+
Object.assign(req.session, authData);
|
|
195
|
+
|
|
196
|
+
// Save session and redirect
|
|
197
|
+
req.session.save((saveErr) => {
|
|
198
|
+
if (saveErr) {
|
|
199
|
+
console.error('Error saving session:', saveErr);
|
|
200
|
+
return res.redirect(`${req.adminPath || '/admin'}/login?error=Session save error`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Redirect to admin dashboard or originally requested URL
|
|
204
|
+
const redirectTo = req.session.returnTo || (req.adminPath || '/admin');
|
|
205
|
+
delete req.session.returnTo;
|
|
206
|
+
res.redirect(redirectTo);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('Login error:', error);
|
|
212
|
+
res.redirect(`${req.adminPath || '/admin'}/login?error=Authentication failed`);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Logout user and clear session
|
|
218
|
+
*/
|
|
219
|
+
const postLogout = asyncHandler(async (req, res) => {
|
|
220
|
+
req.session.destroy((err) => {
|
|
221
|
+
if (err) {
|
|
222
|
+
console.error('Error destroying session:', err);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
res.redirect(`${req.adminPath || '/admin'}/login?success=Logged out successfully`);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check current authentication status (API endpoint)
|
|
231
|
+
*/
|
|
232
|
+
const getAuthStatus = asyncHandler(async (req, res) => {
|
|
233
|
+
if (!req.session || !req.session.authenticated) {
|
|
234
|
+
return res.json({
|
|
235
|
+
authenticated: false,
|
|
236
|
+
authType: null,
|
|
237
|
+
user: null
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const authData = {
|
|
242
|
+
authenticated: req.session.authenticated,
|
|
243
|
+
authType: req.session.authType,
|
|
244
|
+
loginTime: req.session.loginTime
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
if (req.session.authType === 'iam') {
|
|
248
|
+
authData.user = {
|
|
249
|
+
id: req.session.userId,
|
|
250
|
+
email: req.session.email,
|
|
251
|
+
name: req.session.name,
|
|
252
|
+
role: req.session.role
|
|
253
|
+
};
|
|
254
|
+
} else {
|
|
255
|
+
authData.user = {
|
|
256
|
+
username: req.session.username,
|
|
257
|
+
role: req.session.role
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
res.json(authData);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
module.exports = {
|
|
265
|
+
getLogin,
|
|
266
|
+
postLogin,
|
|
267
|
+
postLogout,
|
|
268
|
+
getAuthStatus
|
|
269
|
+
};
|