@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
package/src/middleware/auth.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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(
|
|
10
|
+
router.use(adminAuth);
|
|
11
11
|
|
|
12
12
|
router.get(
|
|
13
13
|
'/:code/assignment',
|
|
@@ -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
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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,
|