@ultimat3/http 22.0.0 → 22.1.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/package.json +5 -5
- package/src/csrf.ts +11 -22
- package/src/error-map.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/http",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.1.0",
|
|
4
4
|
"description": "Owned request lifecycle over Bun.serve: router, ordered pipeline, problem+json errors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"test": "bun test"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ultimat3/core": "22.
|
|
35
|
-
"@ultimat3/i18n": "22.
|
|
36
|
-
"@ultimat3/schema": "22.
|
|
37
|
-
"@ultimat3/time": "22.
|
|
34
|
+
"@ultimat3/core": "22.1.0",
|
|
35
|
+
"@ultimat3/i18n": "22.1.0",
|
|
36
|
+
"@ultimat3/schema": "22.1.0",
|
|
37
|
+
"@ultimat3/time": "22.1.0"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/src/csrf.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// went through. `setRedirect` exists so those form posts work without JS, which makes them a
|
|
6
6
|
// first-class surface here rather than a legacy one.
|
|
7
7
|
|
|
8
|
-
import { classifyAddress } from '@ultimat3/core';
|
|
8
|
+
import { classifyAddress, proveSameOrigin } from '@ultimat3/core';
|
|
9
9
|
import type { CorsConfig } from './cors';
|
|
10
10
|
import { originListed } from './cors';
|
|
11
11
|
import { HttpError } from './errors';
|
|
@@ -26,9 +26,6 @@ export const DEFAULT_CSRF: CsrfConfig = { mode: 'origin' };
|
|
|
26
26
|
/** Methods with no side effects, per RFC 9110. A CSRF check on these is a check on nothing. */
|
|
27
27
|
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']);
|
|
28
28
|
|
|
29
|
-
/** The complete `Sec-Fetch-Site` vocabulary. Anything else was written by a non-browser. */
|
|
30
|
-
const KNOWN_SITES = new Set(['same-origin', 'same-site', 'cross-site', 'none']);
|
|
31
|
-
|
|
32
29
|
export interface CsrfCheckInput {
|
|
33
30
|
readonly method: string;
|
|
34
31
|
/** The origin this app was reached on — scheme from `ctx.https`, host from the request URL. */
|
|
@@ -60,29 +57,21 @@ export const checkCsrf = (input: CsrfCheckInput): CsrfVerdict => {
|
|
|
60
57
|
if (input.anonymous) return { ok: true };
|
|
61
58
|
if (input.hasAuthorizationHeader) return { ok: true };
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
// The rule itself is core's, shared with the sync node's upgrade: one answer to "did this
|
|
61
|
+
// come from this app?" on every surface an ambient credential reaches.
|
|
62
|
+
//
|
|
66
63
|
// `originListed`, never `allowedOrigin`: that one answers the RESPONSE header, and for
|
|
67
64
|
// `origins: ['*'], credentials: false` — the only wildcard `assertCorsConfig` admits — its
|
|
68
65
|
// answer is `'*'`, which is not null and so read as "this origin is one we allow". A
|
|
69
66
|
// credentialed cross-site POST from evil.test was therefore accepted by the check that exists
|
|
70
67
|
// to refuse exactly it. An exact match is the only allowance a write may be built on.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
return {
|
|
80
|
-
ok: false,
|
|
81
|
-
reason:
|
|
82
|
-
input.origin === null
|
|
83
|
-
? 'the request carried neither sec-fetch-site nor origin, so it cannot be shown to be same-origin'
|
|
84
|
-
: 'the origin it declares is not this app and is not listed in http.cors.origins',
|
|
85
|
-
};
|
|
68
|
+
return proveSameOrigin({
|
|
69
|
+
selfOrigins: [input.selfOrigin],
|
|
70
|
+
origin: input.origin,
|
|
71
|
+
secFetchSite: input.secFetchSite,
|
|
72
|
+
listed: (origin) => originListed(input.cors, origin),
|
|
73
|
+
listName: 'http.cors.origins',
|
|
74
|
+
});
|
|
86
75
|
};
|
|
87
76
|
|
|
88
77
|
/** The origin a browser compares against — the PUBLIC one, so a TLS-terminating proxy agrees. */
|
package/src/error-map.ts
CHANGED
|
@@ -425,6 +425,11 @@ export const ERROR_STATUS = {
|
|
|
425
425
|
X_REALTIME_UNINSTALLED: 500,
|
|
426
426
|
// Boot-time: a realtime config and its environment that disagree refuse before any request.
|
|
427
427
|
X_REALTIME_TOPOLOGY: 500,
|
|
428
|
+
// The replicator's own Postgres connection failed TLS. A role that never answers a request, so the
|
|
429
|
+
// row keeps the table closed at 500, beside `X_REALTIME_TOPOLOGY`.
|
|
430
|
+
X_REPLICATION_TLS: 500,
|
|
431
|
+
// A browser page on a foreign origin asked for a socket — refused, not unauthenticated: 403.
|
|
432
|
+
X_SOCKET_ORIGIN_REFUSED: 403,
|
|
428
433
|
X_RECORD_KEY_MISSING: 500,
|
|
429
434
|
X_RECORD_REJECTED: 500,
|
|
430
435
|
X_SYNC_UNCONFIGURED: 500,
|