@c9up/aurora 0.1.34 → 0.1.37
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/AuroraProvider.d.ts +1 -0
- package/dist/AuroraProvider.js +1 -0
- package/dist/augmentations.d.ts +43 -0
- package/dist/augmentations.js +17 -0
- package/dist/browser.js +1 -1
- package/dist/html.js +91 -11
- package/dist/http.d.ts +8 -1
- package/dist/http.js +19 -0
- package/dist/hydrate.js +9 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/dist/relay.js +6 -28
- package/dist/render.js +1 -2
- package/dist/rpc.js +12 -30
- package/dist/ssr.js +47 -8
- package/dist/xsrf.d.ts +63 -0
- package/dist/xsrf.js +81 -0
- package/package.json +6 -4
- package/src/AuroraProvider.ts +1 -0
- package/src/augmentations.ts +50 -0
- package/src/browser.ts +1 -1
- package/src/html.ts +93 -10
- package/src/http.ts +27 -1
- package/src/hydrate.ts +7 -5
- package/src/index.ts +2 -0
- package/src/relay.ts +7 -25
- package/src/render.ts +1 -2
- package/src/rpc.ts +14 -28
- package/src/ssr.ts +46 -8
- package/src/xsrf.ts +97 -0
package/src/xsrf.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Echoing the CSRF cookie back as a header, in one place.
|
|
4
|
+
*
|
|
5
|
+
* The signed double-submit guard on the server reads `XSRF-TOKEN` from the
|
|
6
|
+
* cookie jar and compares it to `X-XSRF-TOKEN` on the request. A browser sends
|
|
7
|
+
* the cookie on its own; the header is the client's half, and it is the half
|
|
8
|
+
* that says "this request came from our own page", because a cross-site caller
|
|
9
|
+
* can cause the cookie to ride along but cannot read it.
|
|
10
|
+
*
|
|
11
|
+
* This lived three times over. `rpc.ts` and `relay.ts` each had a copy, and
|
|
12
|
+
* both described theirs as mirroring `HttpClient.#retrieveXsrfToken` — a method
|
|
13
|
+
* `HttpClient` never had. So the client the docs tell you to submit a form with
|
|
14
|
+
* sent no header at all, and every POST through it was refused the moment an
|
|
15
|
+
* application turned CSRF on. One reader now, used by all three.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** The cookie the server seeds. */
|
|
19
|
+
export const XSRF_COOKIE_NAME = "XSRF-TOKEN";
|
|
20
|
+
|
|
21
|
+
/** The header it is echoed in (the Axios/Angular convention the server reads). */
|
|
22
|
+
export const XSRF_HEADER_NAME = "X-XSRF-TOKEN";
|
|
23
|
+
|
|
24
|
+
/** Per-client switches for the automatic header. */
|
|
25
|
+
export interface XsrfOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Echo the CSRF cookie as a header on same-origin requests. Default `true`.
|
|
28
|
+
* A no-op outside a browser and when the cookie is absent — a bearer-authed
|
|
29
|
+
* API is CSRF-exempt and seeds no cookie, so nothing is sent there either.
|
|
30
|
+
*/
|
|
31
|
+
xsrf?: boolean;
|
|
32
|
+
/** Cookie to read the token from. Default `XSRF-TOKEN`. */
|
|
33
|
+
xsrfCookieName?: string;
|
|
34
|
+
/** Header to echo it in. Default `X-XSRF-TOKEN`. */
|
|
35
|
+
xsrfHeaderName?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read a cookie's raw value from `document.cookie`.
|
|
40
|
+
*
|
|
41
|
+
* Verbatim, never decoded: the server compares the header to the cookie
|
|
42
|
+
* byte-for-byte. The token is hex + `.` + base64url, so there is nothing a
|
|
43
|
+
* decode could change — but a decode that ever did change something would turn
|
|
44
|
+
* a valid request into a rejected one, silently.
|
|
45
|
+
*
|
|
46
|
+
* Returns `undefined` server-side (no `document`) or when the cookie is absent.
|
|
47
|
+
*/
|
|
48
|
+
export function readXsrfCookie(
|
|
49
|
+
name: string = XSRF_COOKIE_NAME,
|
|
50
|
+
): string | undefined {
|
|
51
|
+
if (typeof document === "undefined") return undefined;
|
|
52
|
+
const prefix = `${name}=`;
|
|
53
|
+
for (const part of document.cookie.split(";")) {
|
|
54
|
+
const trimmed = part.trimStart();
|
|
55
|
+
if (trimmed.startsWith(prefix)) return trimmed.slice(prefix.length);
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Is this URL served by the page's own origin?
|
|
62
|
+
*
|
|
63
|
+
* The question is not "does it match the client's baseURL" — a client whose
|
|
64
|
+
* baseURL IS a third-party API would pass that one. A CSRF token authenticates
|
|
65
|
+
* the page's session; sending it anywhere else hands a working token to whoever
|
|
66
|
+
* runs that host.
|
|
67
|
+
*
|
|
68
|
+
* A relative URL is same-origin by construction. Outside a browser there is no
|
|
69
|
+
* page and no cookie, so the answer is no.
|
|
70
|
+
*/
|
|
71
|
+
export function isSameOriginAsPage(url: string): boolean {
|
|
72
|
+
if (typeof window === "undefined") return false;
|
|
73
|
+
if (!/^[a-z][a-z\d+\-.]*:\/\//i.test(url)) return true;
|
|
74
|
+
try {
|
|
75
|
+
return new URL(url).origin === window.location.origin;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The header to add for `url`, or `undefined` when there is nothing to send.
|
|
83
|
+
*
|
|
84
|
+
* Absent cookie, disabled, cross-origin target, or no browser: nothing. The
|
|
85
|
+
* caller merges the result rather than being handed an empty object, so a call
|
|
86
|
+
* site cannot accidentally overwrite a header it set itself.
|
|
87
|
+
*/
|
|
88
|
+
export function xsrfHeaderFor(
|
|
89
|
+
url: string,
|
|
90
|
+
options: XsrfOptions = {},
|
|
91
|
+
): Record<string, string> | undefined {
|
|
92
|
+
if (options.xsrf === false) return undefined;
|
|
93
|
+
if (!isSameOriginAsPage(url)) return undefined;
|
|
94
|
+
const token = readXsrfCookie(options.xsrfCookieName ?? XSRF_COOKIE_NAME);
|
|
95
|
+
if (token === undefined) return undefined;
|
|
96
|
+
return { [options.xsrfHeaderName ?? XSRF_HEADER_NAME]: token };
|
|
97
|
+
}
|