create-acrylblends 0.0.0-stage → 0.1.1
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 +61 -2
- package/bin/create-acrylblends.js +4 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +274 -0
- package/dist/index.js.map +1 -0
- package/docs/framework/LICENSE +23 -0
- package/docs/framework/README.md +63 -0
- package/docs/framework/UPSTREAM.md +3 -0
- package/docs/framework/agents/index.md +24 -0
- package/docs/framework/api/acryl.md +259 -0
- package/docs/framework/api/context.md +527 -0
- package/docs/framework/api/exception.md +80 -0
- package/docs/framework/api/index.md +12 -0
- package/docs/framework/api/presets.md +65 -0
- package/docs/framework/api/request.md +396 -0
- package/docs/framework/api/routing.md +303 -0
- package/docs/framework/api/summary.md +140 -0
- package/docs/framework/concepts/benchmarks.md +26 -0
- package/docs/framework/concepts/developer-experience.md +5 -0
- package/docs/framework/concepts/middleware.md +21 -0
- package/docs/framework/concepts/motivation.md +14 -0
- package/docs/framework/concepts/routers.md +63 -0
- package/docs/framework/concepts/stacks.md +235 -0
- package/docs/framework/concepts/web-standard.md +37 -0
- package/docs/framework/cordis/acryl-in-a-cordis-app.md +146 -0
- package/docs/framework/cordis/concepts.md +105 -0
- package/docs/framework/cordis/middleware.md +8 -0
- package/docs/framework/cordis/migration-from-0-3.md +111 -0
- package/docs/framework/cordis/migration-from-0-4.md +14 -0
- package/docs/framework/cordis/migration-from-dsh-webserver.md +109 -0
- package/docs/framework/cordis/mounting.md +85 -0
- package/docs/framework/cordis/node-server.md +75 -0
- package/docs/framework/cordis/plugin-lifecycle.md +111 -0
- package/docs/framework/cordis/surfaces.md +71 -0
- package/docs/framework/cordis/transport-and-cancellation.md +57 -0
- package/docs/framework/cordis/validation.md +135 -0
- package/docs/framework/getting-started/basic.md +196 -0
- package/docs/framework/getting-started/bun.md +221 -0
- package/docs/framework/getting-started/nodejs.md +344 -0
- package/docs/framework/getting-started/other-runtimes.md +23 -0
- package/docs/framework/guides/best-practices.md +231 -0
- package/docs/framework/guides/create-acrylblends.md +79 -0
- package/docs/framework/guides/examples.md +3 -0
- package/docs/framework/guides/faq.md +19 -0
- package/docs/framework/guides/helpers.md +33 -0
- package/docs/framework/guides/jsx-dom.md +299 -0
- package/docs/framework/guides/jsx.md +440 -0
- package/docs/framework/guides/middleware.md +253 -0
- package/docs/framework/guides/rpc.md +886 -0
- package/docs/framework/guides/testing.md +133 -0
- package/docs/framework/guides/validation.md +383 -0
- package/docs/framework/helpers/accepts.md +60 -0
- package/docs/framework/helpers/adapter.md +87 -0
- package/docs/framework/helpers/conninfo.md +112 -0
- package/docs/framework/helpers/cookie.md +232 -0
- package/docs/framework/helpers/css.md +285 -0
- package/docs/framework/helpers/dev.md +67 -0
- package/docs/framework/helpers/factory.md +180 -0
- package/docs/framework/helpers/html.md +123 -0
- package/docs/framework/helpers/jwt.md +184 -0
- package/docs/framework/helpers/proxy.md +118 -0
- package/docs/framework/helpers/route.md +143 -0
- package/docs/framework/helpers/ssg.md +393 -0
- package/docs/framework/helpers/streaming.md +119 -0
- package/docs/framework/helpers/testing.md +101 -0
- package/docs/framework/helpers/websocket.md +215 -0
- package/docs/framework/middleware/builtin/basic-auth.md +165 -0
- package/docs/framework/middleware/builtin/bearer-auth.md +138 -0
- package/docs/framework/middleware/builtin/body-limit.md +71 -0
- package/docs/framework/middleware/builtin/cache.md +122 -0
- package/docs/framework/middleware/builtin/combine.md +94 -0
- package/docs/framework/middleware/builtin/compress.md +59 -0
- package/docs/framework/middleware/builtin/context-storage.md +78 -0
- package/docs/framework/middleware/builtin/cors.md +135 -0
- package/docs/framework/middleware/builtin/csrf.md +105 -0
- package/docs/framework/middleware/builtin/etag.md +54 -0
- package/docs/framework/middleware/builtin/ip-restriction.md +69 -0
- package/docs/framework/middleware/builtin/jsx-renderer.md +286 -0
- package/docs/framework/middleware/builtin/jwk.md +192 -0
- package/docs/framework/middleware/builtin/jwt.md +145 -0
- package/docs/framework/middleware/builtin/language.md +232 -0
- package/docs/framework/middleware/builtin/logger.md +74 -0
- package/docs/framework/middleware/builtin/loopback-only.md +31 -0
- package/docs/framework/middleware/builtin/method-not-allowed.md +48 -0
- package/docs/framework/middleware/builtin/method-override.md +80 -0
- package/docs/framework/middleware/builtin/mount.md +106 -0
- package/docs/framework/middleware/builtin/pretty-json.md +53 -0
- package/docs/framework/middleware/builtin/request-id.md +94 -0
- package/docs/framework/middleware/builtin/secure-headers.md +247 -0
- package/docs/framework/middleware/builtin/timeout.md +98 -0
- package/docs/framework/middleware/builtin/timing.md +101 -0
- package/docs/framework/middleware/builtin/trailing-slash.md +74 -0
- package/docs/framework/middleware/third-party.md +4 -0
- package/docs/framework/upstream.json +9 -0
- package/package.json +47 -3
- package/templates/bun/gitignore +1 -0
- package/templates/bun/package.json +19 -0
- package/templates/bun/src/index.ts +7 -0
- package/templates/bun/test/app.test.ts +8 -0
- package/templates/bun/tsconfig.json +15 -0
- package/templates/nodejs/gitignore +1 -0
- package/templates/nodejs/package.json +22 -0
- package/templates/nodejs/src/app.ts +7 -0
- package/templates/nodejs/src/index.ts +9 -0
- package/templates/nodejs/test/app.test.ts +9 -0
- package/templates/nodejs/tsconfig.json +16 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Testing Helper
|
|
2
|
+
|
|
3
|
+
The Testing Helper provides functions to make testing of Acryl applications easier.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Acryl } from 'acrylblends'
|
|
9
|
+
import { testClient } from 'acrylblends/testing'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## `testClient()`
|
|
13
|
+
|
|
14
|
+
The `testClient()` function takes an instance of Acryl as its first argument and returns an object typed according to your Acryl application's routes, similar to the [Acryl Client](../guides/rpc.md#client). This allows you to call your defined routes in a type-safe manner with editor autocompletion within your tests.
|
|
15
|
+
|
|
16
|
+
**Important Note on Type Inference:**
|
|
17
|
+
|
|
18
|
+
For the `testClient` to correctly infer the types of your routes and provide autocompletion, **you must define your routes using chained methods directly on the `Acryl` instance**.
|
|
19
|
+
|
|
20
|
+
The type inference relies on the type flowing through the chained `.get()`, `.post()`, etc., calls. If you define routes separately after creating the Acryl instance (like the common pattern shown in the "Hello World" example: `const app = new Acryl(); app.get(...)`), the `testClient` will not have the necessary type information for specific routes, and you won't get the type-safe client features.
|
|
21
|
+
|
|
22
|
+
**Example:**
|
|
23
|
+
|
|
24
|
+
This example works because the `.get()` method is chained directly onto the `new Acryl()` call:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// index.ts
|
|
28
|
+
const app = new Acryl().get('/search', (c) => {
|
|
29
|
+
const query = c.req.query('q')
|
|
30
|
+
return c.json({ query: query, results: ['result1', 'result2'] })
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export default app
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
// index.test.ts
|
|
38
|
+
import { Acryl } from 'acrylblends'
|
|
39
|
+
import { testClient } from 'acrylblends/testing'
|
|
40
|
+
import { describe, it, expect } from 'vitest' // Or your preferred test runner
|
|
41
|
+
import app from './app'
|
|
42
|
+
|
|
43
|
+
describe('Search Endpoint', () => {
|
|
44
|
+
// Create the test client from the app instance
|
|
45
|
+
const client = testClient(app)
|
|
46
|
+
|
|
47
|
+
it('should return search results', async () => {
|
|
48
|
+
// Call the endpoint using the typed client
|
|
49
|
+
// Notice the type safety for query parameters (if defined in the route)
|
|
50
|
+
// and the direct access via .$get()
|
|
51
|
+
const res = await client.search.$get({
|
|
52
|
+
query: { q: 'acrylblends' },
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// Assertions
|
|
56
|
+
expect(res.status).toBe(200)
|
|
57
|
+
expect(await res.json()).toEqual({
|
|
58
|
+
query: 'acrylblends',
|
|
59
|
+
results: ['result1', 'result2'],
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
To include headers in your test, pass them as the second parameter in the call. The second parameter can also take an `init` property as a `RequestInit` object, allowing you to set headers, method, body, etc. Learn more about the `init` property [here](../guides/rpc.md#init-option).
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// index.test.ts
|
|
69
|
+
import { Acryl } from 'acrylblends'
|
|
70
|
+
import { testClient } from 'acrylblends/testing'
|
|
71
|
+
import { describe, it, expect } from 'vitest' // Or your preferred test runner
|
|
72
|
+
import app from './app'
|
|
73
|
+
|
|
74
|
+
describe('Search Endpoint', () => {
|
|
75
|
+
// Create the test client from the app instance
|
|
76
|
+
const client = testClient(app)
|
|
77
|
+
|
|
78
|
+
it('should return search results', async () => {
|
|
79
|
+
// Include the token in the headers and set the content type
|
|
80
|
+
const token = process.env.API_TOKEN!
|
|
81
|
+
const res = await client.search.$get(
|
|
82
|
+
{
|
|
83
|
+
query: { q: 'acrylblends' },
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
headers: {
|
|
87
|
+
Authorization: `Bearer ${token}`,
|
|
88
|
+
'Content-Type': `application/json`,
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
// Assertions
|
|
94
|
+
expect(res.status).toBe(200)
|
|
95
|
+
expect(await res.json()).toEqual({
|
|
96
|
+
query: 'acrylblends',
|
|
97
|
+
results: ['result1', 'result2'],
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
```
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# WebSocket Helper
|
|
2
|
+
|
|
3
|
+
WebSocket Helper is a helper for server-side WebSockets in Acryl applications.
|
|
4
|
+
The Node.js adapter is available; the others are not yet available.
|
|
5
|
+
|
|
6
|
+
## Import
|
|
7
|
+
|
|
8
|
+
Only the Node.js adapter, `acrylblends/node-server`, is available today. The adapters for the other platforms (Cloudflare Workers, Bun, Vercel, AWS Lambda, Netlify and Lambda@Edge) are **not yet available**; their imports are shown for completeness and are not checked by the docs tests.
|
|
9
|
+
|
|
10
|
+
**Cloudflare Workers**
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { Acryl } from 'acrylblends'
|
|
14
|
+
import { upgradeWebSocket } from 'acrylblends/cloudflare-workers'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Bun**
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { Acryl } from 'acrylblends'
|
|
21
|
+
import { upgradeWebSocket, websocket } from 'acrylblends/bun'
|
|
22
|
+
|
|
23
|
+
// ...
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
fetch: app.fetch,
|
|
27
|
+
websocket,
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Node.js**
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { serve, upgradeWebSocket } from 'acrylblends/node-server'
|
|
35
|
+
import { Acryl } from 'acrylblends'
|
|
36
|
+
import { WebSocketServer } from 'ws'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
On Node.js, WebSocket support is built into `acrylblends/node-server`. To enable it, install `ws` and, if you use TypeScript, `@types/ws`. Then create a `WebSocketServer` with `{ noServer: true }` and pass it to `serve()` via the `websocket` option.
|
|
40
|
+
|
|
41
|
+
## `upgradeWebSocket()`
|
|
42
|
+
|
|
43
|
+
`upgradeWebSocket()` returns a handler for handling WebSocket.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const app = new Acryl()
|
|
47
|
+
|
|
48
|
+
app.get(
|
|
49
|
+
'/ws',
|
|
50
|
+
upgradeWebSocket((c) => {
|
|
51
|
+
return {
|
|
52
|
+
onMessage(event, ws) {
|
|
53
|
+
console.log(`Message from client: ${event.data}`)
|
|
54
|
+
ws.send('Hello from server!')
|
|
55
|
+
},
|
|
56
|
+
onClose: () => {
|
|
57
|
+
console.log('Connection closed')
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Available events:
|
|
65
|
+
|
|
66
|
+
- `onOpen` - Currently, Cloudflare Workers does not support it.
|
|
67
|
+
- `onMessage`
|
|
68
|
+
- `onClose`
|
|
69
|
+
- `onError`
|
|
70
|
+
|
|
71
|
+
> **Warning.**
|
|
72
|
+
> If you use middleware that modifies headers (e.g., applying CORS) on a route that uses WebSocket Helper, you may encounter an error saying you can't modify immutable headers. This is because `upgradeWebSocket()` also changes headers internally.
|
|
73
|
+
>
|
|
74
|
+
> Therefore, please be cautious if you are using WebSocket Helper and middleware at the same time.
|
|
75
|
+
>
|
|
76
|
+
|
|
77
|
+
## RPC-mode
|
|
78
|
+
|
|
79
|
+
Handlers defined with WebSocket Helper support RPC mode.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// server.ts
|
|
83
|
+
const wsApp = app.get(
|
|
84
|
+
'/ws',
|
|
85
|
+
upgradeWebSocket((c) => {
|
|
86
|
+
//...
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
export type WebSocketApp = typeof wsApp
|
|
91
|
+
|
|
92
|
+
// client.ts
|
|
93
|
+
const client = hc<WebSocketApp>('http://localhost:8787')
|
|
94
|
+
const socket = client.ws.$ws() // A WebSocket object for a client
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Examples
|
|
98
|
+
|
|
99
|
+
See the examples using WebSocket Helper.
|
|
100
|
+
|
|
101
|
+
### Server and Client
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// server.ts
|
|
105
|
+
import { Acryl } from 'acrylblends'
|
|
106
|
+
import { upgradeWebSocket } from 'acrylblends/cloudflare-workers'
|
|
107
|
+
|
|
108
|
+
const app = new Acryl().get(
|
|
109
|
+
'/ws',
|
|
110
|
+
upgradeWebSocket(() => {
|
|
111
|
+
return {
|
|
112
|
+
onMessage: (event) => {
|
|
113
|
+
console.log(event.data)
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
export default app
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// client.ts
|
|
124
|
+
import { hc } from 'acrylblends/client'
|
|
125
|
+
import type app from './server'
|
|
126
|
+
|
|
127
|
+
const client = hc<typeof app>('http://localhost:8787')
|
|
128
|
+
const ws = client.ws.$ws(0)
|
|
129
|
+
|
|
130
|
+
ws.addEventListener('open', () => {
|
|
131
|
+
setInterval(() => {
|
|
132
|
+
ws.send(new Date().toString())
|
|
133
|
+
}, 1000)
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Bun with JSX
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { Acryl } from 'acrylblends'
|
|
141
|
+
import { upgradeWebSocket, websocket } from 'acrylblends/bun'
|
|
142
|
+
import { html } from 'acrylblends/html'
|
|
143
|
+
|
|
144
|
+
const app = new Acryl()
|
|
145
|
+
|
|
146
|
+
app.get('/', (c) => {
|
|
147
|
+
return c.html(
|
|
148
|
+
<html>
|
|
149
|
+
<head>
|
|
150
|
+
<meta charset='UTF-8' />
|
|
151
|
+
</head>
|
|
152
|
+
<body>
|
|
153
|
+
<div id='now-time'></div>
|
|
154
|
+
{html`
|
|
155
|
+
<script>
|
|
156
|
+
const ws = new WebSocket('ws://localhost:3000/ws')
|
|
157
|
+
const $nowTime = document.getElementById('now-time')
|
|
158
|
+
ws.onmessage = (event) => {
|
|
159
|
+
$nowTime.textContent = event.data
|
|
160
|
+
}
|
|
161
|
+
</script>
|
|
162
|
+
`}
|
|
163
|
+
</body>
|
|
164
|
+
</html>
|
|
165
|
+
)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const ws = app.get(
|
|
169
|
+
'/ws',
|
|
170
|
+
upgradeWebSocket((c) => {
|
|
171
|
+
let intervalId
|
|
172
|
+
return {
|
|
173
|
+
onOpen(_event, ws) {
|
|
174
|
+
intervalId = setInterval(() => {
|
|
175
|
+
ws.send(new Date().toString())
|
|
176
|
+
}, 200)
|
|
177
|
+
},
|
|
178
|
+
onClose() {
|
|
179
|
+
clearInterval(intervalId)
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
export default {
|
|
186
|
+
fetch: app.fetch,
|
|
187
|
+
websocket,
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Node.js
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { serve, upgradeWebSocket } from 'acrylblends/node-server'
|
|
195
|
+
import { Acryl } from 'acrylblends'
|
|
196
|
+
import { WebSocketServer } from 'ws'
|
|
197
|
+
|
|
198
|
+
const app = new Acryl()
|
|
199
|
+
|
|
200
|
+
app.get(
|
|
201
|
+
'/ws',
|
|
202
|
+
upgradeWebSocket(() => ({
|
|
203
|
+
onMessage(event, ws) {
|
|
204
|
+
ws.send(String(event.data))
|
|
205
|
+
},
|
|
206
|
+
}))
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
const wss = new WebSocketServer({ noServer: true })
|
|
210
|
+
|
|
211
|
+
serve({
|
|
212
|
+
fetch: app.fetch,
|
|
213
|
+
websocket: { server: wss },
|
|
214
|
+
})
|
|
215
|
+
```
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Basic Auth Middleware
|
|
2
|
+
|
|
3
|
+
This middleware can apply Basic authentication to a specified path.
|
|
4
|
+
Implementing Basic authentication with Cloudflare Workers or other platforms is more complicated than it seems, but with this middleware, it's a breeze.
|
|
5
|
+
|
|
6
|
+
For more information about how the Basic auth scheme works under the hood, see the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme).
|
|
7
|
+
|
|
8
|
+
## Import
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { Acryl } from 'acrylblends'
|
|
12
|
+
import { basicAuth } from 'acrylblends/basic-auth'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
const app = new Acryl()
|
|
19
|
+
|
|
20
|
+
app.use(
|
|
21
|
+
'/auth/*',
|
|
22
|
+
basicAuth({
|
|
23
|
+
username: 'acrylblends',
|
|
24
|
+
password: 'acoolproject',
|
|
25
|
+
})
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
app.get('/auth/page', (c) => {
|
|
29
|
+
return c.text('You are authorized')
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
To restrict to a specific route + method:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const app = new Acryl()
|
|
37
|
+
|
|
38
|
+
app.get('/auth/page', (c) => {
|
|
39
|
+
return c.text('Viewing page')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
app.delete(
|
|
43
|
+
'/auth/page',
|
|
44
|
+
basicAuth({ username: 'acrylblends', password: 'acoolproject' }),
|
|
45
|
+
(c) => {
|
|
46
|
+
return c.text('Page deleted')
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If you want to verify the user by yourself, specify the `verifyUser` option; returning `true` means it is accepted.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const app = new Acryl()
|
|
55
|
+
|
|
56
|
+
app.use(
|
|
57
|
+
basicAuth({
|
|
58
|
+
verifyUser: (username, password, c) => {
|
|
59
|
+
return (
|
|
60
|
+
username === 'dynamic-user' && password === 'acrylblends-password'
|
|
61
|
+
)
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Options
|
|
68
|
+
|
|
69
|
+
### **(required)** username: `string`
|
|
70
|
+
|
|
71
|
+
The username of the user who is authenticating.
|
|
72
|
+
|
|
73
|
+
### **(required)** password: `string`
|
|
74
|
+
|
|
75
|
+
The password value for the provided username to authenticate against.
|
|
76
|
+
|
|
77
|
+
### *(optional)* realm: `string`
|
|
78
|
+
|
|
79
|
+
The domain name of the realm, as part of the returned WWW-Authenticate challenge header. The default is `"Secure Area"`.
|
|
80
|
+
See more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives
|
|
81
|
+
|
|
82
|
+
### *(optional)* hashFunction: `Function`
|
|
83
|
+
|
|
84
|
+
A function to handle hashing for safe comparison of passwords.
|
|
85
|
+
|
|
86
|
+
### *(optional)* verifyUser: `(username: string, password: string, c: Context) => boolean | Promise<boolean>`
|
|
87
|
+
|
|
88
|
+
The function to verify the user.
|
|
89
|
+
|
|
90
|
+
### *(optional)* invalidUserMessage: `string | object | MessageFunction`
|
|
91
|
+
|
|
92
|
+
`MessageFunction` is `(c: Context) => string | object | Promise<string | object>`. The custom message if the user is invalid.
|
|
93
|
+
|
|
94
|
+
### *(optional)* onAuthSuccess: `(c: Context, username: string) => void | Promise<void>`
|
|
95
|
+
|
|
96
|
+
A callback function invoked after successful authentication. This allows you to set context variables or perform side effects without re-parsing the Authorization header.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
app.use(
|
|
100
|
+
'/auth/*',
|
|
101
|
+
basicAuth({
|
|
102
|
+
username: 'acrylblends',
|
|
103
|
+
password: 'acoolproject',
|
|
104
|
+
onAuthSuccess: (c, username) => {
|
|
105
|
+
c.set('username', username)
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
app.get('/auth/page', (c) => {
|
|
111
|
+
const username = c.get('username')
|
|
112
|
+
return c.text(`Hello, ${username}!`)
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## More Options
|
|
117
|
+
|
|
118
|
+
### *(optional)* ...users: `{ username: string, password: string }[]`
|
|
119
|
+
|
|
120
|
+
## Recipes
|
|
121
|
+
|
|
122
|
+
### Defining Multiple Users
|
|
123
|
+
|
|
124
|
+
This middleware also allows you to pass arbitrary parameters containing objects defining more `username` and `password` pairs.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
app.use(
|
|
128
|
+
'/auth/*',
|
|
129
|
+
basicAuth(
|
|
130
|
+
{
|
|
131
|
+
username: 'acrylblends',
|
|
132
|
+
password: 'acoolproject',
|
|
133
|
+
// Define other params in the first object
|
|
134
|
+
realm: 'www.example.com',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
username: 'acrylblends-admin',
|
|
138
|
+
password: 'super-secure',
|
|
139
|
+
// Cannot redefine other params here
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
username: 'acrylblends-user-1',
|
|
143
|
+
password: 'a-secret',
|
|
144
|
+
// Or here
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Or less hardcoded:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { users } from '../config/users'
|
|
154
|
+
|
|
155
|
+
app.use(
|
|
156
|
+
'/auth/*',
|
|
157
|
+
basicAuth(
|
|
158
|
+
{
|
|
159
|
+
realm: 'www.example.com',
|
|
160
|
+
...users[0],
|
|
161
|
+
},
|
|
162
|
+
...users.slice(1)
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
```
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Bearer Auth Middleware
|
|
2
|
+
|
|
3
|
+
The Bearer Auth Middleware provides authentication by verifying an API token in the Request header.
|
|
4
|
+
The HTTP clients accessing the endpoint will add the `Authorization` header with `Bearer {token}` as the header value.
|
|
5
|
+
|
|
6
|
+
Using `curl` from the terminal, it would look like this:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
curl -H 'Authorization: Bearer acrylblendsiscool' http://localhost:8787/auth/page
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { Acryl } from 'acrylblends'
|
|
16
|
+
import { bearerAuth } from 'acrylblends/bearer-auth'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
> [!NOTE]
|
|
22
|
+
> Your `token` must match the regex `/[A-Za-z0-9._~+/-]+=*/`, otherwise a 400 error will be returned. Notably, this regex accommodates both URL-safe Base64- and standard Base64-encoded JWTs. This middleware does not require the bearer token to be a JWT, just that it matches the above regex.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
const app = new Acryl()
|
|
26
|
+
|
|
27
|
+
const token = process.env.API_TOKEN!
|
|
28
|
+
|
|
29
|
+
app.use('/api/*', bearerAuth({ token }))
|
|
30
|
+
|
|
31
|
+
app.get('/api/page', (c) => {
|
|
32
|
+
return c.json({ message: 'You are authorized' })
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
To restrict to a specific route + method:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const app = new Acryl()
|
|
40
|
+
|
|
41
|
+
const token = process.env.API_TOKEN!
|
|
42
|
+
|
|
43
|
+
app.get('/api/page', (c) => {
|
|
44
|
+
return c.json({ message: 'Read posts' })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
app.post('/api/page', bearerAuth({ token }), (c) => {
|
|
48
|
+
return c.json({ message: 'Created post!' }, 201)
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
To implement multiple tokens (E.g., any valid token can read but create/update/delete are restricted to a privileged token):
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const app = new Acryl()
|
|
56
|
+
|
|
57
|
+
const readToken = 'read'
|
|
58
|
+
const privilegedToken = 'read+write'
|
|
59
|
+
const privilegedMethods = ['POST', 'PUT', 'PATCH', 'DELETE']
|
|
60
|
+
|
|
61
|
+
app.on('GET', '/api/page/*', async (c, next) => {
|
|
62
|
+
// List of valid tokens
|
|
63
|
+
const bearer = bearerAuth({ token: [readToken, privilegedToken] })
|
|
64
|
+
return bearer(c, next)
|
|
65
|
+
})
|
|
66
|
+
app.on(privilegedMethods, '/api/page/*', async (c, next) => {
|
|
67
|
+
// Single valid privileged token
|
|
68
|
+
const bearer = bearerAuth({ token: privilegedToken })
|
|
69
|
+
return bearer(c, next)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Define handlers for GET, POST, etc.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
If you want to verify the value of the token yourself, specify the `verifyToken` option; returning `true` means it is accepted.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const app = new Acryl()
|
|
79
|
+
|
|
80
|
+
app.use(
|
|
81
|
+
'/auth-verify-token/*',
|
|
82
|
+
bearerAuth({
|
|
83
|
+
verifyToken: async (token, c) => {
|
|
84
|
+
return token === 'dynamic-token'
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Options
|
|
91
|
+
|
|
92
|
+
### **(required)** token: `string` | `string[]`
|
|
93
|
+
|
|
94
|
+
The string to validate the incoming bearer token against.
|
|
95
|
+
|
|
96
|
+
### *(optional)* realm: `string`
|
|
97
|
+
|
|
98
|
+
The domain name of the realm, as part of the returned WWW-Authenticate challenge header. The default is `""`.
|
|
99
|
+
See more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives
|
|
100
|
+
|
|
101
|
+
### *(optional)* prefix: `string`
|
|
102
|
+
|
|
103
|
+
The prefix (or known as `schema`) for the Authorization header value. The default is `"Bearer"`.
|
|
104
|
+
|
|
105
|
+
### *(optional)* headerName: `string`
|
|
106
|
+
|
|
107
|
+
The header name. The default value is `Authorization`.
|
|
108
|
+
|
|
109
|
+
### *(optional)* hashFunction: `Function`
|
|
110
|
+
|
|
111
|
+
A function to handle hashing for safe comparison of authentication tokens.
|
|
112
|
+
|
|
113
|
+
### *(optional)* verifyToken: `(token: string, c: Context) => boolean | Promise<boolean>`
|
|
114
|
+
|
|
115
|
+
The function to verify the token.
|
|
116
|
+
|
|
117
|
+
### *(optional)* noAuthenticationHeader: `object`
|
|
118
|
+
|
|
119
|
+
Customizes the error response when the request does not have an authentication header.
|
|
120
|
+
|
|
121
|
+
- `wwwAuthenticateHeader`: `string | object | MessageFunction` - Customizes the WWW-Authenticate header value.
|
|
122
|
+
- `message`: `string | object | MessageFunction` - The custom message for the response body.
|
|
123
|
+
|
|
124
|
+
`MessageFunction` is `(c: Context) => string | object | Promise<string | object>`.
|
|
125
|
+
|
|
126
|
+
### *(optional)* invalidAuthenticationHeader: `object`
|
|
127
|
+
|
|
128
|
+
Customizes the error response when the authentication header format is invalid.
|
|
129
|
+
|
|
130
|
+
- `wwwAuthenticateHeader`: `string | object | MessageFunction` - Customizes the WWW-Authenticate header value.
|
|
131
|
+
- `message`: `string | object | MessageFunction` - The custom message for the response body.
|
|
132
|
+
|
|
133
|
+
### *(optional)* invalidToken: `object`
|
|
134
|
+
|
|
135
|
+
Customizes the error response when the token is invalid.
|
|
136
|
+
|
|
137
|
+
- `wwwAuthenticateHeader`: `string | object | MessageFunction` - Customizes the WWW-Authenticate header value.
|
|
138
|
+
- `message`: `string | object | MessageFunction` - The custom message for the response body.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Body Limit Middleware
|
|
2
|
+
|
|
3
|
+
The Body Limit Middleware can limit the file size of the request body.
|
|
4
|
+
|
|
5
|
+
This middleware first uses the value of the `Content-Length` header in the request, if present.
|
|
6
|
+
If it is not set, it reads the body in the stream and executes an error handler if it is larger than the specified file size.
|
|
7
|
+
|
|
8
|
+
## Import
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { Acryl } from 'acrylblends'
|
|
12
|
+
import { bodyLimit } from 'acrylblends/body-limit'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
const app = new Acryl()
|
|
19
|
+
|
|
20
|
+
app.post(
|
|
21
|
+
'/upload',
|
|
22
|
+
bodyLimit({
|
|
23
|
+
maxSize: 50 * 1024, // 50kb
|
|
24
|
+
onError: (c) => {
|
|
25
|
+
return c.text('overflow :(', 413)
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
async (c) => {
|
|
29
|
+
const body = await c.req.parseBody()
|
|
30
|
+
if (body['file'] instanceof File) {
|
|
31
|
+
console.log(`Got file sized: ${body['file'].size}`)
|
|
32
|
+
}
|
|
33
|
+
return c.text('pass :)')
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Options
|
|
39
|
+
|
|
40
|
+
### **(required)** maxSize: `number`
|
|
41
|
+
|
|
42
|
+
The maximum file size of the file you want to limit. The default is `100 * 1024` - `100kb`.
|
|
43
|
+
|
|
44
|
+
### *(optional)* onError: `OnError`
|
|
45
|
+
|
|
46
|
+
The error handler to be invoked if the specified file size is exceeded.
|
|
47
|
+
|
|
48
|
+
## Usage with Bun for large requests
|
|
49
|
+
|
|
50
|
+
If the Body Limit Middleware is used explicitly to allow a request body larger than the default, it might be necessary to make changes to your `Bun.serve` configuration accordingly. [At the time of writing](https://github.com/oven-sh/bun/blob/f2cfa15e4ef9d730fc6842ad8b79fb7ab4c71cb9/packages/bun-types/bun.d.ts#L2191), `Bun.serve`'s default request body limit is 128MiB. If you set Acryl's Body Limit Middleware to a value bigger than that, your requests will still fail and, additionally, the `onError` handler specified in the middleware will not be called. This is because `Bun.serve()` will set the status code to `413` and terminate the connection before passing the request to Acryl.
|
|
51
|
+
|
|
52
|
+
If you want to accept requests larger than 128MiB with Acryl and Bun, you need to set the limit for Bun as well:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
export default {
|
|
56
|
+
port: process.env['PORT'] || 3000,
|
|
57
|
+
fetch: app.fetch,
|
|
58
|
+
maxRequestBodySize: 1024 * 1024 * 200, // your value here
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
or, depending on your setup:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
Bun.serve({
|
|
66
|
+
fetch(req, server) {
|
|
67
|
+
return app.fetch(req, { ip: server.requestIP(req) })
|
|
68
|
+
},
|
|
69
|
+
maxRequestBodySize: 1024 * 1024 * 200, // your value here
|
|
70
|
+
})
|
|
71
|
+
```
|