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.
- package/README.md +4 -2
- package/dist/assets/types/hmr.d.ts +2 -0
- package/dist/cli-entry.js +1 -1
- package/dist/data-table/cli.d.ts +2 -0
- package/dist/data-table/cli.d.ts.map +1 -0
- package/dist/{ui/scroll-lock.js → data-table/cli.js} +1 -1
- package/dist/fetch-router.d.ts +7 -0
- package/dist/fetch-router.d.ts.map +1 -1
- package/dist/headers/accept-encoding.d.ts +2 -0
- package/dist/headers/accept-encoding.d.ts.map +1 -0
- package/dist/headers/accept-encoding.js +2 -0
- package/dist/headers/accept-language.d.ts +2 -0
- package/dist/headers/accept-language.d.ts.map +1 -0
- package/dist/headers/accept-language.js +2 -0
- package/dist/headers/accept.d.ts +2 -0
- package/dist/headers/accept.d.ts.map +1 -0
- package/dist/headers/accept.js +2 -0
- package/dist/headers/cache-control.d.ts +2 -0
- package/dist/headers/cache-control.d.ts.map +1 -0
- package/dist/headers/cache-control.js +2 -0
- package/dist/headers/content-disposition.d.ts +2 -0
- package/dist/headers/content-disposition.d.ts.map +1 -0
- package/dist/headers/content-disposition.js +2 -0
- package/dist/headers/content-range.d.ts +2 -0
- package/dist/headers/content-range.d.ts.map +1 -0
- package/dist/headers/content-range.js +2 -0
- package/dist/headers/content-type.d.ts +2 -0
- package/dist/headers/content-type.d.ts.map +1 -0
- package/dist/headers/content-type.js +2 -0
- package/dist/headers/cookie.d.ts +2 -0
- package/dist/headers/cookie.d.ts.map +1 -0
- package/dist/headers/cookie.js +2 -0
- package/dist/headers/if-match.d.ts +2 -0
- package/dist/headers/if-match.d.ts.map +1 -0
- package/dist/headers/if-match.js +2 -0
- package/dist/headers/if-none-match.d.ts +2 -0
- package/dist/headers/if-none-match.d.ts.map +1 -0
- package/dist/headers/if-none-match.js +2 -0
- package/dist/headers/if-range.d.ts +2 -0
- package/dist/headers/if-range.d.ts.map +1 -0
- package/dist/headers/if-range.js +2 -0
- package/dist/headers/range.d.ts +2 -0
- package/dist/headers/range.d.ts.map +1 -0
- package/dist/headers/range.js +2 -0
- package/dist/headers/raw-headers.d.ts +2 -0
- package/dist/headers/raw-headers.d.ts.map +1 -0
- package/dist/headers/raw-headers.js +2 -0
- package/dist/headers/set-cookie.d.ts +2 -0
- package/dist/headers/set-cookie.d.ts.map +1 -0
- package/dist/headers/set-cookie.js +2 -0
- package/dist/headers/vary.d.ts +2 -0
- package/dist/headers/vary.d.ts.map +1 -0
- package/dist/{ui/separator.js → headers/vary.js} +1 -1
- package/dist/node-hmr/runtime.d.ts +2 -0
- package/dist/node-hmr/runtime.d.ts.map +1 -0
- package/dist/node-hmr/runtime.js +2 -0
- package/dist/node-hmr/types.d.ts +2 -0
- package/dist/node-hmr.d.ts +2 -0
- package/dist/node-hmr.d.ts.map +1 -0
- package/dist/{ui/glyph.js → node-hmr.js} +1 -1
- package/dist/node-tsx/load-module.d.ts +2 -0
- package/dist/node-tsx/load-module.d.ts.map +1 -0
- package/dist/node-tsx/load-module.js +2 -0
- package/dist/node-tsx.d.ts +3 -0
- package/dist/node-tsx.d.ts.map +1 -0
- package/dist/node-tsx.js +3 -0
- package/dist/render-middleware.d.ts +2 -0
- package/dist/render-middleware.d.ts.map +1 -0
- package/dist/render-middleware.js +2 -0
- package/dist/route-pattern/href.d.ts +2 -0
- package/dist/route-pattern/href.d.ts.map +1 -0
- package/dist/route-pattern/href.js +2 -0
- package/dist/route-pattern/join.d.ts +2 -0
- package/dist/route-pattern/join.d.ts.map +1 -0
- package/dist/route-pattern/join.js +2 -0
- package/dist/route-pattern/match.d.ts +2 -0
- package/dist/route-pattern/match.d.ts.map +1 -0
- package/dist/route-pattern/match.js +2 -0
- package/dist/ui/accordion/primitives.d.ts +2 -0
- package/dist/ui/accordion/primitives.d.ts.map +1 -0
- package/dist/ui/accordion/primitives.js +2 -0
- package/dist/ui/button.d.ts +1 -0
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +1 -0
- package/dist/ui/checkbox.d.ts +3 -0
- package/dist/ui/checkbox.d.ts.map +1 -0
- package/dist/ui/checkbox.js +3 -0
- package/dist/ui/combobox/primitives.d.ts +2 -0
- package/dist/ui/combobox/primitives.d.ts.map +1 -0
- package/dist/ui/combobox/primitives.js +2 -0
- package/dist/ui/dev/refresh.d.ts +2 -0
- package/dist/ui/dev/refresh.d.ts.map +1 -0
- package/dist/ui/dev/refresh.js +2 -0
- package/dist/ui/input.d.ts +3 -0
- package/dist/ui/input.d.ts.map +1 -0
- package/dist/ui/input.js +3 -0
- package/dist/ui/menu/primitives.d.ts +2 -0
- package/dist/ui/menu/primitives.d.ts.map +1 -0
- package/dist/ui/menu/primitives.js +2 -0
- package/dist/ui/radio.d.ts +3 -0
- package/dist/ui/radio.d.ts.map +1 -0
- package/dist/ui/radio.js +3 -0
- package/dist/ui/select/primitives.d.ts +2 -0
- package/dist/ui/select/primitives.d.ts.map +1 -0
- package/dist/ui/select/primitives.js +2 -0
- package/dist/ui/tabs/primitives.d.ts +2 -0
- package/dist/ui/tabs/primitives.d.ts.map +1 -0
- package/dist/ui/tabs/primitives.js +2 -0
- package/dist/ui/tabs.d.ts +2 -0
- package/dist/ui/tabs.d.ts.map +1 -0
- package/{src/ui/theme.ts → dist/ui/tabs.js} +1 -1
- package/dist/ui/toggle/primitives.d.ts +2 -0
- package/dist/ui/toggle/primitives.d.ts.map +1 -0
- package/dist/ui/toggle/primitives.js +2 -0
- package/dist/ui/toggle.d.ts +3 -0
- package/dist/ui/toggle.d.ts.map +1 -0
- package/dist/ui/toggle.js +3 -0
- package/dist/ui-hmr/assets.d.ts +2 -0
- package/dist/ui-hmr/assets.d.ts.map +1 -0
- package/dist/ui-hmr/assets.js +2 -0
- package/dist/ui-hmr/node.d.ts +3 -0
- package/dist/ui-hmr/node.d.ts.map +1 -0
- package/dist/ui-hmr/node.js +3 -0
- package/dist/ui-hmr/runtime/browser.d.ts +2 -0
- package/dist/ui-hmr/runtime/browser.d.ts.map +1 -0
- package/dist/ui-hmr/runtime/browser.js +2 -0
- package/dist/ui-hmr/runtime/server.d.ts +2 -0
- package/dist/ui-hmr/runtime/server.d.ts.map +1 -0
- package/dist/ui-hmr/runtime/server.js +2 -0
- package/dist/ui-hmr.d.ts +2 -0
- package/dist/ui-hmr.d.ts.map +1 -0
- package/{src/ui/glyph.ts → dist/ui-hmr.js} +1 -1
- package/package.json +284 -129
- package/src/assert/README.md +116 -0
- package/src/assets/README.md +805 -0
- package/src/assets/types/hmr.d.ts +2 -0
- package/src/async-context-middleware/README.md +100 -0
- package/src/auth/README.md +445 -0
- package/src/auth-middleware/README.md +246 -0
- package/src/cli/README.md +182 -0
- package/src/compression-middleware/README.md +176 -0
- package/src/cookie/README.md +106 -0
- package/src/cop-middleware/README.md +117 -0
- package/src/cors-middleware/README.md +174 -0
- package/src/csrf-middleware/README.md +92 -0
- package/src/data-schema/README.md +416 -0
- package/src/data-table/README.md +678 -0
- package/src/data-table/cli.ts +2 -0
- package/src/data-table-mysql/README.md +120 -0
- package/src/data-table-postgres/README.md +98 -0
- package/src/data-table-sqlite/README.md +100 -0
- package/src/fetch-proxy/README.md +70 -0
- package/src/fetch-router/README.md +982 -0
- package/src/fetch-router.ts +7 -0
- package/src/file-storage/README.md +57 -0
- package/src/file-storage-s3/README.md +46 -0
- package/src/form-data-middleware/README.md +108 -0
- package/src/form-data-parser/README.md +158 -0
- package/src/fs/README.md +60 -0
- package/src/headers/README.md +639 -0
- package/src/headers/accept-encoding.ts +2 -0
- package/src/headers/accept-language.ts +2 -0
- package/src/headers/accept.ts +2 -0
- package/src/headers/cache-control.ts +2 -0
- package/src/headers/content-disposition.ts +2 -0
- package/src/headers/content-range.ts +2 -0
- package/src/headers/content-type.ts +2 -0
- package/src/headers/cookie.ts +2 -0
- package/src/headers/if-match.ts +2 -0
- package/src/headers/if-none-match.ts +2 -0
- package/src/headers/if-range.ts +2 -0
- package/src/headers/range.ts +2 -0
- package/src/headers/raw-headers.ts +2 -0
- package/src/headers/set-cookie.ts +2 -0
- package/src/{node-serve.ts → headers/vary.ts} +1 -1
- package/src/html-template/README.md +101 -0
- package/src/lazy-file/README.md +109 -0
- package/src/logger-middleware/README.md +132 -0
- package/src/method-override-middleware/README.md +71 -0
- package/src/mime/README.md +117 -0
- package/src/multipart-parser/README.md +241 -0
- package/src/node-fetch-server/README.md +377 -0
- package/src/node-hmr/README.md +307 -0
- package/src/node-hmr/runtime.ts +2 -0
- package/src/node-hmr/types.d.ts +2 -0
- package/{dist/ui/theme.js → src/node-hmr.ts} +1 -1
- package/src/node-tsx/README.md +74 -0
- package/src/node-tsx/load-module.ts +2 -0
- package/{dist/node-serve.js → src/node-tsx.ts} +2 -1
- package/src/render-middleware/README.md +99 -0
- package/src/render-middleware.ts +2 -0
- package/src/route-pattern/README.md +424 -0
- package/src/route-pattern/href.ts +2 -0
- package/src/route-pattern/join.ts +2 -0
- package/src/route-pattern/match.ts +2 -0
- package/src/session/README.md +171 -0
- package/src/session-middleware/README.md +111 -0
- package/src/session-storage-memcache/README.md +37 -0
- package/src/session-storage-redis/README.md +36 -0
- package/src/static-middleware/README.md +89 -0
- package/src/tar-parser/README.md +74 -0
- package/src/terminal/README.md +92 -0
- package/src/test/README.md +500 -0
- package/src/ui/README.md +216 -0
- package/src/ui/accordion/README.md +202 -0
- package/src/ui/accordion/primitives/README.md +202 -0
- package/src/ui/accordion/primitives.ts +2 -0
- package/src/ui/anchor/README.md +196 -0
- package/src/ui/animation/README.md +316 -0
- package/src/ui/breadcrumbs/README.md +55 -0
- package/src/ui/button/README.md +44 -0
- package/src/ui/button.ts +1 -0
- package/src/ui/checkbox/README.md +59 -0
- package/src/ui/checkbox.ts +3 -0
- package/src/ui/combobox/README.md +194 -0
- package/src/ui/combobox/primitives/README.md +194 -0
- package/src/ui/combobox/primitives.ts +2 -0
- package/src/ui/dev/refresh.ts +2 -0
- package/src/ui/input/README.md +52 -0
- package/src/ui/input.ts +3 -0
- package/src/ui/listbox/README.md +83 -0
- package/src/ui/menu/README.md +161 -0
- package/src/ui/menu/primitives/README.md +161 -0
- package/src/ui/menu/primitives.ts +2 -0
- package/src/ui/popover/README.md +103 -0
- package/src/ui/radio/README.md +53 -0
- package/src/ui/radio.ts +3 -0
- package/src/ui/select/README.md +117 -0
- package/src/ui/select/primitives/README.md +117 -0
- package/src/ui/select/primitives.ts +2 -0
- package/src/ui/server/README.md +103 -0
- package/src/ui/tabs/README.md +141 -0
- package/src/ui/tabs/primitives/README.md +141 -0
- package/src/ui/tabs/primitives.ts +2 -0
- package/src/ui/tabs.ts +2 -0
- package/src/ui/test/README.md +198 -0
- package/src/ui/toggle/README.md +56 -0
- package/src/ui/toggle/primitives/README.md +56 -0
- package/src/ui/toggle/primitives.ts +2 -0
- package/src/ui/toggle.ts +3 -0
- package/src/ui-hmr/README.md +119 -0
- package/src/ui-hmr/assets.ts +2 -0
- package/src/ui-hmr/node.ts +3 -0
- package/src/ui-hmr/runtime/browser.ts +2 -0
- package/src/ui-hmr/runtime/server.ts +2 -0
- package/src/ui-hmr.ts +2 -0
- package/dist/node-serve.d.ts +0 -2
- package/dist/node-serve.d.ts.map +0 -1
- package/dist/ui/glyph.d.ts +0 -2
- package/dist/ui/glyph.d.ts.map +0 -1
- package/dist/ui/scroll-lock.d.ts +0 -2
- package/dist/ui/scroll-lock.d.ts.map +0 -1
- package/dist/ui/separator.d.ts +0 -2
- package/dist/ui/separator.d.ts.map +0 -1
- package/dist/ui/theme.d.ts +0 -2
- package/dist/ui/theme.d.ts.map +0 -1
- package/src/ui/scroll-lock.ts +0 -2
- package/src/ui/separator.ts +0 -2
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# auth-middleware
|
|
2
|
+
|
|
3
|
+
Request-time authentication and route protection for Remix. Use this package to resolve identity into `context.auth` from sessions, bearer tokens, API keys, or your own schemes. Pair it with [`remix/auth`](https://github.com/remix-run/remix/tree/main/packages/auth) when you need browser login routes that call `verifyCredentials()` or `finishExternalAuth()`, then rotate the session id with `completeAuth()` before writing the auth record.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Request auth resolution without mutating request objects
|
|
8
|
+
- Route protection with `requireAuth()` and configurable failure behavior
|
|
9
|
+
- Built-in auth schemes for sessions, bearer tokens, and API keys
|
|
10
|
+
- Ordered fallback across multiple auth schemes
|
|
11
|
+
- Public and private route support with the same resolved auth state
|
|
12
|
+
- Read auth state with `context.auth` (or `context.get(Auth)`)
|
|
13
|
+
- Designed to pair with browser login flows that persist session auth records earlier in the request lifecycle
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm i remix
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
The following example shows the request-time half of a session-backed browser login flow:
|
|
24
|
+
|
|
25
|
+
- another part of the app has already called `completeAuth()` and written `{ userId }` into the returned session
|
|
26
|
+
- `remix/middleware/auth` reads that value, resolves the current user, and protects the dashboard route
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { auth, createSessionAuthScheme, requireAuth } from 'remix/middleware/auth'
|
|
30
|
+
import { createRouter } from 'remix/router'
|
|
31
|
+
import { route } from 'remix/routes'
|
|
32
|
+
import { session } from 'remix/middleware/session'
|
|
33
|
+
|
|
34
|
+
let routes = route({
|
|
35
|
+
app: {
|
|
36
|
+
dashboard: '/dashboard',
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
type User = { id: string; email: string }
|
|
41
|
+
|
|
42
|
+
let router = createRouter({
|
|
43
|
+
middleware: [
|
|
44
|
+
session(sessionCookie, sessionStorage),
|
|
45
|
+
auth({
|
|
46
|
+
schemes: [
|
|
47
|
+
createSessionAuthScheme<User, { userId: string }>({
|
|
48
|
+
read(session) {
|
|
49
|
+
return session.get('auth') as { userId: string } | null
|
|
50
|
+
},
|
|
51
|
+
verify(value) {
|
|
52
|
+
return users.getById(value.userId)
|
|
53
|
+
},
|
|
54
|
+
invalidate(session) {
|
|
55
|
+
session.unset('auth')
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
],
|
|
59
|
+
}),
|
|
60
|
+
],
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
router.get(routes.app.dashboard, {
|
|
64
|
+
middleware: [requireAuth<User>()],
|
|
65
|
+
handler(context) {
|
|
66
|
+
let auth = context.auth
|
|
67
|
+
if (!auth.ok) {
|
|
68
|
+
return new Response('Unauthorized', { status: 401 })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Response.json({
|
|
72
|
+
id: auth.identity.id,
|
|
73
|
+
email: auth.identity.email,
|
|
74
|
+
method: auth.method,
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
In this example, `createSessionAuthScheme()` turns a persisted session auth record back into request auth state, `auth()` stores that state at `context.auth` (or `context.get(Auth)`), and `requireAuth()` rejects anonymous requests.
|
|
81
|
+
|
|
82
|
+
If you need to create the login route, start an OAuth redirect, finish a provider callback, or write the session auth record in the first place, use [`remix/auth`](https://github.com/remix-run/remix/tree/main/packages/auth):
|
|
83
|
+
|
|
84
|
+
- `verifyCredentials()` for direct credentials flows
|
|
85
|
+
- `startExternalAuth()` and `finishExternalAuth()` for OAuth and OIDC flows
|
|
86
|
+
- `completeAuth()` to rotate the session id before writing the auth record that this package reads later
|
|
87
|
+
|
|
88
|
+
## Route Protection
|
|
89
|
+
|
|
90
|
+
This package includes two middlewares:
|
|
91
|
+
|
|
92
|
+
- `auth()` to resolve auth state and store it in `context.auth` (or `context.get(Auth)`)
|
|
93
|
+
- `requireAuth()` to reject requests that aren't authenticated
|
|
94
|
+
|
|
95
|
+
That separation is intentional so the same auth resolution can support public routes, API routes, and browser routes with different failure behavior.
|
|
96
|
+
|
|
97
|
+
`auth()` resolves auth state and stores either `{ ok: true, identity, method }` or `{ ok: false, error? }` in `context.auth`.
|
|
98
|
+
|
|
99
|
+
Use `requireAuth()` after `auth()` when a route must be authenticated. If `auth()` did not run first, `requireAuth()` throws. Otherwise it returns `401 Unauthorized` by default, or you can replace that with `onFailure(context, auth)` to return JSON, redirects, or any other custom response.
|
|
100
|
+
|
|
101
|
+
Handlers whose context contract includes `auth()` and `requireAuth<Identity>()` can read `context.auth.identity` without a manual lookup or cast. You can also use `context.get(Auth)`.
|
|
102
|
+
|
|
103
|
+
Auth challenges are forwarded to `WWW-Authenticate` automatically when the auth failure included a `challenge`, so clients that honor those challenges can react without custom header handling.
|
|
104
|
+
|
|
105
|
+
## Auth Schemes
|
|
106
|
+
|
|
107
|
+
An `AuthScheme` is any object with a `name` and an `authenticate(context)` method. The `auth()` middleware tries each scheme in order until one returns a success or failure result. If no scheme returns success or failure, the request is treated as anonymous.
|
|
108
|
+
|
|
109
|
+
This package ships with three built-in auth schemes:
|
|
110
|
+
|
|
111
|
+
- `createBearerTokenAuthScheme()` for bearer tokens in the [HTTP `Authorization: Bearer <token>` header](https://datatracker.ietf.org/doc/html/rfc6750#section-2.1)
|
|
112
|
+
- `createAPIAuthScheme()` for API keys in a custom request header
|
|
113
|
+
- `createSessionAuthScheme()` for session-backed auth loaded by [a `session()` middleware](https://github.com/remix-run/remix/tree/main/packages/session-middleware)
|
|
114
|
+
|
|
115
|
+
## Custom Auth Schemes
|
|
116
|
+
|
|
117
|
+
If none of the built-in auth schemes match your environment, you can create your own auth scheme easily. A custom scheme usually wraps one auth mechanism behind a small `create*` factory function and returns an `AuthScheme`. For example, apps behind a trusted access proxy can authenticate requests from forwarded identity headers instead of sessions or bearer tokens.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import type { RequestContext } from 'remix/router'
|
|
121
|
+
import type { AuthScheme } from 'remix/middleware/auth'
|
|
122
|
+
|
|
123
|
+
type User = {
|
|
124
|
+
id: string
|
|
125
|
+
role: 'admin' | 'user'
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function createTrustedProxyAuthScheme(): AuthScheme<User> {
|
|
129
|
+
return {
|
|
130
|
+
name: 'trusted-proxy',
|
|
131
|
+
async authenticate(context: RequestContext) {
|
|
132
|
+
let email = context.headers.get('X-Forwarded-Email')
|
|
133
|
+
|
|
134
|
+
if (email == null) {
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let user = await users.getByEmail(email)
|
|
139
|
+
|
|
140
|
+
if (user == null) {
|
|
141
|
+
return {
|
|
142
|
+
status: 'failure',
|
|
143
|
+
code: 'invalid_credentials',
|
|
144
|
+
message: 'Unknown forwarded user',
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
status: 'success',
|
|
150
|
+
identity: user,
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Only use a scheme like this when the app is reachable exclusively through infrastructure you trust to set the headers you rely on. In this case, the `X-Forwarded-Email` header.
|
|
158
|
+
|
|
159
|
+
`authenticate(context)` can return:
|
|
160
|
+
|
|
161
|
+
- `null`, `undefined`, or no return value to skip this scheme
|
|
162
|
+
- `{ status: 'success', identity }` to authenticate the request
|
|
163
|
+
- `{ status: 'failure', code?, message?, challenge? }` to stop with an auth error
|
|
164
|
+
|
|
165
|
+
The scheme `name` becomes `auth.method` when authentication succeeds.
|
|
166
|
+
|
|
167
|
+
## Simple Auth Cookies
|
|
168
|
+
|
|
169
|
+
If your app already has an auth cookie and you do not need a session-backed identity lookup, you can use a small custom auth scheme and still rely on `requireAuth()` for route protection.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { auth, requireAuth } from 'remix/middleware/auth'
|
|
173
|
+
import type { AuthScheme } from 'remix/middleware/auth'
|
|
174
|
+
import { createCookie } from 'remix/cookie'
|
|
175
|
+
import { createRouter } from 'remix/router'
|
|
176
|
+
import { redirect } from 'remix/response/redirect'
|
|
177
|
+
|
|
178
|
+
let authCookie = createCookie('__auth', {
|
|
179
|
+
httpOnly: true,
|
|
180
|
+
sameSite: 'lax',
|
|
181
|
+
path: '/',
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
let authCookieScheme: AuthScheme<'demo-user'> = {
|
|
185
|
+
name: 'auth-cookie',
|
|
186
|
+
async authenticate(context) {
|
|
187
|
+
let value = await authCookie.parse(context.headers.get('Cookie'))
|
|
188
|
+
if (value !== '1') {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
status: 'success',
|
|
194
|
+
identity: 'demo-user',
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let requireAuthCookie = requireAuth<'demo-user'>({
|
|
200
|
+
onFailure(context) {
|
|
201
|
+
let isFrameRequest = context.request.headers.get('X-Remix-Frame') === 'true'
|
|
202
|
+
if (isFrameRequest) {
|
|
203
|
+
return new Response('<p>Not authorized</p>', {
|
|
204
|
+
status: 401,
|
|
205
|
+
headers: {
|
|
206
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return redirect('/login')
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
let router = createRouter({
|
|
216
|
+
middleware: [
|
|
217
|
+
auth({
|
|
218
|
+
schemes: [authCookieScheme],
|
|
219
|
+
}),
|
|
220
|
+
],
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
router.get('/dashboard', {
|
|
224
|
+
middleware: [requireAuthCookie],
|
|
225
|
+
handler() {
|
|
226
|
+
return new Response('ok')
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
This pattern keeps the auth check app-owned. Use [`remix/middleware/session`](https://github.com/remix-run/remix/tree/main/packages/session-middleware) and [`remix/auth`](https://github.com/remix-run/remix/tree/main/packages/auth) when you need server-managed session data, credential verification helpers, or OAuth/OIDC flows.
|
|
232
|
+
|
|
233
|
+
## Related Packages
|
|
234
|
+
|
|
235
|
+
- [`auth`](https://github.com/remix-run/remix/tree/main/packages/auth) - Browser auth primitives for credentials, OAuth, and OIDC flows
|
|
236
|
+
- [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Router and middleware runtime
|
|
237
|
+
- [`response`](https://github.com/remix-run/remix/tree/main/packages/response) - Response helpers like redirects
|
|
238
|
+
|
|
239
|
+
## Related Work
|
|
240
|
+
|
|
241
|
+
- [HTTP Authentication Framework](https://datatracker.ietf.org/doc/html/rfc7235)
|
|
242
|
+
- [OAuth 2.0 Bearer Token Usage](https://datatracker.ietf.org/doc/html/rfc6750)
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for creating and managing Remix projects.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Create new Remix projects with `npx remix@next new` or installed `remix new`
|
|
8
|
+
- Print shell completion scripts with `remix completion`
|
|
9
|
+
- Check project environment and Remix app conventions with `remix doctor`
|
|
10
|
+
- Apply available low-risk project fixes with `remix doctor --fix`
|
|
11
|
+
- Manage the current app database with `remix db`
|
|
12
|
+
- Inspect the current app route tree with `remix routes`
|
|
13
|
+
- Run project tests with `remix test`
|
|
14
|
+
- Configure commands with a static, commented `remix.json` file
|
|
15
|
+
- Print the current Remix version with `remix version`
|
|
16
|
+
- Use the same CLI through the `remix` package or the `remix/cli` API
|
|
17
|
+
- Scaffold a starter app that matches the Remix project layout conventions
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Use `npx remix@next new <target-dir>` to scaffold a new Remix app. Install `remix` when you want the local `remix` command:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm i remix
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Shell completion
|
|
28
|
+
|
|
29
|
+
Install bash completion:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
remix completion bash >> ~/.bashrc
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Install zsh completion:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
remix completion zsh >> ~/.zshrc
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
Use `npx remix@next new my-remix-app` to scaffold a new Remix app. After installing Remix, the equivalent local command is `remix new my-remix-app`.
|
|
44
|
+
|
|
45
|
+
The rest of the CLI is available through the installed `remix` command:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
remix new my-remix-app
|
|
49
|
+
remix completion bash >> ~/.bashrc
|
|
50
|
+
remix doctor
|
|
51
|
+
remix doctor --fix
|
|
52
|
+
remix db migrate
|
|
53
|
+
remix db status
|
|
54
|
+
remix db reset --force
|
|
55
|
+
remix routes
|
|
56
|
+
remix routes --table
|
|
57
|
+
remix routes --table --no-headers
|
|
58
|
+
remix test
|
|
59
|
+
remix version
|
|
60
|
+
remix --no-color doctor
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
You can also run the CLI programmatically:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { runRemix } from 'remix/cli'
|
|
67
|
+
|
|
68
|
+
await runRemix(['new', 'my-remix-app'])
|
|
69
|
+
await runRemix(['completion', 'bash'])
|
|
70
|
+
await runRemix(['doctor'])
|
|
71
|
+
await runRemix(['doctor', '--fix'])
|
|
72
|
+
await runRemix(['db', 'migrate'])
|
|
73
|
+
await runRemix(['db', 'status'])
|
|
74
|
+
await runRemix(['db', 'reset', '--force'])
|
|
75
|
+
await runRemix(['routes'])
|
|
76
|
+
await runRemix(['routes', '--table'])
|
|
77
|
+
await runRemix(['routes', '--table', '--no-headers'])
|
|
78
|
+
await runRemix(['test'])
|
|
79
|
+
await runRemix(['version'])
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Destructive database commands (`remix db wipe` and `remix db reset`) refuse to run without `--force`.
|
|
83
|
+
|
|
84
|
+
`runRemix()` returns the CLI exit code as a promise.
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
The CLI loads an optional `remix.json`. The file is parsed as JSONC, so it may contain comments and
|
|
89
|
+
trailing commas. Every top-level field is optional:
|
|
90
|
+
|
|
91
|
+
```jsonc
|
|
92
|
+
{
|
|
93
|
+
"$schema": "https://remix.run/schemas/remix.json",
|
|
94
|
+
|
|
95
|
+
"db": {
|
|
96
|
+
"adapter": {
|
|
97
|
+
"type": "sqlite",
|
|
98
|
+
"filename": { "env": "DATABASE_URL", "default": "./db/app.sqlite" },
|
|
99
|
+
"foreignKeys": true,
|
|
100
|
+
"busyTimeout": 5000,
|
|
101
|
+
},
|
|
102
|
+
"migrations": {
|
|
103
|
+
"directory": "./db/migrations",
|
|
104
|
+
"journalTable": "data_table_migrations",
|
|
105
|
+
},
|
|
106
|
+
"seed": "./db/seed.sql",
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
"doctor": {
|
|
110
|
+
"strict": true,
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
"test": {
|
|
114
|
+
// Test discovery
|
|
115
|
+
"files": ["**/*.test{,.browser,.e2e}.{ts,tsx}"],
|
|
116
|
+
"browserFiles": ["**/*.test.browser.{ts,tsx}"],
|
|
117
|
+
"e2eFiles": ["**/*.test.e2e.{ts,tsx}"],
|
|
118
|
+
"exclude": ["node_modules/**", "dist/**"],
|
|
119
|
+
"type": ["server", "browser", "e2e"],
|
|
120
|
+
"only": ["/checkout/i"],
|
|
121
|
+
|
|
122
|
+
// Test execution
|
|
123
|
+
"concurrency": 4,
|
|
124
|
+
"pool": "forks",
|
|
125
|
+
"setup": "./test/setup.ts",
|
|
126
|
+
"watch": false,
|
|
127
|
+
|
|
128
|
+
// Playwright
|
|
129
|
+
"playwright": {
|
|
130
|
+
"echo": false,
|
|
131
|
+
"open": false,
|
|
132
|
+
"configFile": "./playwright.config.ts",
|
|
133
|
+
"projects": ["chromium", "firefox"],
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
// Output
|
|
137
|
+
"reporter": "spec",
|
|
138
|
+
"quiet": false,
|
|
139
|
+
|
|
140
|
+
// Coverage
|
|
141
|
+
"coverage": {
|
|
142
|
+
"enabled": true,
|
|
143
|
+
"dir": ".coverage",
|
|
144
|
+
"include": ["app/**"],
|
|
145
|
+
"exclude": ["**/*.test.*"],
|
|
146
|
+
"branches": 80,
|
|
147
|
+
"functions": 80,
|
|
148
|
+
"lines": 80,
|
|
149
|
+
"statements": 80,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Explicit command flags and positional arguments override configured values. Repeated flags replace
|
|
156
|
+
configured arrays, while nested Playwright and coverage settings merge by field. Relative paths and
|
|
157
|
+
globs are resolved from the directory containing the config file. Use `remix doctor --no-strict` to
|
|
158
|
+
disable configured strict mode for one run.
|
|
159
|
+
|
|
160
|
+
`remix db` requires `db.adapter`. Adapters use `type: "sqlite"`, `type: "postgres"`, or
|
|
161
|
+
`type: "mysql"`; PostgreSQL uses `connectionString` and MySQL uses `uri`. A connection value may be
|
|
162
|
+
a string or an object naming an environment variable with an optional default. `db.seed` names a
|
|
163
|
+
SQL file that `remix db seed` and `remix db reset` run against the database. Database flags such as
|
|
164
|
+
`--migrations`, `--seed`, `--journal-table`, and `--connection-env` override the corresponding
|
|
165
|
+
config for one invocation. When no global `--config` is provided, database commands find the
|
|
166
|
+
nearest `remix.json` by walking up from the working directory.
|
|
167
|
+
|
|
168
|
+
Use the global `--config` option to select another JSONC file. The option itself is resolved from the
|
|
169
|
+
CLI working directory and may appear before or after the command:
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
remix --config ./config/remix.ci.json test
|
|
173
|
+
remix test --config ./config/remix.ci.json
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
A missing default `remix.json` is ignored. A missing explicitly selected file, malformed JSONC,
|
|
177
|
+
unknown property, or invalid value is reported as a CLI error. The optional `$schema` field enables
|
|
178
|
+
editor completion and validation; it has no runtime effect.
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# compression-middleware
|
|
2
|
+
|
|
3
|
+
Response compression middleware for Remix. It negotiates `br`, `gzip`, and `deflate` from `Accept-Encoding` and applies sensible defaults for when compression is useful.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Encoding Negotiation** - Selects the best supported encoding from `Accept-Encoding`
|
|
8
|
+
- **Compression Guards** - Skips already-compressed responses and range-enabled responses
|
|
9
|
+
- **Size Thresholds** - Configurable minimum response size for compression
|
|
10
|
+
- **MIME Filtering** - Compresses only content types likely to benefit
|
|
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 { compression } from 'remix/middleware/compression'
|
|
23
|
+
|
|
24
|
+
let router = createRouter({
|
|
25
|
+
middleware: [compression()],
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The middleware will automatically compress responses for compressible MIME types when:
|
|
30
|
+
|
|
31
|
+
- The client supports compression (`Accept-Encoding` header with a supported encoding)
|
|
32
|
+
- The response is large enough to benefit from compression (≥1024 bytes if `Content-Length` is present, by default)
|
|
33
|
+
- The response hasn't already been compressed
|
|
34
|
+
- The response doesn't advertise range support (`Accept-Ranges: bytes`)
|
|
35
|
+
|
|
36
|
+
### Threshold
|
|
37
|
+
|
|
38
|
+
**Default:** `1024` (only enforced if `Content-Length` is present)
|
|
39
|
+
|
|
40
|
+
Set the minimum response size in bytes to compress:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { createRouter } from 'remix/router'
|
|
44
|
+
import { compression } from 'remix/middleware/compression'
|
|
45
|
+
|
|
46
|
+
let router = createRouter({
|
|
47
|
+
middleware: [
|
|
48
|
+
compression({
|
|
49
|
+
threshold: 2048, // Only compress responses ≥2KB
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Encodings
|
|
56
|
+
|
|
57
|
+
**Default:** `['br', 'gzip', 'deflate']`
|
|
58
|
+
|
|
59
|
+
Customize which compression algorithms to support:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { createRouter } from 'remix/router'
|
|
63
|
+
import { compression } from 'remix/middleware/compression'
|
|
64
|
+
|
|
65
|
+
let router = createRouter({
|
|
66
|
+
middleware: [
|
|
67
|
+
compression({
|
|
68
|
+
encodings: ['br', 'gzip'], // Only use Brotli and Gzip
|
|
69
|
+
}),
|
|
70
|
+
],
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The `encodings` option can also be a function that receives the response:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { createRouter } from 'remix/router'
|
|
78
|
+
import { compression } from 'remix/middleware/compression'
|
|
79
|
+
|
|
80
|
+
let router = createRouter({
|
|
81
|
+
middleware: [
|
|
82
|
+
compression({
|
|
83
|
+
encodings: (response) => {
|
|
84
|
+
// Use different encodings for server-sent events
|
|
85
|
+
let contentType = response.headers.get('Content-Type')
|
|
86
|
+
return contentType?.startsWith('text/event-stream;')
|
|
87
|
+
? ['gzip', 'deflate']
|
|
88
|
+
: ['br', 'gzip', 'deflate']
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
],
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Filter Media Type
|
|
96
|
+
|
|
97
|
+
**Default:** Uses `isCompressibleMimeType()` from [`remix/mime`](https://github.com/remix-run/remix/tree/main/packages/mime)
|
|
98
|
+
|
|
99
|
+
You can customize this behavior with the `filterMediaType` option:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { createRouter } from 'remix/router'
|
|
103
|
+
import { compression } from 'remix/middleware/compression'
|
|
104
|
+
import { isCompressibleMimeType } from 'remix/mime'
|
|
105
|
+
|
|
106
|
+
let router = createRouter({
|
|
107
|
+
middleware: [
|
|
108
|
+
compression({
|
|
109
|
+
filterMediaType(mediaType) {
|
|
110
|
+
// Add a custom media type to the default compressible list
|
|
111
|
+
return isCompressibleMimeType(mediaType) || mediaType === 'application/vnd.example+data'
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
],
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Compression Options
|
|
119
|
+
|
|
120
|
+
**Default:** Uses Node.js defaults for [zlib](https://nodejs.org/api/zlib.html#class-options) and [Brotli](https://nodejs.org/api/zlib.html#class-brotlioptions), with automatic flush handling for server-sent events.
|
|
121
|
+
|
|
122
|
+
You can pass options options to the underlying Node.js `zlib` and `brotli` compressors for fine-grained control:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { createRouter } from 'remix/router'
|
|
126
|
+
import { compression } from 'remix/middleware/compression'
|
|
127
|
+
import { zlib } from 'node:zlib'
|
|
128
|
+
|
|
129
|
+
let router = createRouter({
|
|
130
|
+
middleware: [
|
|
131
|
+
compression({
|
|
132
|
+
zlib: {
|
|
133
|
+
level: 6,
|
|
134
|
+
},
|
|
135
|
+
brotli: {
|
|
136
|
+
params: {
|
|
137
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: 4,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
],
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Like `encodings`, both `zlib` and `brotli` options can also be functions that receive the response:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import zlib from 'node:zlib'
|
|
149
|
+
import { createRouter } from 'remix/router'
|
|
150
|
+
import { compression } from 'remix/middleware/compression'
|
|
151
|
+
|
|
152
|
+
let router = createRouter({
|
|
153
|
+
middleware: [
|
|
154
|
+
compression({
|
|
155
|
+
brotli: (response) => {
|
|
156
|
+
let contentType = response.headers.get('Content-Type')
|
|
157
|
+
return {
|
|
158
|
+
params: {
|
|
159
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: contentType?.startsWith('text/html;') ? 4 : 11,
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
],
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Related Packages
|
|
169
|
+
|
|
170
|
+
- [`remix/router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Router for the web Fetch API
|
|
171
|
+
- [`remix/mime`](https://github.com/remix-run/remix/tree/main/packages/mime) - MIME type utilities
|
|
172
|
+
- [`remix/response`](https://github.com/remix-run/remix/tree/main/packages/response) - Response helpers
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|