@devultur/self-host 1.5.2 → 1.6.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 +9 -1
- package/dist/{chunk-PQO3X57I.js → chunk-BPHMKSQ7.js} +1 -0
- package/dist/index.d.ts +132 -5
- package/dist/index.js +822 -196
- package/dist/transcode/container.js +1 -1
- package/docs/guides/plugins.md +13 -5
- package/docs/guides/self-host.md +25 -4
- package/package.json +2 -2
package/docs/guides/plugins.md
CHANGED
|
@@ -8,7 +8,7 @@ same API as yours. Add plugins to extend the Worker, or replace a default one.
|
|
|
8
8
|
import { createWorker, defaultPlugins } from "@devultur/self-host";
|
|
9
9
|
import { usageLog } from "./plugins/usage-log";
|
|
10
10
|
|
|
11
|
-
export { DownloadLink } from "@devultur/self-host";
|
|
11
|
+
export { DownloadGrants, DownloadLink } from "@devultur/self-host";
|
|
12
12
|
|
|
13
13
|
export default createWorker({
|
|
14
14
|
plugins: [...defaultPlugins({ transcode: { maxResolution: "1080p" } }), usageLog()],
|
|
@@ -22,7 +22,7 @@ export default createWorker({
|
|
|
22
22
|
| `staticConfig(config)` | Applies the [rules](self-host.md#rules) to every request. Put it first: later plugins narrow it. |
|
|
23
23
|
| `workerSecretAuth()` | Control routes require `Authorization: Bearer <WORKER_AUTH_SECRET>`; the transcode runner uploads with the same secret. |
|
|
24
24
|
| `credentialPolicy(options?)` | Decides before a credential is issued. Without options it issues what is asked; `maxTtlSeconds: { viewer: 3600 }` caps a kind. |
|
|
25
|
-
| `downloadLinks()` | `POST /v1/token/download` and `GET /v1/download/<id>` (needs the `DOWNLOAD_LINKS` Durable Object). Without it
|
|
25
|
+
| `downloadLinks()` | `POST /v1/token/download` and `GET /v1/download/<id>` (needs the `DOWNLOAD_LINKS` Durable Object), and download grants: `/v1/token/grant` and `/v1/grant/*` (also need `DOWNLOAD_GRANTS`). Without it they answer `404`. |
|
|
26
26
|
|
|
27
27
|
`createWorker()` refuses to start when no plugin authenticates requests, so dropping `workerSecretAuth()` without
|
|
28
28
|
an auth plugin of yours fails at deploy, not in production.
|
|
@@ -42,6 +42,7 @@ interface WorkerPlugin {
|
|
|
42
42
|
routes?: Array<{ path: string; app: Hono }>;
|
|
43
43
|
issueCredential?: (context: Context, request: CredentialRequest) => CredentialDecision | Promise<CredentialDecision>;
|
|
44
44
|
beforeLinkRedeem?: (context: Context, link: DownloadLinkInfo) => Response | null | Promise<Response | null>;
|
|
45
|
+
authorizeGrant?: (context: Context, request: GrantAccessRequest) => Response | null | Promise<Response | null>;
|
|
45
46
|
authorizeLockChange?: (context: Context, request: LockChangeRequest) => LockChangeDecision | Promise<LockChangeDecision>;
|
|
46
47
|
onLockAudit?: (context: Context, record: LockAuditRecord) => void | Promise<void>;
|
|
47
48
|
}
|
|
@@ -94,8 +95,8 @@ updateRequestState(context, { context: { appId: "web" } });
|
|
|
94
95
|
|
|
95
96
|
## Hooks
|
|
96
97
|
|
|
97
|
-
**`issueCredential`** runs before the Worker issues a viewer token, an upload token, a download link
|
|
98
|
-
service token. It gets the `kind`, the `scopes`, the `path` it is bound to and the requested `expiresAt` (epoch ms).
|
|
98
|
+
**`issueCredential`** runs before the Worker issues a viewer token, an upload token, a download link, a download grant
|
|
99
|
+
or the runner's service token. It gets the `kind`, the `scopes`, the `path` it is bound to and the requested `expiresAt` (epoch ms).
|
|
99
100
|
Return `{ allow: false, response }` to refuse, or `{ allow: true, expiresAt?, claims? }` to issue it: `expiresAt`
|
|
100
101
|
caps the expiry, and `claims` are added to a token. The hooks of every plugin run in order: the first refusal wins,
|
|
101
102
|
the earliest `expiresAt` applies, and the claims of all of them are added.
|
|
@@ -120,6 +121,12 @@ allow gets `403 { "code": "token_rejected" }`; which claim failed is only logged
|
|
|
120
121
|
**`beforeLinkRedeem`** runs before `GET /v1/download/<id>` spends a use of a link. Return a `Response` to refuse it
|
|
121
122
|
(the use isn't spent), or `null` to let it go on. It gets the link's `storagePrefix`, `key` and plugin `context`.
|
|
122
123
|
|
|
124
|
+
**`authorizeGrant`** runs before a download grant is used, once the Worker checked the grant is live and belongs to the
|
|
125
|
+
request's storage prefix: `action` is `head`, `issue-link`, `delete-file` or `revoke` (its bearer), or `owner-revoke`
|
|
126
|
+
(the owner, by its public id). It also gets the `grant` (its key, caps, `linksIssued`, metadata and the plugin `context`
|
|
127
|
+
it was issued with, never its secret). Return a `Response` to refuse (a refused link spends nothing), or `null`. Use it
|
|
128
|
+
to tie a grant to the credential that issued it, for example to refuse it once that credential is revoked.
|
|
129
|
+
|
|
123
130
|
**`authorizeLockChange`** runs before the Worker changes a lock ([Retention and holds](object-lock.md)):
|
|
124
131
|
`PUT /v1/lock/<key>` once per change, and `POST /v1/lock/hold` once per page. It gets:
|
|
125
132
|
|
|
@@ -173,7 +180,8 @@ const auditToD1: WorkerPlugin = {
|
|
|
173
180
|
## After the route
|
|
174
181
|
|
|
175
182
|
- `getRouteResult(context)` tells a middleware that runs after `await next()` what the route did: `redeemedLink`
|
|
176
|
-
(the download link just redeemed), `
|
|
183
|
+
(the download link just redeemed), `issuedGrant` and `revokedGrant` (a download grant, with its public id; a revoke
|
|
184
|
+
says `by: "bearer"` or `"owner"`), `grantDeletedKey` (a grant deleted its file), `originalDeleted` (`transcode.complete` deleted the original),
|
|
177
185
|
`originalRetained` (it kept a locked original instead) and
|
|
178
186
|
`transcodeJob` (the job a runner callback belongs to, with its plugin `context`).
|
|
179
187
|
- `getPendingUpload(context)`, before the route, returns the upload an upload URL points to: its key, storage key,
|
package/docs/guides/self-host.md
CHANGED
|
@@ -44,7 +44,7 @@ media Worker from `@devultur/self-host`:
|
|
|
44
44
|
|
|
45
45
|
```typescript
|
|
46
46
|
import { createWorker } from "@devultur/self-host";
|
|
47
|
-
export { DownloadLink } from "@devultur/self-host";
|
|
47
|
+
export { DownloadGrants, DownloadLink } from "@devultur/self-host";
|
|
48
48
|
|
|
49
49
|
export default createWorker();
|
|
50
50
|
```
|
|
@@ -98,7 +98,7 @@ It prints a config snippet with the new namespace's `id`. Paste that id into the
|
|
|
98
98
|
|
|
99
99
|
#### 4. Write `wrangler.toml`
|
|
100
100
|
|
|
101
|
-
Bind both buckets, the KV namespace and the `DownloadLink` Durable
|
|
101
|
+
Bind both buckets, the KV namespace and the `DownloadLink` and `DownloadGrants` Durable Objects:
|
|
102
102
|
|
|
103
103
|
```toml
|
|
104
104
|
name = "media"
|
|
@@ -123,13 +123,22 @@ id = "<kv-namespace-id>"
|
|
|
123
123
|
name = "DOWNLOAD_LINKS"
|
|
124
124
|
class_name = "DownloadLink"
|
|
125
125
|
|
|
126
|
+
[[durable_objects.bindings]]
|
|
127
|
+
name = "DOWNLOAD_GRANTS"
|
|
128
|
+
class_name = "DownloadGrants"
|
|
129
|
+
|
|
126
130
|
[[migrations]]
|
|
127
131
|
tag = "v1"
|
|
128
132
|
new_sqlite_classes = ["DownloadLink"]
|
|
133
|
+
|
|
134
|
+
[[migrations]]
|
|
135
|
+
tag = "v2"
|
|
136
|
+
new_sqlite_classes = ["DownloadGrants"]
|
|
129
137
|
```
|
|
130
138
|
|
|
131
|
-
Without the `DOWNLOAD_LINKS` binding, `POST /v1/token/download` answers `501
|
|
132
|
-
|
|
139
|
+
Without the `DOWNLOAD_LINKS` binding, `POST /v1/token/download` answers `501`; without `DOWNLOAD_GRANTS`, the download
|
|
140
|
+
grant routes do. A Worker deployed before download grants keeps its `v1` migration and adds `v2`. For staging and
|
|
141
|
+
production in one file, see [Per-environment config](#per-environment-config).
|
|
133
142
|
|
|
134
143
|
#### 5. Set the secrets
|
|
135
144
|
|
|
@@ -189,6 +198,10 @@ account_id = "<your-account-id>"
|
|
|
189
198
|
tag = "v1"
|
|
190
199
|
new_sqlite_classes = ["DownloadLink"]
|
|
191
200
|
|
|
201
|
+
[[migrations]]
|
|
202
|
+
tag = "v2"
|
|
203
|
+
new_sqlite_classes = ["DownloadGrants"]
|
|
204
|
+
|
|
192
205
|
# Staging
|
|
193
206
|
[env.staging]
|
|
194
207
|
name = "media-staging"
|
|
@@ -209,6 +222,10 @@ id = "<staging-kv-namespace-id>"
|
|
|
209
222
|
name = "DOWNLOAD_LINKS"
|
|
210
223
|
class_name = "DownloadLink"
|
|
211
224
|
|
|
225
|
+
[[env.staging.durable_objects.bindings]]
|
|
226
|
+
name = "DOWNLOAD_GRANTS"
|
|
227
|
+
class_name = "DownloadGrants"
|
|
228
|
+
|
|
212
229
|
# Production
|
|
213
230
|
[env.production]
|
|
214
231
|
name = "media-production"
|
|
@@ -228,6 +245,10 @@ id = "<production-kv-namespace-id>"
|
|
|
228
245
|
[[env.production.durable_objects.bindings]]
|
|
229
246
|
name = "DOWNLOAD_LINKS"
|
|
230
247
|
class_name = "DownloadLink"
|
|
248
|
+
|
|
249
|
+
[[env.production.durable_objects.bindings]]
|
|
250
|
+
name = "DOWNLOAD_GRANTS"
|
|
251
|
+
class_name = "DownloadGrants"
|
|
231
252
|
```
|
|
232
253
|
|
|
233
254
|
`vars` aren't inherited either: repeat `[env.<name>.vars]` (for example `TRANSCODE_RUNNER`) in each one. Then set
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devultur/self-host",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Self-hosted media Worker for Cloudflare: R2 uploads, edge-cached delivery, image transforms, HLS transcode and captions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@jsquash/resize": "2.1.1",
|
|
48
48
|
"@jsquash/webp": "1.5.0",
|
|
49
49
|
"hono": "^4.7.0",
|
|
50
|
-
"@devultur/core": "1.
|
|
50
|
+
"@devultur/core": "1.6.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@cf-wasm/photon": "0.4.0",
|