@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 +1 -1
- package/src/middleware/csrf.js +37 -0
- package/src/server.js +3 -0
package/package.json
CHANGED
|
@@ -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
|
|