@intranefr/superbackend 1.5.3 → 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 (106) 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 +7 -0
  12. package/package.json +3 -1
  13. package/plugins/core-waiting-list-migration/README.md +118 -0
  14. package/plugins/core-waiting-list-migration/index.js +438 -0
  15. package/plugins/global-settings-presets/index.js +20 -0
  16. package/plugins/hello-cli/index.js +17 -0
  17. package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
  18. package/plugins/ui-components-seeder/components/suiToast.js +186 -0
  19. package/plugins/ui-components-seeder/index.js +31 -0
  20. package/public/js/admin-ui-components-preview.js +281 -0
  21. package/public/js/admin-ui-components.js +408 -0
  22. package/public/js/llm-provider-model-picker.js +193 -0
  23. package/public/test-iframe-fix.html +63 -0
  24. package/public/test-iframe.html +14 -0
  25. package/src/admin/endpointRegistry.js +68 -0
  26. package/src/controllers/admin.controller.js +25 -5
  27. package/src/controllers/adminDataCleanup.controller.js +45 -0
  28. package/src/controllers/adminLlm.controller.js +0 -8
  29. package/src/controllers/adminLogin.controller.js +269 -0
  30. package/src/controllers/adminPlugins.controller.js +55 -0
  31. package/src/controllers/adminRegistry.controller.js +106 -0
  32. package/src/controllers/adminStats.controller.js +4 -4
  33. package/src/controllers/registry.controller.js +32 -0
  34. package/src/controllers/waitingList.controller.js +52 -74
  35. package/src/middleware/auth.js +71 -1
  36. package/src/middleware/rbac.js +62 -0
  37. package/src/middleware.js +454 -153
  38. package/src/models/GlobalSetting.js +11 -1
  39. package/src/models/UiComponent.js +2 -0
  40. package/src/models/User.js +1 -1
  41. package/src/routes/admin.routes.js +3 -3
  42. package/src/routes/adminAgents.routes.js +2 -2
  43. package/src/routes/adminAssets.routes.js +11 -11
  44. package/src/routes/adminBlog.routes.js +2 -2
  45. package/src/routes/adminBlogAi.routes.js +2 -2
  46. package/src/routes/adminBlogAutomation.routes.js +2 -2
  47. package/src/routes/adminCache.routes.js +2 -2
  48. package/src/routes/adminConsoleManager.routes.js +2 -2
  49. package/src/routes/adminCrons.routes.js +2 -2
  50. package/src/routes/adminDataCleanup.routes.js +26 -0
  51. package/src/routes/adminDbBrowser.routes.js +2 -2
  52. package/src/routes/adminEjsVirtual.routes.js +2 -2
  53. package/src/routes/adminFeatureFlags.routes.js +6 -6
  54. package/src/routes/adminHeadless.routes.js +2 -2
  55. package/src/routes/adminHealthChecks.routes.js +2 -2
  56. package/src/routes/adminI18n.routes.js +2 -2
  57. package/src/routes/adminJsonConfigs.routes.js +8 -8
  58. package/src/routes/adminLlm.routes.js +8 -8
  59. package/src/routes/adminLogin.routes.js +23 -0
  60. package/src/routes/adminMarkdowns.routes.js +3 -9
  61. package/src/routes/adminMigration.routes.js +12 -12
  62. package/src/routes/adminPages.routes.js +2 -2
  63. package/src/routes/adminPlugins.routes.js +15 -0
  64. package/src/routes/adminProxy.routes.js +2 -2
  65. package/src/routes/adminRateLimits.routes.js +8 -8
  66. package/src/routes/adminRbac.routes.js +2 -2
  67. package/src/routes/adminRegistry.routes.js +24 -0
  68. package/src/routes/adminScripts.routes.js +2 -2
  69. package/src/routes/adminSeoConfig.routes.js +10 -10
  70. package/src/routes/adminTelegram.routes.js +2 -2
  71. package/src/routes/adminTerminals.routes.js +2 -2
  72. package/src/routes/adminUiComponents.routes.js +2 -2
  73. package/src/routes/adminUploadNamespaces.routes.js +7 -7
  74. package/src/routes/blogInternal.routes.js +2 -2
  75. package/src/routes/experiments.routes.js +2 -2
  76. package/src/routes/formsAdmin.routes.js +6 -6
  77. package/src/routes/globalSettings.routes.js +8 -8
  78. package/src/routes/internalExperiments.routes.js +2 -2
  79. package/src/routes/notificationAdmin.routes.js +7 -7
  80. package/src/routes/orgAdmin.routes.js +16 -16
  81. package/src/routes/pages.routes.js +3 -3
  82. package/src/routes/registry.routes.js +11 -0
  83. package/src/routes/stripeAdmin.routes.js +12 -12
  84. package/src/routes/userAdmin.routes.js +7 -7
  85. package/src/routes/waitingListAdmin.routes.js +2 -2
  86. package/src/routes/workflows.routes.js +3 -3
  87. package/src/services/dataCleanup.service.js +286 -0
  88. package/src/services/jsonConfigs.service.js +262 -0
  89. package/src/services/plugins.service.js +348 -0
  90. package/src/services/registry.service.js +452 -0
  91. package/src/services/uiComponents.service.js +180 -0
  92. package/src/services/waitingListJson.service.js +401 -0
  93. package/src/utils/rbac/rightsRegistry.js +118 -0
  94. package/test-access.js +63 -0
  95. package/test-iframe-fix.html +63 -0
  96. package/test-iframe.html +14 -0
  97. package/views/admin-403.ejs +92 -0
  98. package/views/admin-dashboard-home.ejs +52 -2
  99. package/views/admin-dashboard.ejs +143 -2
  100. package/views/admin-data-cleanup.ejs +357 -0
  101. package/views/admin-login.ejs +286 -0
  102. package/views/admin-plugins-system.ejs +223 -0
  103. package/views/admin-ui-components.ejs +82 -402
  104. package/views/admin-users.ejs +207 -11
  105. package/views/partials/dashboard/nav-items.ejs +2 -0
  106. package/views/partials/llm-provider-model-picker.ejs +0 -161
@@ -0,0 +1,401 @@
1
+ const crypto = require('crypto');
2
+ const {
3
+ getJsonConfigValueBySlug,
4
+ updateJsonConfigValueBySlug,
5
+ clearJsonConfigCacheByPattern,
6
+ isJsonConfigCached,
7
+ getJsonConfigCacheInfo
8
+ } = require('./jsonConfigs.service');
9
+
10
+ const WAITING_LIST_ENTRIES_KEY = 'waiting-list-entries';
11
+ const WAITING_LIST_STATS_KEY = 'waiting-list-stats';
12
+
13
+ /**
14
+ * Waiting List JSON Service
15
+ * Manages waiting list data using JSON Configs system with enhanced caching
16
+ */
17
+
18
+ function generateId() {
19
+ return crypto.randomBytes(16).toString('hex');
20
+ }
21
+
22
+ function normalizeEmail(email) {
23
+ return String(email || '').trim().toLowerCase();
24
+ }
25
+
26
+ function validateEntry(entry) {
27
+ if (!entry || typeof entry !== 'object') {
28
+ const err = new Error('Entry must be an object');
29
+ err.code = 'VALIDATION';
30
+ throw err;
31
+ }
32
+
33
+ if (!entry.email || typeof entry.email !== 'string') {
34
+ const err = new Error('Email is required');
35
+ err.code = 'VALIDATION';
36
+ throw err;
37
+ }
38
+
39
+ const normalizedEmail = normalizeEmail(entry.email);
40
+ if (!normalizedEmail.includes('@')) {
41
+ const err = new Error('Invalid email format');
42
+ err.code = 'VALIDATION';
43
+ throw err;
44
+ }
45
+
46
+ if (!entry.type || typeof entry.type !== 'string') {
47
+ const err = new Error('Type is required');
48
+ err.code = 'VALIDATION';
49
+ throw err;
50
+ }
51
+
52
+ return {
53
+ id: entry.id || generateId(),
54
+ email: normalizedEmail,
55
+ type: String(entry.type).trim(),
56
+ status: entry.status || 'active',
57
+ referralSource: entry.referralSource || 'website',
58
+ createdAt: entry.createdAt || new Date().toISOString(),
59
+ updatedAt: entry.updatedAt || new Date().toISOString()
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Get waiting list entries with caching
65
+ */
66
+ async function getWaitingListEntries(options = {}) {
67
+ try {
68
+ const data = await getJsonConfigValueBySlug(WAITING_LIST_ENTRIES_KEY, {
69
+ bypassCache: options.bypassCache
70
+ });
71
+
72
+ return {
73
+ entries: Array.isArray(data.entries) ? data.entries : [],
74
+ lastUpdated: data.lastUpdated || null
75
+ };
76
+ } catch (error) {
77
+ if (error.code === 'NOT_FOUND') {
78
+ return { entries: [], lastUpdated: null };
79
+ }
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Add new waiting list entry with automatic cache invalidation
86
+ */
87
+ async function addWaitingListEntry(entryData) {
88
+ const validatedEntry = validateEntry(entryData);
89
+
90
+ try {
91
+ const result = await updateJsonConfigValueBySlug(WAITING_LIST_ENTRIES_KEY, (currentData) => {
92
+ const data = currentData || { entries: [] };
93
+ const entries = Array.isArray(data.entries) ? data.entries : [];
94
+
95
+ // Check for duplicate email
96
+ const existingEntry = entries.find(e =>
97
+ normalizeEmail(e.email) === normalizeEmail(validatedEntry.email)
98
+ );
99
+
100
+ if (existingEntry) {
101
+ const err = new Error('This email is already on our waiting list');
102
+ err.code = 'DUPLICATE_EMAIL';
103
+ throw err;
104
+ }
105
+
106
+ // Add new entry
107
+ entries.push(validatedEntry);
108
+
109
+ return {
110
+ ...data,
111
+ entries,
112
+ lastUpdated: new Date().toISOString()
113
+ };
114
+ }, { invalidateCache: true });
115
+
116
+ // Clear stats cache since data changed
117
+ clearWaitingListCache();
118
+
119
+ return validatedEntry;
120
+ } catch (error) {
121
+ // If the config doesn't exist, initialize it first
122
+ if (error.code === 'NOT_FOUND') {
123
+ await initializeWaitingListData();
124
+
125
+ // Retry the operation after initialization (only once)
126
+ try {
127
+ return await addWaitingListEntry(entryData);
128
+ } catch (retryError) {
129
+ // If we still get NOT_FOUND, something went wrong with initialization
130
+ if (retryError.code === 'NOT_FOUND') {
131
+ const err = new Error('Failed to initialize waiting list data structure');
132
+ err.code = 'INITIALIZATION_FAILED';
133
+ throw err;
134
+ }
135
+ throw retryError;
136
+ }
137
+ }
138
+ throw error;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Update waiting list entry
144
+ */
145
+ async function updateWaitingListEntry(entryId, updates) {
146
+ if (!entryId) {
147
+ const err = new Error('Entry ID is required');
148
+ err.code = 'VALIDATION';
149
+ throw err;
150
+ }
151
+
152
+ const result = await updateJsonConfigValueBySlug(WAITING_LIST_ENTRIES_KEY, (currentData) => {
153
+ const data = currentData || { entries: [] };
154
+ const entries = Array.isArray(data.entries) ? data.entries : [];
155
+
156
+ const entryIndex = entries.findIndex(e => e.id === entryId);
157
+ if (entryIndex === -1) {
158
+ const err = new Error('Waiting list entry not found');
159
+ err.code = 'NOT_FOUND';
160
+ throw err;
161
+ }
162
+
163
+ // Update entry with validation
164
+ const updatedEntry = validateEntry({
165
+ ...entries[entryIndex],
166
+ ...updates,
167
+ id: entryId, // Preserve original ID
168
+ updatedAt: new Date().toISOString()
169
+ });
170
+
171
+ entries[entryIndex] = updatedEntry;
172
+
173
+ return {
174
+ ...data,
175
+ entries,
176
+ lastUpdated: new Date().toISOString()
177
+ };
178
+ }, { invalidateCache: true });
179
+
180
+ // Clear stats cache since data changed
181
+ clearWaitingListCache();
182
+
183
+ return result;
184
+ }
185
+
186
+ /**
187
+ * Remove waiting list entry
188
+ */
189
+ async function removeWaitingListEntry(entryId) {
190
+ if (!entryId) {
191
+ const err = new Error('Entry ID is required');
192
+ err.code = 'VALIDATION';
193
+ throw err;
194
+ }
195
+
196
+ const result = await updateJsonConfigValueBySlug(WAITING_LIST_ENTRIES_KEY, (currentData) => {
197
+ const data = currentData || { entries: [] };
198
+ const entries = Array.isArray(data.entries) ? data.entries : [];
199
+
200
+ const entryIndex = entries.findIndex(e => e.id === entryId);
201
+ if (entryIndex === -1) {
202
+ const err = new Error('Waiting list entry not found');
203
+ err.code = 'NOT_FOUND';
204
+ throw err;
205
+ }
206
+
207
+ // Remove entry
208
+ entries.splice(entryIndex, 1);
209
+
210
+ return {
211
+ ...data,
212
+ entries,
213
+ lastUpdated: new Date().toISOString()
214
+ };
215
+ }, { invalidateCache: true });
216
+
217
+ // Clear stats cache since data changed
218
+ clearWaitingListCache();
219
+
220
+ return result;
221
+ }
222
+
223
+ /**
224
+ * Get waiting list statistics with 5-minute TTL
225
+ */
226
+ async function getWaitingListStats(options = {}) {
227
+ const ttlSeconds = options.ttlSeconds || 300; // 5 minutes default
228
+
229
+ try {
230
+ // Try to get cached stats first
231
+ if (!options.bypassCache && isJsonConfigCached(WAITING_LIST_STATS_KEY)) {
232
+ const cacheInfo = getJsonConfigCacheInfo(WAITING_LIST_STATS_KEY);
233
+ if (cacheInfo.exists && cacheInfo.ttlRemaining > 0) {
234
+ return await getJsonConfigValueBySlug(WAITING_LIST_STATS_KEY);
235
+ }
236
+ }
237
+
238
+ // Generate fresh stats
239
+ const { entries } = await getWaitingListEntries();
240
+
241
+ const activeEntries = entries.filter(e => e.status === 'active');
242
+ const totalSubscribers = activeEntries.length;
243
+
244
+ // Type aggregation
245
+ const typeAgg = activeEntries.reduce((acc, entry) => {
246
+ const type = String(entry.type || 'unknown').trim();
247
+ acc[type] = (acc[type] || 0) + 1;
248
+ return acc;
249
+ }, {});
250
+
251
+ // Backward compatibility fields
252
+ const buyerCount = (typeAgg.buyer || 0) + (typeAgg.both || 0);
253
+ const sellerCount = (typeAgg.seller || 0) + (typeAgg.both || 0);
254
+
255
+ // Mock growth data (same as original)
256
+ const growthThisWeek = Math.floor(totalSubscribers * 0.05);
257
+
258
+ const stats = {
259
+ totalSubscribers,
260
+ buyerCount,
261
+ sellerCount,
262
+ typeCounts: typeAgg,
263
+ growthThisWeek,
264
+ lastUpdated: new Date().toISOString()
265
+ };
266
+
267
+ // Cache the stats
268
+ await updateJsonConfigValueBySlug(WAITING_LIST_STATS_KEY, () => stats, {
269
+ invalidateCache: false
270
+ });
271
+
272
+ return stats;
273
+ } catch (error) {
274
+ if (error.code === 'NOT_FOUND') {
275
+ // Return default stats if no data exists
276
+ return {
277
+ totalSubscribers: 0,
278
+ buyerCount: 0,
279
+ sellerCount: 0,
280
+ typeCounts: {},
281
+ growthThisWeek: 0,
282
+ lastUpdated: new Date().toISOString()
283
+ };
284
+ }
285
+ throw error;
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Get paginated waiting list entries for admin
291
+ */
292
+ async function getWaitingListEntriesAdmin(filters = {}) {
293
+ const { status, type, email, limit = 50, offset = 0 } = filters;
294
+
295
+ const { entries } = await getWaitingListEntries();
296
+
297
+ // Apply filters
298
+ let filteredEntries = entries;
299
+
300
+ if (status) {
301
+ filteredEntries = filteredEntries.filter(e => e.status === status);
302
+ }
303
+
304
+ if (type) {
305
+ filteredEntries = filteredEntries.filter(e => e.type === type);
306
+ }
307
+
308
+ if (email) {
309
+ const searchEmail = normalizeEmail(email);
310
+ filteredEntries = filteredEntries.filter(e =>
311
+ normalizeEmail(e.email) === searchEmail
312
+ );
313
+ }
314
+
315
+ // Sort by creation date (newest first)
316
+ filteredEntries.sort((a, b) =>
317
+ new Date(b.createdAt || 0).getTime() - new Date(a.createdAt || 0).getTime()
318
+ );
319
+
320
+ // Apply pagination
321
+ const parsedLimit = Math.min(500, Math.max(1, parseInt(limit, 10) || 50));
322
+ const parsedOffset = Math.max(0, parseInt(offset, 10) || 0);
323
+
324
+ const paginatedEntries = filteredEntries.slice(parsedOffset, parsedOffset + parsedLimit);
325
+
326
+ return {
327
+ entries: paginatedEntries,
328
+ pagination: {
329
+ total: filteredEntries.length,
330
+ limit: parsedLimit,
331
+ offset: parsedOffset
332
+ }
333
+ };
334
+ }
335
+
336
+ /**
337
+ * Clear all waiting list related caches
338
+ */
339
+ function clearWaitingListCache() {
340
+ return clearJsonConfigCacheByPattern('waiting-list-*');
341
+ }
342
+
343
+ /**
344
+ * Get waiting list cache information
345
+ */
346
+ function getWaitingListCacheInfo() {
347
+ return {
348
+ entries: getJsonConfigCacheInfo(WAITING_LIST_ENTRIES_KEY),
349
+ stats: getJsonConfigCacheInfo(WAITING_LIST_STATS_KEY)
350
+ };
351
+ }
352
+
353
+ /**
354
+ * Initialize waiting list data structure if it doesn't exist
355
+ */
356
+ async function initializeWaitingListData() {
357
+ try {
358
+ await getJsonConfigValueBySlug(WAITING_LIST_ENTRIES_KEY, { bypassCache: true });
359
+ } catch (error) {
360
+ if (error.code === 'NOT_FOUND') {
361
+ // Create initial data structure
362
+ const { createJsonConfig } = require('./jsonConfigs.service');
363
+ await createJsonConfig({
364
+ title: 'Waiting List Entries',
365
+ alias: WAITING_LIST_ENTRIES_KEY,
366
+ jsonRaw: JSON.stringify({
367
+ entries: [],
368
+ lastUpdated: new Date().toISOString()
369
+ }),
370
+ publicEnabled: false,
371
+ cacheTtlSeconds: 300
372
+ });
373
+ } else {
374
+ throw error;
375
+ }
376
+ }
377
+ }
378
+
379
+ module.exports = {
380
+ // Core operations
381
+ getWaitingListEntries,
382
+ addWaitingListEntry,
383
+ updateWaitingListEntry,
384
+ removeWaitingListEntry,
385
+ getWaitingListStats,
386
+ getWaitingListEntriesAdmin,
387
+
388
+ // Cache management
389
+ clearWaitingListCache,
390
+ getWaitingListCacheInfo,
391
+
392
+ // Utilities
393
+ initializeWaitingListData,
394
+ validateEntry,
395
+ normalizeEmail,
396
+ generateId,
397
+
398
+ // Constants
399
+ WAITING_LIST_ENTRIES_KEY,
400
+ WAITING_LIST_STATS_KEY
401
+ };
@@ -1,4 +1,5 @@
1
1
  const DEFAULT_RIGHTS = [
2
+ // Existing RBAC rights
2
3
  'rbac:roles:read',
3
4
  'rbac:roles:write',
4
5
  'rbac:groups:read',
@@ -6,6 +7,8 @@ const DEFAULT_RIGHTS = [
6
7
  'rbac:grants:read',
7
8
  'rbac:grants:write',
8
9
  'rbac:test',
10
+
11
+ // Existing experiment and file manager rights
9
12
  'experiments:*',
10
13
  'experiments:read',
11
14
  'experiments:events:write',
@@ -21,6 +24,121 @@ const DEFAULT_RIGHTS = [
21
24
  'file_manager:files:share',
22
25
  'backoffice:*',
23
26
  'backoffice:dashboard:access',
27
+
28
+ // Admin panel login rights
29
+ 'admin_panel__login',
30
+ 'admin_panel__dashboard',
31
+
32
+ // Dashboard section
33
+ 'admin_panel__overview:read',
34
+
35
+ // User Management section
36
+ 'admin_panel__users:read',
37
+ 'admin_panel__users:write',
38
+ 'admin_panel__organizations:read',
39
+ 'admin_panel__organizations:write',
40
+ 'admin_panel__rbac:read',
41
+ 'admin_panel__rbac:write',
42
+ 'admin_panel__notifications:read',
43
+ 'admin_panel__notifications:write',
44
+ 'admin_panel__waiting-list:read',
45
+ 'admin_panel__waiting-list:write',
46
+
47
+ // Content & Config section
48
+ 'admin_panel__i18n:read',
49
+ 'admin_panel__i18n:write',
50
+ 'admin_panel__i18n-locales:read',
51
+ 'admin_panel__i18n-locales:write',
52
+ 'admin_panel__json-configs:read',
53
+ 'admin_panel__json-configs:write',
54
+ 'admin_panel__markdowns:read',
55
+ 'admin_panel__markdowns:write',
56
+ 'admin_panel__seo-config:read',
57
+ 'admin_panel__seo-config:write',
58
+ 'admin_panel__assets:read',
59
+ 'admin_panel__assets:write',
60
+ 'admin_panel__file-manager:read',
61
+ 'admin_panel__file-manager:write',
62
+ 'admin_panel__ui-components:read',
63
+ 'admin_panel__ui-components:write',
64
+ 'admin_panel__headless:read',
65
+ 'admin_panel__headless:write',
66
+ 'admin_panel__pages:read',
67
+ 'admin_panel__pages:write',
68
+ 'admin_panel__blog:read',
69
+ 'admin_panel__blog:write',
70
+
71
+ // System & DevOps section
72
+ 'admin_panel__global-settings:read',
73
+ 'admin_panel__global-settings:write',
74
+ 'admin_panel__plugins-system:read',
75
+ 'admin_panel__plugins-system:write',
76
+ 'admin_panel__feature-flags:read',
77
+ 'admin_panel__feature-flags:write',
78
+ 'admin_panel__ejs-virtual:read',
79
+ 'admin_panel__ejs-virtual:write',
80
+ 'admin_panel__rate-limiter:read',
81
+ 'admin_panel__rate-limiter:write',
82
+ 'admin_panel__proxy:read',
83
+ 'admin_panel__proxy:write',
84
+ 'admin_panel__cache:read',
85
+ 'admin_panel__cache:write',
86
+ 'admin_panel__db-browser:read',
87
+ 'admin_panel__db-browser:write',
88
+ 'admin_panel__data-cleanup:read',
89
+ 'admin_panel__data-cleanup:write',
90
+ 'admin_panel__migration:read',
91
+ 'admin_panel__migration:write',
92
+ 'admin_panel__webhooks:read',
93
+ 'admin_panel__webhooks:write',
94
+ 'admin_panel__coolify-deploy:read',
95
+ 'admin_panel__coolify-deploy:write',
96
+
97
+ // Monitoring & AI section
98
+ 'admin_panel__audit:read',
99
+ 'admin_panel__audit:write',
100
+ 'admin_panel__errors:read',
101
+ 'admin_panel__errors:write',
102
+ 'admin_panel__experiments:read',
103
+ 'admin_panel__experiments:write',
104
+ 'admin_panel__console-manager:read',
105
+ 'admin_panel__console-manager:write',
106
+ 'admin_panel__health-checks:read',
107
+ 'admin_panel__health-checks:write',
108
+ 'admin_panel__metrics:read',
109
+ 'admin_panel__metrics:write',
110
+ 'admin_panel__llm:read',
111
+ 'admin_panel__llm:write',
112
+
113
+ // Billing & Forms section
114
+ 'admin_panel__stripe-pricing:read',
115
+ 'admin_panel__stripe-pricing:write',
116
+ 'admin_panel__forms:read',
117
+ 'admin_panel__forms:write',
118
+
119
+ // Automation section
120
+ 'admin_panel__agents:read',
121
+ 'admin_panel__agents:write',
122
+ 'admin_panel__telegram:read',
123
+ 'admin_panel__telegram:write',
124
+ 'admin_panel__workflows:read',
125
+ 'admin_panel__workflows:write',
126
+ 'admin_panel__scripts:read',
127
+ 'admin_panel__scripts:write',
128
+ 'admin_panel__crons:read',
129
+ 'admin_panel__crons:write',
130
+ 'admin_panel__terminals:read',
131
+ 'admin_panel__terminals:write',
132
+
133
+ // Section wildcards for easier role management
134
+ 'admin_panel__user-management:*',
135
+ 'admin_panel__content-config:*',
136
+ 'admin_panel__system-devops:*',
137
+ 'admin_panel__monitoring-ai:*',
138
+ 'admin_panel__billing-forms:*',
139
+ 'admin_panel__automation:*',
140
+
141
+ // Superuser wildcard
24
142
  '*',
25
143
  ];
26
144
 
package/test-access.js ADDED
@@ -0,0 +1,63 @@
1
+ require('dotenv').config();
2
+ const mongoose = require('mongoose');
3
+
4
+ async function testAccessControl() {
5
+ try {
6
+ await mongoose.connect(process.env.MONGODB_URI, { authSource: 'admin' });
7
+ console.log('✅ Connected to MongoDB');
8
+
9
+ // Check if roles and grants exist
10
+ const RbacRole = require('./src/models/RbacRole');
11
+ const RbacGrant = require('./src/models/RbacGrant');
12
+ const User = require('./src/models/User');
13
+
14
+ // Find the limited-admin user
15
+ const user = await User.findOne({ email: 'limitedadmin@example.com' });
16
+
17
+ if (!user) {
18
+ console.log('❌ User not found');
19
+ return;
20
+ }
21
+
22
+ console.log(`✅ Found user: ${user.email} (${user.role})`);
23
+
24
+ // Find the limited-admin role
25
+ const limitedAdminRole = await RbacRole.findOne({ key: 'limited-admin' });
26
+ if (!limitedAdminRole) {
27
+ console.log('❌ limited-admin role not found');
28
+ return;
29
+ }
30
+
31
+ console.log(`✅ Found role: ${limitedAdminRole.name}`);
32
+
33
+ // Check grants for the role
34
+ const grants = await RbacGrant.find({
35
+ subjectType: 'role',
36
+ subjectId: limitedAdminRole._id
37
+ });
38
+
39
+ console.log(`\n📋 Role grants (${grants.length}):`);
40
+ grants.forEach(grant => {
41
+ console.log(` - ${grant.right}`);
42
+ });
43
+
44
+ // Check specific permissions
45
+ const auditGrant = grants.find(g => g.right === 'admin_panel__audit:read');
46
+ const usersGrant = grants.find(g => g.right === 'admin_panel__users:read');
47
+ const errorsGrant = grants.find(g => g.right === 'admin_panel__errors:read');
48
+
49
+ console.log('\n🔍 Permission Summary:');
50
+ console.log(`Audit access: ${auditGrant ? '✅ ALLOWED' : '❌ DENIED'}`);
51
+ console.log(`Users access: ${usersGrant ? '✅ ALLOWED' : '❌ DENIED'}`);
52
+ console.log(`Errors access: ${errorsGrant ? '✅ ALLOWED' : '❌ DENIED'}`);
53
+
54
+ console.log('\n🎉 Access control test completed!');
55
+
56
+ } catch (error) {
57
+ console.error('❌ Test failed:', error);
58
+ } finally {
59
+ await mongoose.disconnect();
60
+ }
61
+ }
62
+
63
+ testAccessControl();
@@ -0,0 +1,63 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Iframe Fix Test</title>
5
+ <style>
6
+ body { font-family: Arial, sans-serif; padding: 20px; }
7
+ .test-container { margin: 20px 0; }
8
+ iframe { width: 100%; height: 400px; border: 2px solid #ccc; }
9
+ .success { color: green; font-weight: bold; }
10
+ .error { color: red; font-weight: bold; }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <h1>Iframe Authentication Fix Test</h1>
15
+
16
+ <div class="test-container">
17
+ <h2>Test 1: Iframe with Token (Should Work)</h2>
18
+ <iframe src="/admin/stats/dashboard-home?iframe_token=authenticated"></iframe>
19
+ <p id="test1-result">Loading...</p>
20
+ </div>
21
+
22
+ <div class="test-container">
23
+ <h2>Test 2: Iframe without Token (Should Redirect to Login)</h2>
24
+ <iframe src="/admin/stats/dashboard-home"></iframe>
25
+ <p id="test2-result">Loading...</p>
26
+ </div>
27
+
28
+ <script>
29
+ // Test if iframe loads correctly
30
+ setTimeout(() => {
31
+ const iframes = document.querySelectorAll('iframe');
32
+
33
+ // Test 1 - should show Command Center
34
+ iframes[0].contentDocument && iframes[0].contentDocument.body) {
35
+ const content = iframes[0].contentDocument.body.innerText;
36
+ if (content.includes('Command Center')) {
37
+ document.getElementById('test1-result').innerHTML = '<span class="success">✅ SUCCESS: Iframe with token loads correctly</span>';
38
+ } else if (content.includes('login') || content.includes('Login')) {
39
+ document.getElementById('test1-result').innerHTML = '<span class="error">❌ FAILED: Iframe with token redirected to login</span>';
40
+ } else {
41
+ document.getElementById('test1-result').innerHTML = '<span class="error">❌ UNKNOWN: Could not determine iframe content</span>';
42
+ }
43
+ } else {
44
+ document.getElementById('test1-result').innerHTML = '<span class="error">❌ FAILED: Could not access iframe content (cross-origin)</span>';
45
+ }
46
+
47
+ // Test 2 - should redirect to login
48
+ if (iframes[1].contentDocument && iframes[1].contentDocument.body) {
49
+ const content = iframes[1].contentDocument.body.innerText;
50
+ if (content.includes('login') || content.includes('Login')) {
51
+ document.getElementById('test2-result').innerHTML = '<span class="success">✅ SUCCESS: Iframe without token correctly redirects to login</span>';
52
+ } else if (content.includes('Command Center')) {
53
+ document.getElementById('test2-result').innerHTML = '<span class="error">❌ FAILED: Iframe without token loaded content (security issue)</span>';
54
+ } else {
55
+ document.getElementById('test2-result').innerHTML = '<span class="error">❌ UNKNOWN: Could not determine iframe content</span>';
56
+ }
57
+ } else {
58
+ document.getElementById('test2-result').innerHTML = '<span class="error">❌ FAILED: Could not access iframe content (cross-origin)</span>';
59
+ }
60
+ }, 3000);
61
+ </script>
62
+ </body>
63
+ </html>
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Iframe Test</title>
5
+ </head>
6
+ <body>
7
+ <h1>Iframe Test</h1>
8
+ <p>Testing iframe loading of admin dashboard content...</p>
9
+
10
+ <iframe src="/admin/stats/dashboard-home" width="100%" height="500" style="border: 1px solid #ccc;"></iframe>
11
+
12
+ <p>If you see the admin dashboard content above, iframes work. If you see a login page, there's a cookie/session issue.</p>
13
+ </body>
14
+ </html>