@nifrajs/auth 3.5.0 → 4.0.2

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 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 { Middleware } from "@nifrajs/core/server";
7
+ import { type Middleware } from "@nifrajs/core/server";
8
8
  export interface CsrfOptions {
9
9
  /**
10
- * Allowed origins (e.g. `["https://example.com"]`). **Set this in production behind a proxy** - when
11
- * omitted, the check derives same-origin from the request URL, which is correct in dev / when the
12
- * proxy preserves `Host` but not when the public origin differs from the worker's.
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAItD,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,CA2B1D"}
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: "csrf",
17
+ name: `csrf#${instances}`,
7
18
  onRequest(req) {
8
19
  if (SAFE_METHODS.has(req.method))
9
20
  return undefined;
10
- const allowed = configured ?? new Set([new URL(req.url).origin]);
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.has(origin) ? undefined : forbidden();
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.has(refererOrigin) ? undefined : forbidden();
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":"AAQA,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAA;AAWxD,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,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,SAAS,CAAC,GAAG;YACX,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,OAAO,SAAS,CAAA;YAClD,MAAM,OAAO,GAAG,UAAU,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;YAEhE,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACxC,IAAI,MAAM,KAAK,IAAI;gBAAE,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAA;YAEzE,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,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAA;YAC7D,CAAC;YAED,mGAAmG;YACnG,OAAO,SAAS,EAAE,CAAA;QACpB,CAAC;KACF,CAAA;AACH,CAAC"}
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.5.0",
3
+ "version": "4.0.2",
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": "^3.5.0"
43
+ "@nifrajs/core": "^4.0.2"
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 { Middleware } from "@nifrajs/core/server"
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"]`). **Set this in production behind a proxy** - when
14
- * omitted, the check derives same-origin from the request URL, which is correct in dev / when the
15
- * proxy preserves `Host` but not when the public origin differs from the worker's.
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: "csrf",
31
+ name: `csrf#${instances}`,
27
32
  onRequest(req) {
28
33
  if (SAFE_METHODS.has(req.method)) return undefined
29
- const allowed = configured ?? new Set([new URL(req.url).origin])
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.has(origin) ? undefined : forbidden()
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.has(refererOrigin) ? undefined : forbidden()
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).