@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.
Files changed (134) hide show
  1. package/cookies.txt +6 -0
  2. package/cookies1.txt +6 -0
  3. package/cookies2.txt +6 -0
  4. package/cookies3.txt +6 -0
  5. package/cookies4.txt +5 -0
  6. package/cookies_old.txt +5 -0
  7. package/cookies_old_test.txt +6 -0
  8. package/cookies_super.txt +5 -0
  9. package/cookies_super_test.txt +6 -0
  10. package/cookies_test.txt +6 -0
  11. package/index.js +9 -0
  12. package/manage.js +745 -0
  13. package/package.json +6 -2
  14. package/plugins/core-waiting-list-migration/README.md +118 -0
  15. package/plugins/core-waiting-list-migration/index.js +438 -0
  16. package/plugins/global-settings-presets/index.js +20 -0
  17. package/plugins/hello-cli/index.js +17 -0
  18. package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
  19. package/plugins/ui-components-seeder/components/suiToast.js +186 -0
  20. package/plugins/ui-components-seeder/index.js +31 -0
  21. package/public/js/admin-ui-components-preview.js +281 -0
  22. package/public/js/admin-ui-components.js +408 -0
  23. package/public/js/llm-provider-model-picker.js +193 -0
  24. package/public/test-iframe-fix.html +63 -0
  25. package/public/test-iframe.html +14 -0
  26. package/src/admin/endpointRegistry.js +68 -0
  27. package/src/controllers/admin.controller.js +36 -10
  28. package/src/controllers/adminAgents.controller.js +37 -0
  29. package/src/controllers/adminDataCleanup.controller.js +45 -0
  30. package/src/controllers/adminLlm.controller.js +19 -8
  31. package/src/controllers/adminLogin.controller.js +269 -0
  32. package/src/controllers/adminMarkdowns.controller.js +157 -0
  33. package/src/controllers/adminPlugins.controller.js +55 -0
  34. package/src/controllers/adminRegistry.controller.js +106 -0
  35. package/src/controllers/adminScripts.controller.js +138 -0
  36. package/src/controllers/adminStats.controller.js +4 -4
  37. package/src/controllers/adminTelegram.controller.js +72 -0
  38. package/src/controllers/markdowns.controller.js +42 -0
  39. package/src/controllers/registry.controller.js +32 -0
  40. package/src/controllers/waitingList.controller.js +52 -74
  41. package/src/helpers/mongooseHelper.js +6 -6
  42. package/src/helpers/scriptBase.js +2 -2
  43. package/src/middleware/auth.js +71 -1
  44. package/src/middleware/rbac.js +62 -0
  45. package/src/middleware.js +584 -176
  46. package/src/models/Agent.js +105 -0
  47. package/src/models/AgentMessage.js +82 -0
  48. package/src/models/GlobalSetting.js +11 -1
  49. package/src/models/Markdown.js +75 -0
  50. package/src/models/ScriptRun.js +8 -0
  51. package/src/models/TelegramBot.js +42 -0
  52. package/src/models/UiComponent.js +2 -0
  53. package/src/models/User.js +1 -1
  54. package/src/routes/admin.routes.js +3 -3
  55. package/src/routes/adminAgents.routes.js +13 -0
  56. package/src/routes/adminAssets.routes.js +11 -11
  57. package/src/routes/adminBlog.routes.js +2 -2
  58. package/src/routes/adminBlogAi.routes.js +2 -2
  59. package/src/routes/adminBlogAutomation.routes.js +2 -2
  60. package/src/routes/adminCache.routes.js +2 -2
  61. package/src/routes/adminConsoleManager.routes.js +2 -2
  62. package/src/routes/adminCrons.routes.js +2 -2
  63. package/src/routes/adminDataCleanup.routes.js +26 -0
  64. package/src/routes/adminDbBrowser.routes.js +2 -2
  65. package/src/routes/adminEjsVirtual.routes.js +2 -2
  66. package/src/routes/adminFeatureFlags.routes.js +6 -6
  67. package/src/routes/adminHeadless.routes.js +2 -2
  68. package/src/routes/adminHealthChecks.routes.js +2 -2
  69. package/src/routes/adminI18n.routes.js +2 -2
  70. package/src/routes/adminJsonConfigs.routes.js +8 -8
  71. package/src/routes/adminLlm.routes.js +8 -7
  72. package/src/routes/adminLogin.routes.js +23 -0
  73. package/src/routes/adminMarkdowns.routes.js +10 -0
  74. package/src/routes/adminMigration.routes.js +12 -12
  75. package/src/routes/adminPages.routes.js +2 -2
  76. package/src/routes/adminPlugins.routes.js +15 -0
  77. package/src/routes/adminProxy.routes.js +2 -2
  78. package/src/routes/adminRateLimits.routes.js +8 -8
  79. package/src/routes/adminRbac.routes.js +2 -2
  80. package/src/routes/adminRegistry.routes.js +24 -0
  81. package/src/routes/adminScripts.routes.js +6 -3
  82. package/src/routes/adminSeoConfig.routes.js +10 -10
  83. package/src/routes/adminTelegram.routes.js +14 -0
  84. package/src/routes/adminTerminals.routes.js +2 -2
  85. package/src/routes/adminUiComponents.routes.js +2 -2
  86. package/src/routes/adminUploadNamespaces.routes.js +7 -7
  87. package/src/routes/blogInternal.routes.js +2 -2
  88. package/src/routes/experiments.routes.js +2 -2
  89. package/src/routes/formsAdmin.routes.js +6 -6
  90. package/src/routes/globalSettings.routes.js +8 -8
  91. package/src/routes/internalExperiments.routes.js +2 -2
  92. package/src/routes/markdowns.routes.js +16 -0
  93. package/src/routes/notificationAdmin.routes.js +7 -7
  94. package/src/routes/orgAdmin.routes.js +16 -16
  95. package/src/routes/pages.routes.js +3 -3
  96. package/src/routes/registry.routes.js +11 -0
  97. package/src/routes/stripeAdmin.routes.js +12 -12
  98. package/src/routes/userAdmin.routes.js +7 -7
  99. package/src/routes/waitingListAdmin.routes.js +2 -2
  100. package/src/routes/workflows.routes.js +3 -3
  101. package/src/services/agent.service.js +546 -0
  102. package/src/services/agentHistory.service.js +345 -0
  103. package/src/services/agentTools.service.js +578 -0
  104. package/src/services/dataCleanup.service.js +286 -0
  105. package/src/services/jsonConfigs.service.js +284 -10
  106. package/src/services/llm.service.js +219 -6
  107. package/src/services/markdowns.service.js +522 -0
  108. package/src/services/plugins.service.js +348 -0
  109. package/src/services/registry.service.js +452 -0
  110. package/src/services/scriptsRunner.service.js +328 -37
  111. package/src/services/telegram.service.js +130 -0
  112. package/src/services/uiComponents.service.js +180 -0
  113. package/src/services/waitingListJson.service.js +401 -0
  114. package/src/utils/rbac/rightsRegistry.js +118 -0
  115. package/test-access.js +63 -0
  116. package/test-iframe-fix.html +63 -0
  117. package/test-iframe.html +14 -0
  118. package/views/admin-403.ejs +92 -0
  119. package/views/admin-agents.ejs +273 -0
  120. package/views/admin-coolify-deploy.ejs +8 -8
  121. package/views/admin-dashboard-home.ejs +52 -2
  122. package/views/admin-dashboard.ejs +179 -7
  123. package/views/admin-data-cleanup.ejs +357 -0
  124. package/views/admin-experiments.ejs +1 -1
  125. package/views/admin-login.ejs +286 -0
  126. package/views/admin-markdowns.ejs +905 -0
  127. package/views/admin-plugins-system.ejs +223 -0
  128. package/views/admin-scripts.ejs +221 -4
  129. package/views/admin-telegram.ejs +269 -0
  130. package/views/admin-ui-components.ejs +82 -402
  131. package/views/admin-users.ejs +207 -11
  132. package/views/partials/dashboard/nav-items.ejs +5 -0
  133. package/views/partials/llm-provider-model-picker.ejs +0 -161
  134. 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.sh");
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
- // In ref-superbackend, manage.sh already exists in the root of the repository
367
- // If it didn't, we would write it here. For this case, we'll just success.
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.sh) was already there."
372
- : "Coolify Headless Deploy script (manage.sh) is ready in the root directory.",
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
+ };