@a83/orbiter-admin 0.3.20 → 0.3.21

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": "@a83/orbiter-admin",
3
- "version": "0.3.20",
3
+ "version": "0.3.21",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
@@ -0,0 +1,37 @@
1
+ /**
2
+ * CSRF protection via Origin / Referer header validation.
3
+ *
4
+ * sameSite: 'Strict' on the session cookie already blocks most CSRF vectors.
5
+ * This middleware adds a second layer: mutating requests (POST/PUT/PATCH/DELETE)
6
+ * must carry an Origin (or Referer) header that matches ALLOWED_ORIGINS.
7
+ *
8
+ * Browser fetch/XHR always sends Origin on cross-origin requests; same-origin
9
+ * requests send it on POST but not on GET. We only check mutating methods, so
10
+ * the case where Origin is absent on a same-site POST is fine — a same-site
11
+ * request is already allowed.
12
+ *
13
+ * Requests with no Origin AND no Referer (e.g. curl without headers, Postman)
14
+ * are allowed — they can't carry the session cookie cross-site anyway.
15
+ */
16
+
17
+ const MUTATING = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
18
+
19
+ export function csrfMiddleware(allowedOrigins) {
20
+ return async (c, next) => {
21
+ if (!MUTATING.has(c.req.method)) return next();
22
+
23
+ const origin = c.req.header('origin');
24
+ const referer = c.req.header('referer');
25
+
26
+ // No origin/referer — non-browser client; skip check
27
+ if (!origin && !referer) return next();
28
+
29
+ const candidate = origin ?? new URL(referer).origin;
30
+
31
+ if (!allowedOrigins.includes(candidate)) {
32
+ return c.json({ error: 'CSRF check failed' }, 403);
33
+ }
34
+
35
+ return next();
36
+ };
37
+ }
package/src/server.js CHANGED
@@ -25,6 +25,7 @@ import { importRoutes } from './routes/import.js';
25
25
  import { commentRoutes } from './routes/comments.js';
26
26
  import { lockRoutes } from './routes/locks.js';
27
27
  import { requireAuth } from './middleware/auth.js';
28
+ import { csrfMiddleware } from './middleware/csrf.js';
28
29
 
29
30
  const { version: adminVersion } = JSON.parse(
30
31
  readFileSync(join(__dirname, '../package.json'), 'utf8')
@@ -56,6 +57,8 @@ export function createApp(podPath) {
56
57
  credentials: true,
57
58
  }));
58
59
 
60
+ app.use('/api/*', csrfMiddleware(ALLOWED_ORIGINS));
61
+
59
62
  // Public routes
60
63
  app.route('/api/auth', authRoutes);
61
64