remix 3.0.0-beta.0 → 3.0.0-beta.10

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.
Files changed (258) hide show
  1. package/README.md +4 -2
  2. package/dist/assets/types/hmr.d.ts +2 -0
  3. package/dist/cli-entry.js +1 -1
  4. package/dist/data-table/cli.d.ts +2 -0
  5. package/dist/data-table/cli.d.ts.map +1 -0
  6. package/dist/{ui/scroll-lock.js → data-table/cli.js} +1 -1
  7. package/dist/fetch-router.d.ts +7 -0
  8. package/dist/fetch-router.d.ts.map +1 -1
  9. package/dist/headers/accept-encoding.d.ts +2 -0
  10. package/dist/headers/accept-encoding.d.ts.map +1 -0
  11. package/dist/headers/accept-encoding.js +2 -0
  12. package/dist/headers/accept-language.d.ts +2 -0
  13. package/dist/headers/accept-language.d.ts.map +1 -0
  14. package/dist/headers/accept-language.js +2 -0
  15. package/dist/headers/accept.d.ts +2 -0
  16. package/dist/headers/accept.d.ts.map +1 -0
  17. package/dist/headers/accept.js +2 -0
  18. package/dist/headers/cache-control.d.ts +2 -0
  19. package/dist/headers/cache-control.d.ts.map +1 -0
  20. package/dist/headers/cache-control.js +2 -0
  21. package/dist/headers/content-disposition.d.ts +2 -0
  22. package/dist/headers/content-disposition.d.ts.map +1 -0
  23. package/dist/headers/content-disposition.js +2 -0
  24. package/dist/headers/content-range.d.ts +2 -0
  25. package/dist/headers/content-range.d.ts.map +1 -0
  26. package/dist/headers/content-range.js +2 -0
  27. package/dist/headers/content-type.d.ts +2 -0
  28. package/dist/headers/content-type.d.ts.map +1 -0
  29. package/dist/headers/content-type.js +2 -0
  30. package/dist/headers/cookie.d.ts +2 -0
  31. package/dist/headers/cookie.d.ts.map +1 -0
  32. package/dist/headers/cookie.js +2 -0
  33. package/dist/headers/if-match.d.ts +2 -0
  34. package/dist/headers/if-match.d.ts.map +1 -0
  35. package/dist/headers/if-match.js +2 -0
  36. package/dist/headers/if-none-match.d.ts +2 -0
  37. package/dist/headers/if-none-match.d.ts.map +1 -0
  38. package/dist/headers/if-none-match.js +2 -0
  39. package/dist/headers/if-range.d.ts +2 -0
  40. package/dist/headers/if-range.d.ts.map +1 -0
  41. package/dist/headers/if-range.js +2 -0
  42. package/dist/headers/range.d.ts +2 -0
  43. package/dist/headers/range.d.ts.map +1 -0
  44. package/dist/headers/range.js +2 -0
  45. package/dist/headers/raw-headers.d.ts +2 -0
  46. package/dist/headers/raw-headers.d.ts.map +1 -0
  47. package/dist/headers/raw-headers.js +2 -0
  48. package/dist/headers/set-cookie.d.ts +2 -0
  49. package/dist/headers/set-cookie.d.ts.map +1 -0
  50. package/dist/headers/set-cookie.js +2 -0
  51. package/dist/headers/vary.d.ts +2 -0
  52. package/dist/headers/vary.d.ts.map +1 -0
  53. package/dist/{ui/separator.js → headers/vary.js} +1 -1
  54. package/dist/node-hmr/runtime.d.ts +2 -0
  55. package/dist/node-hmr/runtime.d.ts.map +1 -0
  56. package/dist/node-hmr/runtime.js +2 -0
  57. package/dist/node-hmr/types.d.ts +2 -0
  58. package/dist/node-hmr.d.ts +2 -0
  59. package/dist/node-hmr.d.ts.map +1 -0
  60. package/dist/{ui/glyph.js → node-hmr.js} +1 -1
  61. package/dist/node-tsx/load-module.d.ts +2 -0
  62. package/dist/node-tsx/load-module.d.ts.map +1 -0
  63. package/dist/node-tsx/load-module.js +2 -0
  64. package/dist/node-tsx.d.ts +3 -0
  65. package/dist/node-tsx.d.ts.map +1 -0
  66. package/dist/node-tsx.js +3 -0
  67. package/dist/render-middleware.d.ts +2 -0
  68. package/dist/render-middleware.d.ts.map +1 -0
  69. package/dist/render-middleware.js +2 -0
  70. package/dist/route-pattern/href.d.ts +2 -0
  71. package/dist/route-pattern/href.d.ts.map +1 -0
  72. package/dist/route-pattern/href.js +2 -0
  73. package/dist/route-pattern/join.d.ts +2 -0
  74. package/dist/route-pattern/join.d.ts.map +1 -0
  75. package/dist/route-pattern/join.js +2 -0
  76. package/dist/route-pattern/match.d.ts +2 -0
  77. package/dist/route-pattern/match.d.ts.map +1 -0
  78. package/dist/route-pattern/match.js +2 -0
  79. package/dist/ui/accordion/primitives.d.ts +2 -0
  80. package/dist/ui/accordion/primitives.d.ts.map +1 -0
  81. package/dist/ui/accordion/primitives.js +2 -0
  82. package/dist/ui/button.d.ts +1 -0
  83. package/dist/ui/button.d.ts.map +1 -1
  84. package/dist/ui/button.js +1 -0
  85. package/dist/ui/checkbox.d.ts +3 -0
  86. package/dist/ui/checkbox.d.ts.map +1 -0
  87. package/dist/ui/checkbox.js +3 -0
  88. package/dist/ui/combobox/primitives.d.ts +2 -0
  89. package/dist/ui/combobox/primitives.d.ts.map +1 -0
  90. package/dist/ui/combobox/primitives.js +2 -0
  91. package/dist/ui/dev/refresh.d.ts +2 -0
  92. package/dist/ui/dev/refresh.d.ts.map +1 -0
  93. package/dist/ui/dev/refresh.js +2 -0
  94. package/dist/ui/input.d.ts +3 -0
  95. package/dist/ui/input.d.ts.map +1 -0
  96. package/dist/ui/input.js +3 -0
  97. package/dist/ui/menu/primitives.d.ts +2 -0
  98. package/dist/ui/menu/primitives.d.ts.map +1 -0
  99. package/dist/ui/menu/primitives.js +2 -0
  100. package/dist/ui/radio.d.ts +3 -0
  101. package/dist/ui/radio.d.ts.map +1 -0
  102. package/dist/ui/radio.js +3 -0
  103. package/dist/ui/select/primitives.d.ts +2 -0
  104. package/dist/ui/select/primitives.d.ts.map +1 -0
  105. package/dist/ui/select/primitives.js +2 -0
  106. package/dist/ui/tabs/primitives.d.ts +2 -0
  107. package/dist/ui/tabs/primitives.d.ts.map +1 -0
  108. package/dist/ui/tabs/primitives.js +2 -0
  109. package/dist/ui/tabs.d.ts +2 -0
  110. package/dist/ui/tabs.d.ts.map +1 -0
  111. package/{src/ui/theme.ts → dist/ui/tabs.js} +1 -1
  112. package/dist/ui/toggle/primitives.d.ts +2 -0
  113. package/dist/ui/toggle/primitives.d.ts.map +1 -0
  114. package/dist/ui/toggle/primitives.js +2 -0
  115. package/dist/ui/toggle.d.ts +3 -0
  116. package/dist/ui/toggle.d.ts.map +1 -0
  117. package/dist/ui/toggle.js +3 -0
  118. package/dist/ui-hmr/assets.d.ts +2 -0
  119. package/dist/ui-hmr/assets.d.ts.map +1 -0
  120. package/dist/ui-hmr/assets.js +2 -0
  121. package/dist/ui-hmr/node.d.ts +3 -0
  122. package/dist/ui-hmr/node.d.ts.map +1 -0
  123. package/dist/ui-hmr/node.js +3 -0
  124. package/dist/ui-hmr/runtime/browser.d.ts +2 -0
  125. package/dist/ui-hmr/runtime/browser.d.ts.map +1 -0
  126. package/dist/ui-hmr/runtime/browser.js +2 -0
  127. package/dist/ui-hmr/runtime/server.d.ts +2 -0
  128. package/dist/ui-hmr/runtime/server.d.ts.map +1 -0
  129. package/dist/ui-hmr/runtime/server.js +2 -0
  130. package/dist/ui-hmr.d.ts +2 -0
  131. package/dist/ui-hmr.d.ts.map +1 -0
  132. package/{src/ui/glyph.ts → dist/ui-hmr.js} +1 -1
  133. package/package.json +284 -129
  134. package/src/assert/README.md +116 -0
  135. package/src/assets/README.md +805 -0
  136. package/src/assets/types/hmr.d.ts +2 -0
  137. package/src/async-context-middleware/README.md +100 -0
  138. package/src/auth/README.md +445 -0
  139. package/src/auth-middleware/README.md +246 -0
  140. package/src/cli/README.md +182 -0
  141. package/src/compression-middleware/README.md +176 -0
  142. package/src/cookie/README.md +106 -0
  143. package/src/cop-middleware/README.md +117 -0
  144. package/src/cors-middleware/README.md +174 -0
  145. package/src/csrf-middleware/README.md +92 -0
  146. package/src/data-schema/README.md +416 -0
  147. package/src/data-table/README.md +678 -0
  148. package/src/data-table/cli.ts +2 -0
  149. package/src/data-table-mysql/README.md +120 -0
  150. package/src/data-table-postgres/README.md +98 -0
  151. package/src/data-table-sqlite/README.md +100 -0
  152. package/src/fetch-proxy/README.md +70 -0
  153. package/src/fetch-router/README.md +982 -0
  154. package/src/fetch-router.ts +7 -0
  155. package/src/file-storage/README.md +57 -0
  156. package/src/file-storage-s3/README.md +46 -0
  157. package/src/form-data-middleware/README.md +108 -0
  158. package/src/form-data-parser/README.md +158 -0
  159. package/src/fs/README.md +60 -0
  160. package/src/headers/README.md +639 -0
  161. package/src/headers/accept-encoding.ts +2 -0
  162. package/src/headers/accept-language.ts +2 -0
  163. package/src/headers/accept.ts +2 -0
  164. package/src/headers/cache-control.ts +2 -0
  165. package/src/headers/content-disposition.ts +2 -0
  166. package/src/headers/content-range.ts +2 -0
  167. package/src/headers/content-type.ts +2 -0
  168. package/src/headers/cookie.ts +2 -0
  169. package/src/headers/if-match.ts +2 -0
  170. package/src/headers/if-none-match.ts +2 -0
  171. package/src/headers/if-range.ts +2 -0
  172. package/src/headers/range.ts +2 -0
  173. package/src/headers/raw-headers.ts +2 -0
  174. package/src/headers/set-cookie.ts +2 -0
  175. package/src/{node-serve.ts → headers/vary.ts} +1 -1
  176. package/src/html-template/README.md +101 -0
  177. package/src/lazy-file/README.md +109 -0
  178. package/src/logger-middleware/README.md +132 -0
  179. package/src/method-override-middleware/README.md +71 -0
  180. package/src/mime/README.md +117 -0
  181. package/src/multipart-parser/README.md +241 -0
  182. package/src/node-fetch-server/README.md +377 -0
  183. package/src/node-hmr/README.md +307 -0
  184. package/src/node-hmr/runtime.ts +2 -0
  185. package/src/node-hmr/types.d.ts +2 -0
  186. package/{dist/ui/theme.js → src/node-hmr.ts} +1 -1
  187. package/src/node-tsx/README.md +74 -0
  188. package/src/node-tsx/load-module.ts +2 -0
  189. package/{dist/node-serve.js → src/node-tsx.ts} +2 -1
  190. package/src/render-middleware/README.md +99 -0
  191. package/src/render-middleware.ts +2 -0
  192. package/src/route-pattern/README.md +424 -0
  193. package/src/route-pattern/href.ts +2 -0
  194. package/src/route-pattern/join.ts +2 -0
  195. package/src/route-pattern/match.ts +2 -0
  196. package/src/session/README.md +171 -0
  197. package/src/session-middleware/README.md +111 -0
  198. package/src/session-storage-memcache/README.md +37 -0
  199. package/src/session-storage-redis/README.md +36 -0
  200. package/src/static-middleware/README.md +89 -0
  201. package/src/tar-parser/README.md +74 -0
  202. package/src/terminal/README.md +92 -0
  203. package/src/test/README.md +500 -0
  204. package/src/ui/README.md +216 -0
  205. package/src/ui/accordion/README.md +202 -0
  206. package/src/ui/accordion/primitives/README.md +202 -0
  207. package/src/ui/accordion/primitives.ts +2 -0
  208. package/src/ui/anchor/README.md +196 -0
  209. package/src/ui/animation/README.md +316 -0
  210. package/src/ui/breadcrumbs/README.md +55 -0
  211. package/src/ui/button/README.md +44 -0
  212. package/src/ui/button.ts +1 -0
  213. package/src/ui/checkbox/README.md +59 -0
  214. package/src/ui/checkbox.ts +3 -0
  215. package/src/ui/combobox/README.md +194 -0
  216. package/src/ui/combobox/primitives/README.md +194 -0
  217. package/src/ui/combobox/primitives.ts +2 -0
  218. package/src/ui/dev/refresh.ts +2 -0
  219. package/src/ui/input/README.md +52 -0
  220. package/src/ui/input.ts +3 -0
  221. package/src/ui/listbox/README.md +83 -0
  222. package/src/ui/menu/README.md +161 -0
  223. package/src/ui/menu/primitives/README.md +161 -0
  224. package/src/ui/menu/primitives.ts +2 -0
  225. package/src/ui/popover/README.md +103 -0
  226. package/src/ui/radio/README.md +53 -0
  227. package/src/ui/radio.ts +3 -0
  228. package/src/ui/select/README.md +117 -0
  229. package/src/ui/select/primitives/README.md +117 -0
  230. package/src/ui/select/primitives.ts +2 -0
  231. package/src/ui/server/README.md +103 -0
  232. package/src/ui/tabs/README.md +141 -0
  233. package/src/ui/tabs/primitives/README.md +141 -0
  234. package/src/ui/tabs/primitives.ts +2 -0
  235. package/src/ui/tabs.ts +2 -0
  236. package/src/ui/test/README.md +198 -0
  237. package/src/ui/toggle/README.md +56 -0
  238. package/src/ui/toggle/primitives/README.md +56 -0
  239. package/src/ui/toggle/primitives.ts +2 -0
  240. package/src/ui/toggle.ts +3 -0
  241. package/src/ui-hmr/README.md +119 -0
  242. package/src/ui-hmr/assets.ts +2 -0
  243. package/src/ui-hmr/node.ts +3 -0
  244. package/src/ui-hmr/runtime/browser.ts +2 -0
  245. package/src/ui-hmr/runtime/server.ts +2 -0
  246. package/src/ui-hmr.ts +2 -0
  247. package/dist/node-serve.d.ts +0 -2
  248. package/dist/node-serve.d.ts.map +0 -1
  249. package/dist/ui/glyph.d.ts +0 -2
  250. package/dist/ui/glyph.d.ts.map +0 -1
  251. package/dist/ui/scroll-lock.d.ts +0 -2
  252. package/dist/ui/scroll-lock.d.ts.map +0 -1
  253. package/dist/ui/separator.d.ts +0 -2
  254. package/dist/ui/separator.d.ts.map +0 -1
  255. package/dist/ui/theme.d.ts +0 -2
  256. package/dist/ui/theme.d.ts.map +0 -1
  257. package/src/ui/scroll-lock.ts +0 -2
  258. package/src/ui/separator.ts +0 -2
@@ -0,0 +1,106 @@
1
+ # cookie
2
+
3
+ Type-safe cookie parsing and serialization for Remix. It supports secure signing, secret rotation, and complete cookie attribute control.
4
+
5
+ ## Features
6
+
7
+ - **Secure Cookie Signing:** Built-in cryptographic signing using HMAC-SHA256 to prevent cookie tampering, with support for secret rotation without breaking existing cookies.
8
+ - **Secret Rotation Support:** Seamlessly rotate signing secrets while maintaining backward compatibility with existing cookies.
9
+ - **Web Standards Compliant:** Built on Web Crypto API and standard cookie parsing, making it runtime-agnostic (Node.js, Bun, Deno, Cloudflare Workers).
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm i remix
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```tsx
20
+ import { createCookie } from 'remix/cookie'
21
+
22
+ let sessionCookie = createCookie('session', {
23
+ httpOnly: true,
24
+ secrets: ['s3cret1'],
25
+ secure: true,
26
+ })
27
+
28
+ cookie.name // "session"
29
+ cookie.httpOnly // true
30
+ cookie.secure // true
31
+ cookie.signed // true
32
+
33
+ // Get the value of the "session" cookie from the request's `Cookie` header
34
+ let value = await sessionCookie.parse(request.headers.get('Cookie'))
35
+
36
+ // Set the value of the cookie in a Response's `Set-Cookie` header
37
+ let response = new Response('Hello, world!', {
38
+ headers: {
39
+ 'Set-Cookie': await sessionCookie.serialize(value),
40
+ },
41
+ })
42
+ ```
43
+
44
+ ### Signing Cookies
45
+
46
+ This library supports signing cookies, which is useful for ensuring the integrity of the cookie value and preventing tampering. Signing happens automatically when you provide a `secrets` option to the `Cookie` constructor.
47
+
48
+ Secret rotation is also supported, so you can easily rotate in new secrets without breaking existing cookies.
49
+
50
+ ```tsx
51
+ import { Cookie } from 'remix/cookie'
52
+
53
+ // Start with a single secret
54
+ let sessionCookie = createCookie('session', {
55
+ secrets: ['secret1'],
56
+ })
57
+
58
+ console.log(sessionCookie.signed) // true
59
+
60
+ let response = new Response('Hello, world!', {
61
+ headers: {
62
+ 'Set-Cookie': await sessionCookie.serialize(value),
63
+ },
64
+ })
65
+ ```
66
+
67
+ All cookies sent in this scenario will be signed with the secret `secret1`. Later, when it's time to rotate secrets, add a new secret to the beginning of the array and all existing cookies will still be able to be parsed.
68
+
69
+ ```tsx
70
+ let sessionCookie = createCookie('session', {
71
+ secrets: ['secret2', 'secret1'],
72
+ })
73
+
74
+ // This works for cookies signed with either secret
75
+ let value = await sessionCookie.parse(request.headers.get('Cookie'))
76
+
77
+ // Newly serialized cookies will be signed with the new secret
78
+ let response = new Response('Hello, world!', {
79
+ headers: {
80
+ 'Set-Cookie': await sessionCookie.serialize(value),
81
+ },
82
+ })
83
+ ```
84
+
85
+ ### Custom Encoding
86
+
87
+ By default, cookie values are percent-encoded and wrapped in base64 so arbitrary string values can be safely stored in cookies. This is suitable for most use cases, but you can provide your own functions to customize the encoding and decoding of the cookie value.
88
+
89
+ ```tsx
90
+ let sessionCookie = createCookie('session', {
91
+ encode: (value) => value.replaceAll(' ', '-'),
92
+ decode: (value) => value.replaceAll('-', ' '),
93
+ })
94
+ ```
95
+
96
+ Custom `encode` functions are used as the full cookie value codec. Their return values are signed when `secrets` are configured and then serialized as-is, without the default base64 wrapper. This can be useful for viewing the value of cookies in a human-readable format in the browser's developer tools. But you should be sure that the encoded cookie value contains only characters that are [valid in a cookie value](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#attributes) and safe for HTTP headers. Raw Unicode characters should be escaped or encoded by your custom function before they are returned.
97
+
98
+ ## Related Packages
99
+
100
+ - [`headers`](https://github.com/remix-run/remix/tree/main/packages/headers) - Type-safe HTTP header manipulation
101
+ - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Build HTTP routers using the web fetch API
102
+ - [`node-fetch-server`](https://github.com/remix-run/remix/tree/main/packages/node-fetch-server) - Build HTTP servers on Node.js using the web fetch API
103
+
104
+ ## License
105
+
106
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
@@ -0,0 +1,117 @@
1
+ # cop-middleware
2
+
3
+ Cross-origin protection middleware for Remix. It mirrors Go's `CrossOriginProtection` by rejecting unsafe cross-origin browser requests without synchronizer tokens.
4
+
5
+ ## Features
6
+
7
+ - **Browser Provenance Checks** - Uses `Sec-Fetch-Site` when present and falls back to `Origin`
8
+ - **Trusted Origins** - Allow specific cross-origin callers by exact origin
9
+ - **Explicit Escape Hatches** - Support insecure bypass patterns for endpoints like webhooks
10
+ - **No Session State** - Does not require synchronizer tokens or server-side CSRF storage
11
+
12
+ ## Installation
13
+
14
+ ```sh
15
+ npm i remix
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```ts
21
+ import { createRouter } from 'remix/router'
22
+ import { cop } from 'remix/middleware/cop'
23
+
24
+ let router = createRouter({
25
+ middleware: [cop()],
26
+ })
27
+ ```
28
+
29
+ ## Behavior
30
+
31
+ For unsafe methods (`POST`, `PUT`, `PATCH`, `DELETE`), `cop()` follows the same broad model as Go's `CrossOriginProtection`:
32
+
33
+ - Allow `Sec-Fetch-Site: same-origin`
34
+ - Allow `Sec-Fetch-Site: none`
35
+ - Reject other `Sec-Fetch-Site` values unless the request matches a trusted origin or insecure bypass
36
+ - If `Sec-Fetch-Site` is missing, compare `Origin` to the request host
37
+ - If both `Sec-Fetch-Site` and `Origin` are missing, allow the request
38
+
39
+ This middleware is intentionally tokenless. If you cannot guarantee the deployment assumptions behind that model, prefer [`csrf-middleware`](https://github.com/remix-run/remix/tree/main/packages/csrf-middleware).
40
+
41
+ ## Caveats
42
+
43
+ - `cop()` is a browser-origin guard, not a universal CSRF solution. It is designed for deployments that can rely on modern browser provenance signals and same-origin request handling.
44
+ - If both `Sec-Fetch-Site` and `Origin` are missing on an unsafe request, `cop()` allows the request to continue. This is intentional so older clients and non-browser callers do not fail closed by default.
45
+ - If `Sec-Fetch-Site` is missing, `cop()` only rejects when `Origin` is present and does not match the request host.
46
+ - If you need stronger guarantees for session-backed form workflows, mixed deployment environments, or requests that should not fall through when browser provenance headers are missing, use [`csrf-middleware`](https://github.com/remix-run/remix/tree/main/packages/csrf-middleware) or layer both middlewares together.
47
+
48
+ ## Using with csrf-middleware
49
+
50
+ You can also layer `cop()` in front of `csrf()` when you want both browser provenance checks and session-backed synchronizer tokens.
51
+
52
+ ```ts
53
+ import { createCookie } from 'remix/cookie'
54
+ import { createRouter } from 'remix/router'
55
+ import { createCookieSessionStorage } from 'remix/session-storage/cookie'
56
+ import { session } from 'remix/middleware/session'
57
+ import { cop } from 'remix/middleware/cop'
58
+ import { csrf } from 'remix/middleware/csrf'
59
+
60
+ let sessionCookie = createCookie('__session', { secrets: ['secret1'] })
61
+ let sessionStorage = createCookieSessionStorage()
62
+
63
+ let router = createRouter({
64
+ middleware: [cop(), session(sessionCookie, sessionStorage), csrf()],
65
+ })
66
+ ```
67
+
68
+ In this setup, `cop()` runs first and rejects unsafe cross-origin browser requests early using `Sec-Fetch-Site` and `Origin`. Requests that pass `cop()` continue into `csrf()`, which still enforces synchronizer-token validation and origin checks for the remaining traffic.
69
+
70
+ ## Trusted Origins
71
+
72
+ ```ts
73
+ import { createRouter } from 'remix/router'
74
+ import { cop } from 'remix/middleware/cop'
75
+
76
+ let router = createRouter({
77
+ middleware: [
78
+ cop({
79
+ trustedOrigins: ['https://admin.example.com'],
80
+ }),
81
+ ],
82
+ })
83
+ ```
84
+
85
+ Trusted origins must be exact origin values in the form `scheme://host[:port]`.
86
+
87
+ ## Insecure Bypass Patterns
88
+
89
+ Bypass patterns intentionally weaken protection for specific endpoints. They support:
90
+
91
+ - Optional method prefixes, for example `POST /webhooks/{provider}`
92
+ - Exact paths, for example `/healthz`
93
+ - Trailing-slash subtree patterns, for example `/webhooks/`
94
+ - Single-segment wildcards with `{name}`
95
+ - Tail wildcards with `{name...}`
96
+
97
+ ```ts
98
+ import { createRouter } from 'remix/router'
99
+ import { cop } from 'remix/middleware/cop'
100
+
101
+ let router = createRouter({
102
+ middleware: [
103
+ cop({
104
+ insecureBypassPatterns: ['POST /webhooks/{provider}', '/healthz'],
105
+ }),
106
+ ],
107
+ })
108
+ ```
109
+
110
+ ## Related Packages
111
+
112
+ - [`csrf-middleware`](https://github.com/remix-run/remix/tree/main/packages/csrf-middleware) - Session-backed CSRF protection with synchronizer tokens
113
+ - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Router for the web Fetch API
114
+
115
+ ## License
116
+
117
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
@@ -0,0 +1,174 @@
1
+ # cors-middleware
2
+
3
+ CORS middleware for Remix. It adds standard CORS response headers to Fetch API servers and can either short-circuit preflight requests or pass them through to app-defined `OPTIONS` handlers.
4
+
5
+ ## Features
6
+
7
+ - **Preflight Handling** - Automatically handles `OPTIONS` preflight requests
8
+ - **Flexible Origin Rules** - Supports static, regex, list, and function-based origin policies
9
+ - **Credential Support** - Supports credentialed requests with spec-safe origin reflection
10
+ - **Header Controls** - Configure allowed and exposed headers, preflight methods, and max age
11
+ - **Private Network Support** - Optionally allow private network preflight requests
12
+
13
+ ## Installation
14
+
15
+ ```sh
16
+ npm i remix
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ts
22
+ import { createRouter } from 'remix/router'
23
+ import { cors } from 'remix/middleware/cors'
24
+
25
+ let router = createRouter({
26
+ middleware: [
27
+ cors({
28
+ origin: ['https://app.example.com', 'https://admin.example.com'],
29
+ credentials: true,
30
+ exposedHeaders: ['X-Request-Id'],
31
+ }),
32
+ ],
33
+ })
34
+
35
+ router.get('/api/projects', () => {
36
+ return Response.json([{ id: 'p1', name: 'Remix' }], {
37
+ headers: {
38
+ 'X-Request-Id': 'req_123',
39
+ },
40
+ })
41
+ })
42
+ ```
43
+
44
+ ## Origin Policies
45
+
46
+ `origin` supports:
47
+
48
+ - `'*'` to allow all origins
49
+ - `string` for a single exact origin
50
+ - `RegExp` for pattern-based matching
51
+ - `Array<string | RegExp>` for multiple exact and pattern matches
52
+ - `true` to reflect the request origin
53
+ - `(origin, context) => boolean | string` for dynamic policies
54
+
55
+ ### Restrict Origins
56
+
57
+ ```ts
58
+ let router = createRouter({
59
+ middleware: [
60
+ cors({
61
+ origin: ['https://app.example.com', 'https://admin.example.com'],
62
+ credentials: true,
63
+ }),
64
+ ],
65
+ })
66
+ ```
67
+
68
+ ### Dynamic Origin Policies
69
+
70
+ ```ts
71
+ let router = createRouter({
72
+ middleware: [
73
+ cors({
74
+ origin(origin, context) {
75
+ if (context.url.pathname.startsWith('/public/')) {
76
+ return '*'
77
+ }
78
+
79
+ return origin.endsWith('.trusted.example')
80
+ },
81
+ }),
82
+ ],
83
+ })
84
+ ```
85
+
86
+ ## Preflight Behavior
87
+
88
+ By default, preflight requests are short-circuited with status `204`.
89
+
90
+ ```ts
91
+ let router = createRouter({
92
+ middleware: [
93
+ cors({
94
+ methods: ['GET', 'POST', 'PATCH'],
95
+ allowedHeaders: ['Authorization', 'Content-Type'],
96
+ maxAge: 600,
97
+ }),
98
+ ],
99
+ })
100
+ ```
101
+
102
+ Use a function-based `allowedHeaders` policy when the header allowlist depends on the request:
103
+
104
+ ```ts
105
+ let router = createRouter({
106
+ middleware: [
107
+ cors({
108
+ allowedHeaders(request) {
109
+ let requestedHeaders = request.headers.get('Access-Control-Request-Headers')
110
+
111
+ if (requestedHeaders?.includes('x-admin-token')) {
112
+ return ['Authorization', 'Content-Type', 'X-Admin-Token']
113
+ }
114
+
115
+ return ['Authorization', 'Content-Type']
116
+ },
117
+ }),
118
+ ],
119
+ })
120
+ ```
121
+
122
+ Function-based `allowedHeaders` responses vary on `Access-Control-Request-Headers`, so caches do not reuse a preflight response for a different requested-header set.
123
+
124
+ Set `preflightContinue: true` to let downstream handlers process preflight requests. Use `preflightStatusCode` when you want short-circuited preflight responses to return a status other than `204`.
125
+
126
+ ## Private Network Preflights
127
+
128
+ ```ts
129
+ let router = createRouter({
130
+ middleware: [
131
+ cors({
132
+ allowPrivateNetwork: true,
133
+ }),
134
+ ],
135
+ })
136
+ ```
137
+
138
+ When `allowPrivateNetwork` is enabled, the middleware adds `Access-Control-Allow-Private-Network: true` for preflight requests that ask for private network access.
139
+
140
+ ## Expose Response Headers
141
+
142
+ ```ts
143
+ let router = createRouter({
144
+ middleware: [
145
+ cors({
146
+ exposedHeaders: ['X-Request-Id', 'X-Trace-Id'],
147
+ }),
148
+ ],
149
+ })
150
+ ```
151
+
152
+ ## Caveats
153
+
154
+ - CORS is primarily a browser enforcement mechanism. Disallowed non-preflight requests still reach your handlers unless you add separate request validation.
155
+ - When `credentials: true` is used with `origin: '*'`, the middleware reflects the request origin and adds `Vary: Origin` so the response stays cache-safe.
156
+ - When `allowedHeaders` is a function, preflight responses vary on `Access-Control-Request-Headers` so caches do not reuse a response for a different requested-header set.
157
+ - `preflightContinue` and `preflightStatusCode` only affect how preflight `OPTIONS` requests are handled. They do not change actual request authorization.
158
+
159
+ ## Related Packages
160
+
161
+ - [`cop-middleware`](https://github.com/remix-run/remix/tree/main/packages/cop-middleware) - Browser-origin protection middleware for unsafe cross-origin requests
162
+ - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Router for the web Fetch API
163
+ - [`headers`](https://github.com/remix-run/remix/tree/main/packages/headers) - Typed HTTP header utilities
164
+
165
+ ## Related Work
166
+
167
+ - [MDN: Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
168
+ - [Fetch Standard: CORS protocol](https://fetch.spec.whatwg.org/#http-cors-protocol)
169
+ - [expressjs/cors](https://github.com/expressjs/cors)
170
+ - [rack-cors](https://github.com/cyu/rack-cors)
171
+
172
+ ## License
173
+
174
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
@@ -0,0 +1,92 @@
1
+ # csrf-middleware
2
+
3
+ CSRF protection middleware for Remix. It provides synchronizer-token validation backed by session storage, plus origin checks for unsafe requests.
4
+
5
+ ## Features
6
+
7
+ - **Session-Backed Tokens** - Creates and persists CSRF tokens in the request session
8
+ - **Flexible Token Extraction** - Reads tokens from headers, form fields, query params, or a custom resolver
9
+ - **Origin Validation** - Validates `Origin`/`Referer` for unsafe methods with customizable policies
10
+ - **Configurable Enforcement** - Control safe methods, token keys, and failure responses
11
+
12
+ ## Installation
13
+
14
+ ```sh
15
+ npm i remix
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ This middleware requires [`session-middleware`](https://github.com/remix-run/remix/tree/main/packages/session-middleware) to run before it.
21
+
22
+ ```ts
23
+ import { createCookie } from 'remix/cookie'
24
+ import { createRouter } from 'remix/router'
25
+ import { createCookieSessionStorage } from 'remix/session-storage/cookie'
26
+ import { session } from 'remix/middleware/session'
27
+ import { csrf, getCsrfToken } from 'remix/middleware/csrf'
28
+
29
+ let sessionCookie = createCookie('__session', { secrets: ['secret1'] })
30
+ let sessionStorage = createCookieSessionStorage()
31
+
32
+ let router = createRouter({
33
+ middleware: [session(sessionCookie, sessionStorage), csrf()],
34
+ })
35
+
36
+ router.get('/form', (context) => {
37
+ let token = getCsrfToken(context)
38
+
39
+ return new Response(`
40
+ <form method="post" action="/submit">
41
+ <input type="hidden" name="_csrf" value="${token}" />
42
+ <button type="submit">Submit</button>
43
+ </form>
44
+ `)
45
+ })
46
+ ```
47
+
48
+ ## Token Sources
49
+
50
+ By default, `csrf()` checks token values in this order:
51
+
52
+ 1. Request headers: `X-Csrf-Token`, `X-Xsrf-Token`, `Csrf-Token`
53
+ 2. Form field: `_csrf` (requires `formData()` middleware to parse request bodies)
54
+ 3. Query param: `_csrf`
55
+
56
+ You can override extraction using `value(context)`.
57
+
58
+ Headers and form fields are the preferred transports. Query param fallback exists for compatibility, but it is the weakest option because tokens are more likely to be exposed in logs, history, and copied URLs.
59
+
60
+ ## Origin Validation
61
+
62
+ For unsafe methods (`POST`, `PUT`, `PATCH`, `DELETE`), the middleware validates request origin.
63
+
64
+ - Default: same-origin validation when `Origin` or `Referer` is present
65
+ - Custom: provide `origin` as string, regex, array, or function
66
+ - Missing origin behavior: controlled by `allowMissingOrigin` (default `true`)
67
+
68
+ ## Caveats
69
+
70
+ - The synchronizer token is the primary defense in `csrf()`. `Origin` and `Referer` checks are an additional signal, not the only protection.
71
+ - By default, unsafe requests with a valid token still pass when `Origin` and `Referer` are both missing. Set `allowMissingOrigin: false` if your deployment wants to require provenance headers on unsafe requests.
72
+ - Query param tokens are supported for compatibility, but they should not be the default recommendation. Prefer headers or hidden form fields when you control the client.
73
+ - If you want to reject more unsafe requests before token validation, especially when browser provenance headers are available, layer [`cop-middleware`](https://github.com/remix-run/remix/tree/main/packages/cop-middleware) in front of `csrf()`.
74
+
75
+ ## Why This Exists
76
+
77
+ Modern browsers now provide stronger cross-origin signals like `Sec-Fetch-Site`, and explicit `SameSite=Lax` cookies already block many CSRF attacks. We have considered the lighter, tokenless model used by Go's `CrossOriginProtection`, and we think it is a good fit when a deployment can make all of the guarantees that model depends on.
78
+
79
+ Remix cannot assume those guarantees for every app. `csrf()` still exists as the conservative option for apps that want synchronizer tokens in addition to origin checks, especially for session-backed HTML form workflows and mixed deployment environments.
80
+
81
+ If your deployment can guarantee the prerequisites for the tokenless model, this middleware is optional. In that case, [`cop-middleware`](https://github.com/remix-run/remix/tree/main/packages/cop-middleware) may be a better fit.
82
+
83
+ ## Related Packages
84
+
85
+ - [`cop-middleware`](https://github.com/remix-run/remix/tree/main/packages/cop-middleware) - Middleware for tokenless cross-origin protection using browser provenance headers
86
+ - [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Router for the web Fetch API
87
+ - [`session-middleware`](https://github.com/remix-run/remix/tree/main/packages/session-middleware) - Session middleware required by `csrf()`
88
+ - [`form-data-middleware`](https://github.com/remix-run/remix/tree/main/packages/form-data-middleware) - Needed for form body token extraction
89
+
90
+ ## License
91
+
92
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)