@nifrajs/auth 3.5.0 → 4.0.0
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/dist/csrf.d.ts +4 -4
- package/dist/csrf.d.ts.map +1 -1
- package/dist/csrf.js +17 -4
- package/dist/csrf.js.map +1 -1
- package/package.json +5 -2
- package/src/csrf.ts +16 -8
package/dist/csrf.d.ts
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* a cross-origin or same-origin *unsafe* request; it must match an allowed origin, else `403`. Safe
|
|
5
5
|
* methods (GET/HEAD/OPTIONS) pass. Apply with `app.use(csrf({ origins: ["https://example.com"] }))`.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type Middleware } from "@nifrajs/core/server";
|
|
8
8
|
export interface CsrfOptions {
|
|
9
9
|
/**
|
|
10
|
-
* Allowed origins (e.g. `["https://example.com"]`).
|
|
11
|
-
*
|
|
12
|
-
* proxy
|
|
10
|
+
* Allowed origins (e.g. `["https://example.com"]`). When omitted, the request must be same-origin by
|
|
11
|
+
* host (an `https:` page reaching an `http:` request URL is accepted, so a TLS-terminating proxy
|
|
12
|
+
* works). Set it when the proxy rewrites `Host`, so the public origin differs from the worker's.
|
|
13
13
|
*/
|
|
14
14
|
readonly origins?: readonly string[];
|
|
15
15
|
}
|
package/dist/csrf.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAQ3E,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACrC;AAKD,wBAAgB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,UAAU,CA+B1D"}
|
package/dist/csrf.js
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSRF protection via an **Origin / Referer check** on state-changing requests - OWASP's recommended
|
|
3
|
+
* defense for cookie-authenticated apps. A browser always attaches `Origin` (or at least `Referer`) to
|
|
4
|
+
* a cross-origin or same-origin *unsafe* request; it must match an allowed origin, else `403`. Safe
|
|
5
|
+
* methods (GET/HEAD/OPTIONS) pass. Apply with `app.use(csrf({ origins: ["https://example.com"] }))`.
|
|
6
|
+
*/
|
|
7
|
+
import { isSameOriginRequest } from "@nifrajs/core/server";
|
|
1
8
|
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
9
|
+
// A name per instance: `use()` skips a name it already applied, which would drop a second, stricter
|
|
10
|
+
// `csrf()` inside a `group()`.
|
|
11
|
+
let instances = 0;
|
|
2
12
|
const forbidden = () => Response.json({ ok: false, error: "csrf_failed" }, { status: 403 });
|
|
3
13
|
export function csrf(options = {}) {
|
|
4
14
|
const configured = options.origins !== undefined ? new Set(options.origins) : undefined;
|
|
15
|
+
instances += 1;
|
|
5
16
|
return {
|
|
6
|
-
name:
|
|
17
|
+
name: `csrf#${instances}`,
|
|
7
18
|
onRequest(req) {
|
|
8
19
|
if (SAFE_METHODS.has(req.method))
|
|
9
20
|
return undefined;
|
|
10
|
-
|
|
21
|
+
// The same-origin default uses core's check, which accepts an `https:` page reaching an `http:`
|
|
22
|
+
// request URL on the same host - what every TLS-terminating proxy looks like from here.
|
|
23
|
+
const allowed = (candidate) => configured !== undefined ? configured.has(candidate) : isSameOriginRequest(candidate, req);
|
|
11
24
|
const origin = req.headers.get("origin");
|
|
12
25
|
if (origin !== null)
|
|
13
|
-
return allowed
|
|
26
|
+
return allowed(origin) ? undefined : forbidden();
|
|
14
27
|
// Some same-origin requests omit `Origin` - fall back to the `Referer`'s origin.
|
|
15
28
|
const referer = req.headers.get("referer");
|
|
16
29
|
if (referer !== null) {
|
|
@@ -21,7 +34,7 @@ export function csrf(options = {}) {
|
|
|
21
34
|
catch {
|
|
22
35
|
return forbidden(); // malformed Referer
|
|
23
36
|
}
|
|
24
|
-
return allowed
|
|
37
|
+
return allowed(refererOrigin) ? undefined : forbidden();
|
|
25
38
|
}
|
|
26
39
|
// A state-changing request with neither header → reject (fail closed; a browser always sends one).
|
|
27
40
|
return forbidden();
|
package/dist/csrf.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf.js","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"csrf.js","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,mBAAmB,EAAmB,MAAM,sBAAsB,CAAA;AAE3E,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAA;AAExD,oGAAoG;AACpG,+BAA+B;AAC/B,IAAI,SAAS,GAAG,CAAC,CAAA;AAWjB,MAAM,SAAS,GAAG,GAAa,EAAE,CAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;AAErE,MAAM,UAAU,IAAI,CAAC,UAAuB,EAAE;IAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACvF,SAAS,IAAI,CAAC,CAAA;IACd,OAAO;QACL,IAAI,EAAE,QAAQ,SAAS,EAAE;QACzB,SAAS,CAAC,GAAG;YACX,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,OAAO,SAAS,CAAA;YAClD,gGAAgG;YAChG,wFAAwF;YACxF,MAAM,OAAO,GAAG,CAAC,SAAiB,EAAW,EAAE,CAC7C,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;YAE5F,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACxC,IAAI,MAAM,KAAK,IAAI;gBAAE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAA;YAErE,iFAAiF;YACjF,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YAC1C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,IAAI,aAAqB,CAAA;gBACzB,IAAI,CAAC;oBACH,aAAa,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAA;gBACzC,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,SAAS,EAAE,CAAA,CAAC,oBAAoB;gBACzC,CAAC;gBACD,OAAO,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAA;YACzD,CAAC;YAED,mGAAmG;YACnG,OAAO,SAAS,EAAE,CAAA;QACpB,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/auth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Session primitives for nifra - signed-cookie + server-store sessions, CSRF, and route guards. Bring your own identity (Better Auth / Lucia / OAuth); nifra owns the session.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"route-guards"
|
|
15
15
|
],
|
|
16
16
|
"type": "module",
|
|
17
|
+
"nifra": {
|
|
18
|
+
"environment": "library"
|
|
19
|
+
},
|
|
17
20
|
"main": "./dist/index.js",
|
|
18
21
|
"module": "./dist/index.js",
|
|
19
22
|
"types": "./dist/index.d.ts",
|
|
@@ -37,7 +40,7 @@
|
|
|
37
40
|
"access": "public"
|
|
38
41
|
},
|
|
39
42
|
"peerDependencies": {
|
|
40
|
-
"@nifrajs/core": "^
|
|
43
|
+
"@nifrajs/core": "^4.0.0"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"@nifrajs/core": "workspace:*"
|
package/src/csrf.ts
CHANGED
|
@@ -4,15 +4,19 @@
|
|
|
4
4
|
* a cross-origin or same-origin *unsafe* request; it must match an allowed origin, else `403`. Safe
|
|
5
5
|
* methods (GET/HEAD/OPTIONS) pass. Apply with `app.use(csrf({ origins: ["https://example.com"] }))`.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { isSameOriginRequest, type Middleware } from "@nifrajs/core/server"
|
|
8
8
|
|
|
9
9
|
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"])
|
|
10
10
|
|
|
11
|
+
// A name per instance: `use()` skips a name it already applied, which would drop a second, stricter
|
|
12
|
+
// `csrf()` inside a `group()`.
|
|
13
|
+
let instances = 0
|
|
14
|
+
|
|
11
15
|
export interface CsrfOptions {
|
|
12
16
|
/**
|
|
13
|
-
* Allowed origins (e.g. `["https://example.com"]`).
|
|
14
|
-
*
|
|
15
|
-
* proxy
|
|
17
|
+
* Allowed origins (e.g. `["https://example.com"]`). When omitted, the request must be same-origin by
|
|
18
|
+
* host (an `https:` page reaching an `http:` request URL is accepted, so a TLS-terminating proxy
|
|
19
|
+
* works). Set it when the proxy rewrites `Host`, so the public origin differs from the worker's.
|
|
16
20
|
*/
|
|
17
21
|
readonly origins?: readonly string[]
|
|
18
22
|
}
|
|
@@ -22,14 +26,18 @@ const forbidden = (): Response =>
|
|
|
22
26
|
|
|
23
27
|
export function csrf(options: CsrfOptions = {}): Middleware {
|
|
24
28
|
const configured = options.origins !== undefined ? new Set(options.origins) : undefined
|
|
29
|
+
instances += 1
|
|
25
30
|
return {
|
|
26
|
-
name:
|
|
31
|
+
name: `csrf#${instances}`,
|
|
27
32
|
onRequest(req) {
|
|
28
33
|
if (SAFE_METHODS.has(req.method)) return undefined
|
|
29
|
-
|
|
34
|
+
// The same-origin default uses core's check, which accepts an `https:` page reaching an `http:`
|
|
35
|
+
// request URL on the same host - what every TLS-terminating proxy looks like from here.
|
|
36
|
+
const allowed = (candidate: string): boolean =>
|
|
37
|
+
configured !== undefined ? configured.has(candidate) : isSameOriginRequest(candidate, req)
|
|
30
38
|
|
|
31
39
|
const origin = req.headers.get("origin")
|
|
32
|
-
if (origin !== null) return allowed
|
|
40
|
+
if (origin !== null) return allowed(origin) ? undefined : forbidden()
|
|
33
41
|
|
|
34
42
|
// Some same-origin requests omit `Origin` - fall back to the `Referer`'s origin.
|
|
35
43
|
const referer = req.headers.get("referer")
|
|
@@ -40,7 +48,7 @@ export function csrf(options: CsrfOptions = {}): Middleware {
|
|
|
40
48
|
} catch {
|
|
41
49
|
return forbidden() // malformed Referer
|
|
42
50
|
}
|
|
43
|
-
return allowed
|
|
51
|
+
return allowed(refererOrigin) ? undefined : forbidden()
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
// A state-changing request with neither header → reject (fail closed; a browser always sends one).
|