@intranefr/superbackend 1.6.4 → 1.6.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intranefr/superbackend",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "Node.js middleware that gives your project backend superpowers",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -131,9 +131,37 @@ const adminSessionAuth = (req, res, next) => {
131
131
 
132
132
  // Admin authentication middleware that supports both session and basic auth
133
133
  const adminAuth = (req, res, next) => {
134
- // First try session authentication
135
- if (req.session && req.session.authenticated) {
136
- return adminSessionAuth(req, res, next);
134
+ // First try session authentication (only if session exists AND is authenticated)
135
+ if (req.session && req.session.authenticated === true) {
136
+ // Verify session is still valid (check login time)
137
+ const loginTime = new Date(req.session.loginTime);
138
+ const now = new Date();
139
+ const sessionAge = (now - loginTime) / (1000 * 60 * 60); // hours
140
+
141
+ // Session expires after 24 hours
142
+ if (sessionAge > 24) {
143
+ req.session.destroy((err) => {
144
+ if (err) console.error('Error destroying expired session:', err);
145
+ });
146
+
147
+ if (req.xhr || req.headers.accept?.includes('application/json')) {
148
+ return res.status(401).json({
149
+ error: "Session expired",
150
+ redirectTo: `${req.adminPath || '/admin'}/login`
151
+ });
152
+ }
153
+
154
+ return res.redirect(`${req.adminPath || '/admin'}/login?error=Session expired`);
155
+ }
156
+
157
+ // Attach user info to request for consistency with other auth middleware
158
+ req.user = {
159
+ authenticated: true,
160
+ authType: req.session.authType,
161
+ role: req.session.role
162
+ };
163
+
164
+ return next();
137
165
  }
138
166
 
139
167
  // Fallback to basic auth for backward compatibility
package/src/middleware.js CHANGED
@@ -99,6 +99,12 @@ function createMiddleware(options = {}) {
99
99
 
100
100
  const bootstrapPluginsRuntime = async () => {
101
101
  try {
102
+ if (options.plugins?.extraRoots) {
103
+ const roots = Array.isArray(options.plugins.extraRoots) ? options.plugins.extraRoots : [];
104
+ for (const root of roots) {
105
+ await pluginsService.loadAllPluginsFromFolder(root, { context: {} });
106
+ }
107
+ }
102
108
  const superbackend = globalThis.superbackend || globalThis.saasbackend || {};
103
109
  await pluginsService.bootstrap({
104
110
  context: {
@@ -1,13 +1,13 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
3
 
4
- const { adminSessionAuth } = require('../middleware/auth');
4
+ const { adminAuth } = require('../middleware/auth');
5
5
  const rateLimiter = require('../services/rateLimiter.service');
6
6
 
7
7
  const controller = require('../controllers/experiments.controller');
8
8
 
9
9
  router.use(express.json({ limit: '1mb' }));
10
- router.use(adminSessionAuth);
10
+ router.use(adminAuth);
11
11
 
12
12
  router.get(
13
13
  '/:code/assignment',
@@ -249,6 +249,7 @@ async function logAuditEntry({
249
249
  model,
250
250
  variables: variables || {},
251
251
  requestOptions: requestOptions || {},
252
+ auditContext: requestOptions?.auditContext || null,
252
253
  errorMessage,
253
254
  usage: usage || null,
254
255
  },
@@ -12,6 +12,8 @@ const DEFAULT_REGISTRY_ID = 'plugins';
12
12
  const exposedServices = {};
13
13
  const exposedHelpers = {};
14
14
 
15
+ const additionalPluginsRoots = new Set();
16
+
15
17
  function sha256(value) {
16
18
  return crypto.createHash('sha256').update(String(value || ''), 'utf8').digest('hex');
17
19
  }
@@ -24,6 +26,24 @@ function resolvePluginsRoot(customRoot) {
24
26
  return customRoot || path.join(process.cwd(), 'plugins');
25
27
  }
26
28
 
29
+ function listPluginsRoots({ pluginsRoot } = {}) {
30
+ const roots = [];
31
+ const primary = resolvePluginsRoot(pluginsRoot);
32
+ if (primary) roots.push(primary);
33
+ for (const extra of additionalPluginsRoots) {
34
+ if (extra && typeof extra === 'string') roots.push(extra);
35
+ }
36
+ // de-dup while preserving order
37
+ return Array.from(new Set(roots));
38
+ }
39
+
40
+ function registerPluginsRoot(absolutePath) {
41
+ const root = String(absolutePath || '').trim();
42
+ if (!root) return { ok: false, reason: 'empty_path' };
43
+ additionalPluginsRoots.add(root);
44
+ return { ok: true, root };
45
+ }
46
+
27
47
  function normalizePlugin(rawModule, pluginId, absoluteDir) {
28
48
  const candidate = rawModule && typeof rawModule === 'object' ? rawModule : {};
29
49
  const hooks = candidate.hooks && typeof candidate.hooks === 'object' ? candidate.hooks : {};
@@ -131,28 +151,40 @@ function readPluginModule(pluginDir) {
131
151
  }
132
152
 
133
153
  async function discoverPlugins({ pluginsRoot } = {}) {
134
- const root = resolvePluginsRoot(pluginsRoot);
135
- if (!fs.existsSync(root)) return [];
136
-
137
- const entries = fs.readdirSync(root, { withFileTypes: true });
138
- const plugins = [];
139
-
140
- for (const entry of entries) {
141
- if (!entry.isDirectory()) continue;
142
- const pluginId = entry.name;
143
- const absoluteDir = path.join(root, pluginId);
144
- const loaded = readPluginModule(absoluteDir);
145
- if (!loaded) continue;
146
-
147
- const plugin = normalizePlugin(loaded, pluginId, absoluteDir);
148
- if (!plugin.id) continue;
149
- plugins.push(plugin);
154
+ const roots = listPluginsRoots({ pluginsRoot });
155
+ const pluginById = new Map();
156
+
157
+ for (const root of roots) {
158
+ if (!fs.existsSync(root)) continue;
159
+ const entries = fs.readdirSync(root, { withFileTypes: true });
160
+
161
+ for (const entry of entries) {
162
+ if (!entry.isDirectory()) continue;
163
+ const pluginId = entry.name;
164
+ const absoluteDir = path.join(root, pluginId);
165
+ const loaded = readPluginModule(absoluteDir);
166
+ if (!loaded) continue;
167
+
168
+ const plugin = normalizePlugin(loaded, pluginId, absoluteDir);
169
+ if (!plugin.id) continue;
170
+
171
+ // First discovered wins (stable and avoids unexpected overrides)
172
+ if (!pluginById.has(plugin.id)) {
173
+ pluginById.set(plugin.id, plugin);
174
+ }
175
+ }
150
176
  }
151
177
 
178
+ const plugins = Array.from(pluginById.values());
152
179
  plugins.sort((a, b) => a.id.localeCompare(b.id));
153
180
  return plugins;
154
181
  }
155
182
 
183
+ async function loadAllPluginsFromFolder(absolutePath, { context } = {}) {
184
+ registerPluginsRoot(absolutePath);
185
+ return bootstrap({ context });
186
+ }
187
+
156
188
  async function ensurePluginsRegistry() {
157
189
  return registryService.ensureRegistry({
158
190
  id: DEFAULT_REGISTRY_ID,
@@ -337,6 +369,8 @@ module.exports = {
337
369
  PLUGINS_STATE_KEY,
338
370
  DEFAULT_REGISTRY_ID,
339
371
  ensurePluginsRegistry,
372
+ registerPluginsRoot,
373
+ loadAllPluginsFromFolder,
340
374
  discoverPlugins,
341
375
  bootstrap,
342
376
  listPlugins,