@edge-base/server 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/admin-build/.gitkeep +0 -0
- package/admin-build/_app/env.js +1 -0
- package/admin-build/_app/immutable/assets/0.Bm6cF078.css +1 -0
- package/admin-build/_app/immutable/assets/1.BfW3pUNa.css +1 -0
- package/admin-build/_app/immutable/assets/11.CVmQOewb.css +1 -0
- package/admin-build/_app/immutable/assets/12.B1EhbRZT.css +1 -0
- package/admin-build/_app/immutable/assets/13.BvwYeuwE.css +1 -0
- package/admin-build/_app/immutable/assets/14.CdVfcO0R.css +1 -0
- package/admin-build/_app/immutable/assets/15.2yeZ66b-.css +1 -0
- package/admin-build/_app/immutable/assets/17.BVg0JEVu.css +1 -0
- package/admin-build/_app/immutable/assets/18.Rwnl3x_i.css +1 -0
- package/admin-build/_app/immutable/assets/20.DsPWA9AV.css +1 -0
- package/admin-build/_app/immutable/assets/21.Dz2RJ56c.css +1 -0
- package/admin-build/_app/immutable/assets/22.DwNLk5Ai.css +1 -0
- package/admin-build/_app/immutable/assets/23.CFpu0gOO.css +1 -0
- package/admin-build/_app/immutable/assets/24.Cy5LBeoJ.css +1 -0
- package/admin-build/_app/immutable/assets/25.pUyLVf-h.css +1 -0
- package/admin-build/_app/immutable/assets/26.DBcGrlXa.css +1 -0
- package/admin-build/_app/immutable/assets/27.BswYyAJD.css +1 -0
- package/admin-build/_app/immutable/assets/28.B4ueB1Kf.css +1 -0
- package/admin-build/_app/immutable/assets/29.B-qU6PdF.css +1 -0
- package/admin-build/_app/immutable/assets/3.Dg81Pgmd.css +1 -0
- package/admin-build/_app/immutable/assets/30.CsdWum94.css +1 -0
- package/admin-build/_app/immutable/assets/31.U6OwIp50.css +1 -0
- package/admin-build/_app/immutable/assets/4.CyawCCux.css +1 -0
- package/admin-build/_app/immutable/assets/5.C0YO2HTk.css +1 -0
- package/admin-build/_app/immutable/assets/8.Br5jd6kD.css +1 -0
- package/admin-build/_app/immutable/assets/Badge.EMYLHBxE.css +1 -0
- package/admin-build/_app/immutable/assets/Button.DpzMRTjK.css +1 -0
- package/admin-build/_app/immutable/assets/ConfirmDialog.DAnaWRRk.css +1 -0
- package/admin-build/_app/immutable/assets/EmptyState.CwKsu57Y.css +1 -0
- package/admin-build/_app/immutable/assets/Input.BDUSenmU.css +1 -0
- package/admin-build/_app/immutable/assets/Modal.Dm5B0Xie.css +1 -0
- package/admin-build/_app/immutable/assets/PageShell.CmU-Xh-b.css +1 -0
- package/admin-build/_app/immutable/assets/SchemaFieldEditor.g4NsCdno.css +1 -0
- package/admin-build/_app/immutable/assets/Select.BW4Keufm.css +1 -0
- package/admin-build/_app/immutable/assets/Skeleton.KWUulTKJ.css +1 -0
- package/admin-build/_app/immutable/assets/Tabs.CniGYb67.css +1 -0
- package/admin-build/_app/immutable/assets/TimeChart.BTCDAvmT.css +1 -0
- package/admin-build/_app/immutable/assets/Toggle.Cy_K12OM.css +1 -0
- package/admin-build/_app/immutable/assets/TopList.ClFzmPlA.css +1 -0
- package/admin-build/_app/immutable/chunks/7B47DvSx.js +1 -0
- package/admin-build/_app/immutable/chunks/7f08Id8e.js +1 -0
- package/admin-build/_app/immutable/chunks/8wJeQ7LN.js +1 -0
- package/admin-build/_app/immutable/chunks/B-h2afW5.js +1 -0
- package/admin-build/_app/immutable/chunks/B8vJP3wz.js +1 -0
- package/admin-build/_app/immutable/chunks/BR_fL5Yv.js +1 -0
- package/admin-build/_app/immutable/chunks/BY92tFS2.js +1 -0
- package/admin-build/_app/immutable/chunks/BcR-Rdj9.js +1 -0
- package/admin-build/_app/immutable/chunks/BdrwyZv8.js +1 -0
- package/admin-build/_app/immutable/chunks/Bh56EfQ_.js +1 -0
- package/admin-build/_app/immutable/chunks/BkrCkgYp.js +1 -0
- package/admin-build/_app/immutable/chunks/BmRjiP5k.js +1 -0
- package/admin-build/_app/immutable/chunks/BsokvhWC.js +1 -0
- package/admin-build/_app/immutable/chunks/C4D51vTW.js +1 -0
- package/admin-build/_app/immutable/chunks/C6puvcoR.js +2 -0
- package/admin-build/_app/immutable/chunks/CCKNu7m7.js +1 -0
- package/admin-build/_app/immutable/chunks/CWj6FrbW.js +1 -0
- package/admin-build/_app/immutable/chunks/Ce-ngf4p.js +5 -0
- package/admin-build/_app/immutable/chunks/Cs0GwzJA.js +1 -0
- package/admin-build/_app/immutable/chunks/CwROoZK0.js +1 -0
- package/admin-build/_app/immutable/chunks/CxCPv_Ut.js +1 -0
- package/admin-build/_app/immutable/chunks/CxbRue-5.js +1 -0
- package/admin-build/_app/immutable/chunks/CyqB6g-D.js +1 -0
- package/admin-build/_app/immutable/chunks/D5h5A1cc.js +2 -0
- package/admin-build/_app/immutable/chunks/DnyL7Zq-.js +1 -0
- package/admin-build/_app/immutable/chunks/DoPXzH7F.js +1 -0
- package/admin-build/_app/immutable/chunks/DrQSgw-f.js +1 -0
- package/admin-build/_app/immutable/chunks/DttM2zNO.js +1 -0
- package/admin-build/_app/immutable/chunks/DuXuUBWN.js +1 -0
- package/admin-build/_app/immutable/chunks/MdeqaOQx.js +10 -0
- package/admin-build/_app/immutable/chunks/NuUjtcO2.js +1 -0
- package/admin-build/_app/immutable/chunks/Q2nPFxS6.js +1 -0
- package/admin-build/_app/immutable/chunks/R6arueIl.js +1 -0
- package/admin-build/_app/immutable/chunks/UUazaC_N.js +1 -0
- package/admin-build/_app/immutable/chunks/cOYbrQxx.js +1 -0
- package/admin-build/_app/immutable/chunks/eFQHTGwA.js +1 -0
- package/admin-build/_app/immutable/chunks/ehbppgYb.js +1 -0
- package/admin-build/_app/immutable/chunks/glwixJlP.js +1 -0
- package/admin-build/_app/immutable/chunks/vApWTCBs.js +1 -0
- package/admin-build/_app/immutable/chunks/w89G9Xpi.js +1 -0
- package/admin-build/_app/immutable/chunks/wJsUhbfZ.js +1 -0
- package/admin-build/_app/immutable/chunks/zfauFM8P.js +1 -0
- package/admin-build/_app/immutable/entry/app.CcO-Uos3.js +2 -0
- package/admin-build/_app/immutable/entry/start.COebYq3I.js +1 -0
- package/admin-build/_app/immutable/nodes/0.CjtHKU-6.js +1 -0
- package/admin-build/_app/immutable/nodes/1.DEisjlM0.js +1 -0
- package/admin-build/_app/immutable/nodes/10.CvhdyWVB.js +1 -0
- package/admin-build/_app/immutable/nodes/11.DjHqcOvy.js +1 -0
- package/admin-build/_app/immutable/nodes/12.mQLz4Mj_.js +1 -0
- package/admin-build/_app/immutable/nodes/13.CBonZZyP.js +110 -0
- package/admin-build/_app/immutable/nodes/14.d-oiZL0j.js +3 -0
- package/admin-build/_app/immutable/nodes/15.CKPQsUYF.js +1 -0
- package/admin-build/_app/immutable/nodes/16.wPzAPQGx.js +1 -0
- package/admin-build/_app/immutable/nodes/17.DayhKyEZ.js +1 -0
- package/admin-build/_app/immutable/nodes/18.DKwS0Ir0.js +1 -0
- package/admin-build/_app/immutable/nodes/19.wPzAPQGx.js +1 -0
- package/admin-build/_app/immutable/nodes/2.BKoKrw1i.js +1 -0
- package/admin-build/_app/immutable/nodes/20.BvIkkkrW.js +1 -0
- package/admin-build/_app/immutable/nodes/21.DMaFhdHk.js +128 -0
- package/admin-build/_app/immutable/nodes/22.3xdgwuK1.js +1 -0
- package/admin-build/_app/immutable/nodes/23.8Bvgjbsl.js +112 -0
- package/admin-build/_app/immutable/nodes/24.DzSSzRhG.js +2 -0
- package/admin-build/_app/immutable/nodes/25.9KKYBnAE.js +2 -0
- package/admin-build/_app/immutable/nodes/26.Bhn9dfhY.js +1 -0
- package/admin-build/_app/immutable/nodes/27.kRLiC24G.js +1 -0
- package/admin-build/_app/immutable/nodes/28.BVIN1-7N.js +1 -0
- package/admin-build/_app/immutable/nodes/29.3yabZWj4.js +1 -0
- package/admin-build/_app/immutable/nodes/3.BFtSOkX7.js +2 -0
- package/admin-build/_app/immutable/nodes/30.CyCQlwaP.js +1 -0
- package/admin-build/_app/immutable/nodes/31.C4LDXjES.js +1 -0
- package/admin-build/_app/immutable/nodes/4.CvbiMlCa.js +1 -0
- package/admin-build/_app/immutable/nodes/5.C6BLv2eM.js +1 -0
- package/admin-build/_app/immutable/nodes/6.BcXvfl2P.js +1 -0
- package/admin-build/_app/immutable/nodes/7.CIuqhPiK.js +1 -0
- package/admin-build/_app/immutable/nodes/8.BQOR_JfO.js +1 -0
- package/admin-build/_app/immutable/nodes/9.NZqXQxPy.js +1 -0
- package/admin-build/_app/version.json +1 -0
- package/admin-build/favicon.svg +26 -0
- package/admin-build/index.html +45 -0
- package/openapi.json +19543 -0
- package/package.json +66 -0
- package/src/__tests__/admin-assets.test.ts +55 -0
- package/src/__tests__/admin-data-routes.test.ts +488 -0
- package/src/__tests__/admin-db-target.test.ts +103 -0
- package/src/__tests__/admin-routing.test.ts +31 -0
- package/src/__tests__/admin-user-management.test.ts +311 -0
- package/src/__tests__/analytics-query.test.ts +75 -0
- package/src/__tests__/auth-d1.test.ts +749 -0
- package/src/__tests__/auth-db-adapter.test.ts +73 -0
- package/src/__tests__/auth-jwt.test.ts +440 -0
- package/src/__tests__/auth-oauth.test.ts +389 -0
- package/src/__tests__/auth-password.test.ts +367 -0
- package/src/__tests__/auth-redirect.test.ts +87 -0
- package/src/__tests__/backup-restore.test.ts +711 -0
- package/src/__tests__/broadcast.test.ts +128 -0
- package/src/__tests__/cli.test.ts +178 -0
- package/src/__tests__/cloudflare-realtime.test.ts +113 -0
- package/src/__tests__/config.test.ts +469 -0
- package/src/__tests__/cors.test.ts +154 -0
- package/src/__tests__/cron.test.ts +302 -0
- package/src/__tests__/d1-handler.test.ts +402 -0
- package/src/__tests__/d1-sql.test.ts +120 -0
- package/src/__tests__/database-live-config.test.ts +42 -0
- package/src/__tests__/database-live-emitter.test.ts +56 -0
- package/src/__tests__/database-live-filters.test.ts +63 -0
- package/src/__tests__/database-live-route.test.ts +113 -0
- package/src/__tests__/db-sql.test.ts +163 -0
- package/src/__tests__/do-lifecycle.test.ts +263 -0
- package/src/__tests__/do-router.test.ts +729 -0
- package/src/__tests__/email-provider.test.ts +128 -0
- package/src/__tests__/email-templates.test.ts +528 -0
- package/src/__tests__/error-format.test.ts +250 -0
- package/src/__tests__/field-ops.test.ts +242 -0
- package/src/__tests__/functions-context.test.ts +334 -0
- package/src/__tests__/functions-d1-proxy.test.ts +229 -0
- package/src/__tests__/functions-registry-runtime-config.test.ts +17 -0
- package/src/__tests__/functions-route.test.ts +139 -0
- package/src/__tests__/internal-request.test.ts +77 -0
- package/src/__tests__/log-writer.test.ts +44 -0
- package/src/__tests__/logger.test.ts +58 -0
- package/src/__tests__/meta-admin-proxy.test.ts +48 -0
- package/src/__tests__/meta-export-coverage.test.ts +191 -0
- package/src/__tests__/meta-route-registration.test.ts +47 -0
- package/src/__tests__/namespace-dump.test.ts +28 -0
- package/src/__tests__/oauth-providers.test.ts +337 -0
- package/src/__tests__/openapi-coverage.test.ts +144 -0
- package/src/__tests__/pagination.test.ts +59 -0
- package/src/__tests__/password-policy.test.ts +191 -0
- package/src/__tests__/plugin-migrations.test.ts +379 -0
- package/src/__tests__/postgres-batch-compat.test.ts +133 -0
- package/src/__tests__/postgres-dialect.test.ts +328 -0
- package/src/__tests__/postgres-executor.test.ts +79 -0
- package/src/__tests__/postgres-field-ops-compat.test.ts +222 -0
- package/src/__tests__/postgres-schema-init.test.ts +105 -0
- package/src/__tests__/postgres-table-utils.test.ts +107 -0
- package/src/__tests__/presence.test.ts +199 -0
- package/src/__tests__/provider.test.ts +550 -0
- package/src/__tests__/public-user-profile.test.ts +339 -0
- package/src/__tests__/push-handlers.test.ts +179 -0
- package/src/__tests__/push-provider.test.ts +80 -0
- package/src/__tests__/push-token.test.ts +418 -0
- package/src/__tests__/query.test.ts +771 -0
- package/src/__tests__/rate-limit.test.ts +260 -0
- package/src/__tests__/room-access-policy.test.ts +101 -0
- package/src/__tests__/room-handler-context.test.ts +130 -0
- package/src/__tests__/room-monitoring.test.ts +138 -0
- package/src/__tests__/room-runtime-routing.test.ts +222 -0
- package/src/__tests__/room.test.ts +254 -0
- package/src/__tests__/route-parser.test.ts +490 -0
- package/src/__tests__/rules.test.ts +234 -0
- package/src/__tests__/runtime-surface-accounting.test.ts +120 -0
- package/src/__tests__/scheduled.test.ts +80 -0
- package/src/__tests__/schema.test.ts +1273 -0
- package/src/__tests__/security-hardening.test.ts +312 -0
- package/src/__tests__/server.unit.test.ts +333 -0
- package/src/__tests__/service-key-db-proxy.test.ts +650 -0
- package/src/__tests__/service-key-provider-bypass.test.ts +138 -0
- package/src/__tests__/service-key.test.ts +757 -0
- package/src/__tests__/smoke-skip-report.test.ts +72 -0
- package/src/__tests__/sms-provider.test.ts +39 -0
- package/src/__tests__/sql-route.test.ts +218 -0
- package/src/__tests__/storage-hook-context.test.ts +115 -0
- package/src/__tests__/totp.test.ts +200 -0
- package/src/__tests__/uuid.test.ts +144 -0
- package/src/__tests__/validation.test.ts +773 -0
- package/src/__tests__/websocket-pending.test.ts +163 -0
- package/src/_functions-registry.ts +51 -0
- package/src/bench-entry.ts +9 -0
- package/src/cloudflare-test.d.ts +1 -0
- package/src/durable-objects/auth-do.ts +49 -0
- package/src/durable-objects/database-do.ts +2240 -0
- package/src/durable-objects/database-live-do.ts +949 -0
- package/src/durable-objects/logs-do.ts +1200 -0
- package/src/durable-objects/room-runtime-base.ts +1604 -0
- package/src/durable-objects/rooms-do.ts +2191 -0
- package/src/generated-config.ts +6 -0
- package/src/index.ts +382 -0
- package/src/lib/admin-assets.ts +54 -0
- package/src/lib/admin-db-target.ts +301 -0
- package/src/lib/admin-routing.ts +35 -0
- package/src/lib/admin-user-management.ts +464 -0
- package/src/lib/analytics-adapter.ts +103 -0
- package/src/lib/analytics-query.ts +579 -0
- package/src/lib/auth-d1-service.ts +1193 -0
- package/src/lib/auth-d1.ts +1056 -0
- package/src/lib/auth-db-adapter.ts +289 -0
- package/src/lib/auth-redirect.ts +116 -0
- package/src/lib/cidr.ts +115 -0
- package/src/lib/client-ip.ts +51 -0
- package/src/lib/cloudflare-realtime.ts +251 -0
- package/src/lib/control-db.ts +36 -0
- package/src/lib/cron.ts +163 -0
- package/src/lib/d1-handler.ts +1425 -0
- package/src/lib/d1-schema-init.ts +255 -0
- package/src/lib/d1-sql.ts +33 -0
- package/src/lib/database-live-config.ts +24 -0
- package/src/lib/database-live-emitter.ts +111 -0
- package/src/lib/db-sql.ts +66 -0
- package/src/lib/do-retry.ts +36 -0
- package/src/lib/do-router.ts +270 -0
- package/src/lib/do-sql.ts +73 -0
- package/src/lib/email-provider.ts +379 -0
- package/src/lib/email-templates.ts +285 -0
- package/src/lib/email-translations.ts +422 -0
- package/src/lib/errors.ts +151 -0
- package/src/lib/functions.ts +2091 -0
- package/src/lib/hono.ts +56 -0
- package/src/lib/internal-request.ts +56 -0
- package/src/lib/jwt.ts +354 -0
- package/src/lib/log-writer.ts +272 -0
- package/src/lib/namespace-dump.ts +125 -0
- package/src/lib/oauth-providers.ts +1225 -0
- package/src/lib/op-parser.ts +99 -0
- package/src/lib/openapi.ts +146 -0
- package/src/lib/pagination.ts +19 -0
- package/src/lib/password-policy.ts +102 -0
- package/src/lib/password.ts +145 -0
- package/src/lib/plugin-migrations.ts +612 -0
- package/src/lib/postgres-executor.ts +203 -0
- package/src/lib/postgres-handler.ts +1102 -0
- package/src/lib/postgres-schema-init.ts +341 -0
- package/src/lib/postgres-table-utils.ts +87 -0
- package/src/lib/public-user-profile.ts +187 -0
- package/src/lib/push-provider.ts +409 -0
- package/src/lib/push-token.ts +294 -0
- package/src/lib/query-engine.ts +768 -0
- package/src/lib/room-monitoring.ts +97 -0
- package/src/lib/room-runtime.ts +14 -0
- package/src/lib/route-parser.ts +434 -0
- package/src/lib/schema.ts +538 -0
- package/src/lib/schemas.ts +152 -0
- package/src/lib/service-key.ts +419 -0
- package/src/lib/sms-provider.ts +230 -0
- package/src/lib/startup-config.ts +99 -0
- package/src/lib/totp.ts +242 -0
- package/src/lib/uuid.ts +87 -0
- package/src/lib/validation.ts +205 -0
- package/src/lib/version.ts +2 -0
- package/src/lib/websocket-pending.ts +40 -0
- package/src/middleware/auth.ts +169 -0
- package/src/middleware/captcha-verify.ts +217 -0
- package/src/middleware/cors.ts +159 -0
- package/src/middleware/error-handler.ts +54 -0
- package/src/middleware/internal-guard.ts +26 -0
- package/src/middleware/logger.ts +126 -0
- package/src/middleware/rate-limit.ts +283 -0
- package/src/middleware/rules.ts +475 -0
- package/src/routes/admin-auth.ts +447 -0
- package/src/routes/admin.ts +3501 -0
- package/src/routes/analytics-api.ts +290 -0
- package/src/routes/auth.ts +4222 -0
- package/src/routes/backup.ts +1466 -0
- package/src/routes/config.ts +53 -0
- package/src/routes/d1.ts +109 -0
- package/src/routes/database-live.ts +281 -0
- package/src/routes/functions.ts +155 -0
- package/src/routes/health.ts +32 -0
- package/src/routes/kv.ts +167 -0
- package/src/routes/oauth.ts +1055 -0
- package/src/routes/push.ts +1465 -0
- package/src/routes/room.ts +639 -0
- package/src/routes/schema-endpoint.ts +76 -0
- package/src/routes/sql.ts +176 -0
- package/src/routes/storage.ts +1674 -0
- package/src/routes/tables.ts +699 -0
- package/src/routes/users.ts +21 -0
- package/src/routes/vectorize.ts +372 -0
- package/src/types.ts +99 -0
package/src/lib/hono.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/**
|
|
3
|
+
* EdgeBase OpenAPIHono wrapper — relaxed handler return type.
|
|
4
|
+
*
|
|
5
|
+
* EdgeBase uses a DO-proxy pattern: route handlers forward raw Durable Object
|
|
6
|
+
* responses as `new Response(resp.body, ...)`. The upstream `@hono/zod-openapi`
|
|
7
|
+
* `openapi()` method enforces typed return values (`RouteConfigToTypedResponse`)
|
|
8
|
+
* when routes define response `content` schemas, which conflicts with this
|
|
9
|
+
* proxy pattern.
|
|
10
|
+
*
|
|
11
|
+
* This module re-exports `OpenAPIHono` with a relaxed `openapi()` method that
|
|
12
|
+
* accepts `Response` returns while preserving full request/context typing.
|
|
13
|
+
*
|
|
14
|
+
* All route files should import from this module instead of `@hono/zod-openapi`.
|
|
15
|
+
*/
|
|
16
|
+
import { OpenAPIHono as _OriginalOpenAPIHono, createRoute, z } from '@hono/zod-openapi';
|
|
17
|
+
import type { Env } from '../types.js';
|
|
18
|
+
|
|
19
|
+
/** Standard Hono environment type used by all EdgeBase routes. */
|
|
20
|
+
export type HonoEnv = { Bindings: Env };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Re-export OpenAPIHono with a relaxed `openapi()` signature.
|
|
24
|
+
*
|
|
25
|
+
* EdgeBase routes proxy Durable Object responses as raw `Response` objects.
|
|
26
|
+
* Upstream `@hono/zod-openapi` enforces `RouteConfigToTypedResponse` as the
|
|
27
|
+
* handler return type when routes define response `content` schemas — this
|
|
28
|
+
* conflicts with the DO-proxy pattern.
|
|
29
|
+
*
|
|
30
|
+
* We use `Omit` + intersection to replace only `openapi` while keeping all
|
|
31
|
+
* other Hono methods (`.route()`, `.use()`, `.onError()`, etc.) fully typed.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// Instance type: everything from OpenAPIHono, but openapi() accepts any handler return.
|
|
35
|
+
// Handler parameter typing is preserved via the `c: Context<E>` constraint.
|
|
36
|
+
type RelaxedInstance<E extends Record<string, unknown>> =
|
|
37
|
+
Omit<_OriginalOpenAPIHono<E>, 'openapi' | 'route'> & {
|
|
38
|
+
openapi<R extends import('@hono/zod-openapi').RouteConfig>(
|
|
39
|
+
route: R,
|
|
40
|
+
handler: (c: import('hono').Context<E, any, any>) => any,
|
|
41
|
+
hook?: any,
|
|
42
|
+
): RelaxedInstance<E>;
|
|
43
|
+
route(path: string, app: any): RelaxedInstance<E>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Constructor type: preserves `new <E>(opts?)` while producing RelaxedInstance
|
|
47
|
+
interface RelaxedOpenAPIHonoConstructor {
|
|
48
|
+
new <E extends Record<string, unknown> = Record<string, unknown>>(
|
|
49
|
+
opts?: { defaultHook?: (...args: any[]) => any },
|
|
50
|
+
): RelaxedInstance<E>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const OpenAPIHono: RelaxedOpenAPIHonoConstructor =
|
|
54
|
+
_OriginalOpenAPIHono as unknown as RelaxedOpenAPIHonoConstructor;
|
|
55
|
+
|
|
56
|
+
export { createRoute, z };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
import type { HonoEnv } from './hono.js';
|
|
3
|
+
import type { Env } from '../types.js';
|
|
4
|
+
|
|
5
|
+
export function isTrustedInternalRequestUrl(url: string): boolean {
|
|
6
|
+
try {
|
|
7
|
+
const host = new URL(url).host;
|
|
8
|
+
return host === 'internal' || host === 'do';
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isTrustedInternalContext(c: Pick<Context<HonoEnv>, 'get' | 'req'>): boolean {
|
|
15
|
+
return c.get('isInternalRequest' as never) === true || isTrustedInternalRequestUrl(c.req.url);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildInternalHandlerContext(options: {
|
|
19
|
+
env: Env;
|
|
20
|
+
request: Request;
|
|
21
|
+
body?: Record<string, unknown>;
|
|
22
|
+
executionCtx?: ExecutionContext;
|
|
23
|
+
}): Context<HonoEnv> {
|
|
24
|
+
const fallbackExecutionCtx = options.executionCtx ??
|
|
25
|
+
({ waitUntil() {} } as unknown as ExecutionContext);
|
|
26
|
+
const url = new URL(options.request.url);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
env: options.env,
|
|
30
|
+
executionCtx: fallbackExecutionCtx,
|
|
31
|
+
req: {
|
|
32
|
+
raw: options.request,
|
|
33
|
+
url: options.request.url,
|
|
34
|
+
header: (name: string) => options.request.headers.get(name) ?? undefined,
|
|
35
|
+
json: async () => options.body ?? {},
|
|
36
|
+
query: (name?: string) =>
|
|
37
|
+
name
|
|
38
|
+
? url.searchParams.get(name) ?? undefined
|
|
39
|
+
: Object.fromEntries(url.searchParams.entries()),
|
|
40
|
+
},
|
|
41
|
+
get(key: string) {
|
|
42
|
+
if (key === 'auth') return null;
|
|
43
|
+
if (key === 'isServiceKey') {
|
|
44
|
+
return options.request.headers.get('X-Is-Service-Key') === 'true';
|
|
45
|
+
}
|
|
46
|
+
if (key === 'isInternalRequest') return true;
|
|
47
|
+
return undefined;
|
|
48
|
+
},
|
|
49
|
+
json(payload: unknown, status = 200) {
|
|
50
|
+
return new Response(JSON.stringify(payload), {
|
|
51
|
+
status,
|
|
52
|
+
headers: { 'Content-Type': 'application/json' },
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
} as unknown as Context<HonoEnv>;
|
|
56
|
+
}
|
package/src/lib/jwt.ts
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT utilities — Access Token / Refresh Token / Admin Token
|
|
3
|
+
*
|
|
4
|
+
* Uses `jose` (Web Crypto API native, Cloudflare Workers compatible).
|
|
5
|
+
* HS256 symmetric signing with Workers Secrets.
|
|
6
|
+
*/
|
|
7
|
+
import { SignJWT, jwtVerify } from 'jose';
|
|
8
|
+
|
|
9
|
+
// ─── Types ───
|
|
10
|
+
|
|
11
|
+
export interface AccessTokenPayload {
|
|
12
|
+
sub: string; // userId
|
|
13
|
+
email?: string | null;
|
|
14
|
+
displayName?: string | null;
|
|
15
|
+
role?: string;
|
|
16
|
+
isAnonymous?: boolean;
|
|
17
|
+
custom?: Record<string, unknown>; // customClaims
|
|
18
|
+
jti?: string; // unique token ID — ensures uniqueness within same second
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RefreshTokenPayload {
|
|
22
|
+
sub: string; // userId
|
|
23
|
+
type: 'refresh';
|
|
24
|
+
jti?: string; // unique session ID for token uniqueness
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AdminTokenPayload {
|
|
28
|
+
sub: string; // adminId
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface VerifiedToken {
|
|
32
|
+
sub: string;
|
|
33
|
+
iss: string;
|
|
34
|
+
exp: number;
|
|
35
|
+
iat: number;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Constants ───
|
|
40
|
+
|
|
41
|
+
const USER_ISSUER = 'edgebase:user';
|
|
42
|
+
const ADMIN_ISSUER = 'edgebase:admin';
|
|
43
|
+
const ALGORITHM = 'HS256';
|
|
44
|
+
|
|
45
|
+
// ─── Helpers ───
|
|
46
|
+
|
|
47
|
+
function textToKey(secret: string): Uint8Array {
|
|
48
|
+
return new TextEncoder().encode(secret);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse duration string like '15m', '7d', '1h' to seconds.
|
|
53
|
+
*/
|
|
54
|
+
export function parseDuration(duration: string): number {
|
|
55
|
+
const match = duration.match(/^(\d+)(s|m|h|d)$/);
|
|
56
|
+
if (!match) throw new Error(`Invalid duration: ${duration}`);
|
|
57
|
+
const [, value, unit] = match;
|
|
58
|
+
const num = parseInt(value, 10);
|
|
59
|
+
const MAX_DAYS = 365; // 1 year max
|
|
60
|
+
const seconds = (() => {
|
|
61
|
+
switch (unit) {
|
|
62
|
+
case 's': return num;
|
|
63
|
+
case 'm': return num * 60;
|
|
64
|
+
case 'h': return num * 3600;
|
|
65
|
+
case 'd': return num * 86400;
|
|
66
|
+
default: throw new Error(`Invalid duration unit: ${unit}`);
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
69
|
+
if (seconds > MAX_DAYS * 86400) {
|
|
70
|
+
throw new Error(`Duration exceeds maximum of ${MAX_DAYS} days: ${duration}`);
|
|
71
|
+
}
|
|
72
|
+
return seconds;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── Sign ───
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Sign a user Access Token (short TTL, default 15m).
|
|
79
|
+
*/
|
|
80
|
+
export async function signAccessToken(
|
|
81
|
+
payload: AccessTokenPayload,
|
|
82
|
+
secret: string,
|
|
83
|
+
ttl: string = '15m',
|
|
84
|
+
): Promise<string> {
|
|
85
|
+
const jwt = new SignJWT({
|
|
86
|
+
...payload,
|
|
87
|
+
})
|
|
88
|
+
.setProtectedHeader({ alg: ALGORITHM })
|
|
89
|
+
.setIssuedAt()
|
|
90
|
+
.setIssuer(USER_ISSUER)
|
|
91
|
+
.setSubject(payload.sub)
|
|
92
|
+
.setExpirationTime(ttl)
|
|
93
|
+
.setJti(payload.jti ?? crypto.randomUUID());
|
|
94
|
+
|
|
95
|
+
return jwt.sign(textToKey(secret));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Sign a Refresh Token (long TTL, default 28d).
|
|
100
|
+
* Refresh Token is also JWT — allows Registry-free shardId calculation.
|
|
101
|
+
*/
|
|
102
|
+
export async function signRefreshToken(
|
|
103
|
+
payload: RefreshTokenPayload,
|
|
104
|
+
secret: string,
|
|
105
|
+
ttl: string = '28d',
|
|
106
|
+
): Promise<string> {
|
|
107
|
+
const builder = new SignJWT({
|
|
108
|
+
type: 'refresh',
|
|
109
|
+
})
|
|
110
|
+
.setProtectedHeader({ alg: ALGORITHM })
|
|
111
|
+
.setIssuedAt()
|
|
112
|
+
.setIssuer(USER_ISSUER)
|
|
113
|
+
.setSubject(payload.sub)
|
|
114
|
+
.setExpirationTime(ttl);
|
|
115
|
+
|
|
116
|
+
if (payload.jti) {
|
|
117
|
+
builder.setJti(payload.jti);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return builder.sign(textToKey(secret));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Sign an Admin Access Token.
|
|
125
|
+
*/
|
|
126
|
+
export async function signAdminAccessToken(
|
|
127
|
+
payload: AdminTokenPayload,
|
|
128
|
+
secret: string,
|
|
129
|
+
ttl: string = '1h',
|
|
130
|
+
): Promise<string> {
|
|
131
|
+
const jwt = new SignJWT({ type: 'access' })
|
|
132
|
+
.setProtectedHeader({ alg: ALGORITHM })
|
|
133
|
+
.setIssuedAt()
|
|
134
|
+
.setIssuer(ADMIN_ISSUER)
|
|
135
|
+
.setSubject(payload.sub)
|
|
136
|
+
.setExpirationTime(ttl);
|
|
137
|
+
|
|
138
|
+
return jwt.sign(textToKey(secret));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Sign an Admin Refresh Token.
|
|
143
|
+
*/
|
|
144
|
+
export async function signAdminRefreshToken(
|
|
145
|
+
payload: AdminTokenPayload,
|
|
146
|
+
secret: string,
|
|
147
|
+
ttl: string = '28d',
|
|
148
|
+
): Promise<string> {
|
|
149
|
+
const jwt = new SignJWT({ type: 'refresh' })
|
|
150
|
+
.setProtectedHeader({ alg: ALGORITHM })
|
|
151
|
+
.setIssuedAt()
|
|
152
|
+
.setIssuer(ADMIN_ISSUER)
|
|
153
|
+
.setSubject(payload.sub)
|
|
154
|
+
.setExpirationTime(ttl);
|
|
155
|
+
|
|
156
|
+
return jwt.sign(textToKey(secret));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ─── Verify ───
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Verify and decode a JWT.
|
|
163
|
+
* Returns decoded payload or throws.
|
|
164
|
+
*/
|
|
165
|
+
export async function verifyToken(
|
|
166
|
+
token: string,
|
|
167
|
+
secret: string,
|
|
168
|
+
expectedIssuer?: string,
|
|
169
|
+
): Promise<VerifiedToken> {
|
|
170
|
+
try {
|
|
171
|
+
const { payload } = await jwtVerify(token, textToKey(secret), {
|
|
172
|
+
issuer: expectedIssuer,
|
|
173
|
+
});
|
|
174
|
+
return payload as unknown as VerifiedToken;
|
|
175
|
+
} catch (err: unknown) {
|
|
176
|
+
// Use error `code` property instead of `instanceof` — jose error class
|
|
177
|
+
// identity can break across module boundaries in Workers/vitest-pool-workers.
|
|
178
|
+
const code = (err as { code?: string })?.code;
|
|
179
|
+
if (code === 'ERR_JWT_EXPIRED') {
|
|
180
|
+
throw new TokenExpiredError('Token expired');
|
|
181
|
+
}
|
|
182
|
+
if (code === 'ERR_JWT_CLAIM_VALIDATION_FAILED') {
|
|
183
|
+
throw new TokenInvalidError(`Token claim validation failed: ${(err as Error).message}`);
|
|
184
|
+
}
|
|
185
|
+
throw new TokenInvalidError('Invalid token');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Verify a user Access Token.
|
|
191
|
+
*/
|
|
192
|
+
export async function verifyAccessToken(
|
|
193
|
+
token: string,
|
|
194
|
+
secret: string,
|
|
195
|
+
): Promise<VerifiedToken> {
|
|
196
|
+
const payload = await verifyToken(token, secret, USER_ISSUER);
|
|
197
|
+
if (payload.type === 'refresh') {
|
|
198
|
+
throw new TokenInvalidError('Refresh token cannot be used as access token');
|
|
199
|
+
}
|
|
200
|
+
return payload;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Verify a Refresh Token.
|
|
205
|
+
*/
|
|
206
|
+
export async function verifyRefreshToken(
|
|
207
|
+
token: string,
|
|
208
|
+
secret: string,
|
|
209
|
+
): Promise<VerifiedToken> {
|
|
210
|
+
const payload = await verifyToken(token, secret, USER_ISSUER);
|
|
211
|
+
if (payload.type !== 'refresh') {
|
|
212
|
+
throw new TokenInvalidError('Not a refresh token');
|
|
213
|
+
}
|
|
214
|
+
return payload;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Verify an Admin Token.
|
|
219
|
+
*/
|
|
220
|
+
export async function verifyAdminToken(
|
|
221
|
+
token: string,
|
|
222
|
+
secret: string,
|
|
223
|
+
): Promise<VerifiedToken> {
|
|
224
|
+
const payload = await verifyToken(token, secret, ADMIN_ISSUER);
|
|
225
|
+
// Explicitly reject refresh tokens used as access tokens.
|
|
226
|
+
if (payload.type === 'refresh') {
|
|
227
|
+
throw new TokenInvalidError('Refresh token cannot be used as access token');
|
|
228
|
+
}
|
|
229
|
+
// Legacy tokens (no type field): reject if TTL exceeds 2 hours.
|
|
230
|
+
// Admin access tokens have 1h TTL; refresh tokens have 28d TTL.
|
|
231
|
+
// This catches legacy refresh tokens that lack the type field.
|
|
232
|
+
if (!payload.type && payload.iat && payload.exp) {
|
|
233
|
+
const tokenLifetime = payload.exp - payload.iat;
|
|
234
|
+
const MAX_ACCESS_LIFETIME = 7200; // 2 hours (generous margin over 1h access TTL)
|
|
235
|
+
if (tokenLifetime > MAX_ACCESS_LIFETIME) {
|
|
236
|
+
throw new TokenInvalidError('Legacy token with excessive lifetime rejected');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return payload;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Verify an Admin Refresh Token.
|
|
244
|
+
*/
|
|
245
|
+
export async function verifyAdminRefreshToken(
|
|
246
|
+
token: string,
|
|
247
|
+
secret: string,
|
|
248
|
+
): Promise<VerifiedToken> {
|
|
249
|
+
const payload = await verifyToken(token, secret, ADMIN_ISSUER);
|
|
250
|
+
if (payload.type !== 'refresh') {
|
|
251
|
+
throw new TokenInvalidError('Not a refresh token');
|
|
252
|
+
}
|
|
253
|
+
return payload;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ─── Grace Period Fallback Verify ───
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Verify a Refresh Token with old-key fallback for JWT key rotation grace period.
|
|
260
|
+
* Grace period: 28 days (matches Refresh Token TTL).
|
|
261
|
+
* new key → old key (within 28d) → reject.
|
|
262
|
+
*/
|
|
263
|
+
export async function verifyRefreshTokenWithFallback(
|
|
264
|
+
token: string,
|
|
265
|
+
secret: string,
|
|
266
|
+
oldSecret?: string,
|
|
267
|
+
oldAt?: string,
|
|
268
|
+
): Promise<VerifiedToken> {
|
|
269
|
+
try {
|
|
270
|
+
return await verifyRefreshToken(token, secret);
|
|
271
|
+
} catch (err) {
|
|
272
|
+
// Only fall back to old key on signature mismatch (not expiry)
|
|
273
|
+
if (!(err instanceof TokenExpiredError) && err instanceof TokenInvalidError && oldSecret && oldAt) {
|
|
274
|
+
const elapsed = Date.now() - new Date(oldAt).getTime();
|
|
275
|
+
const GRACE_MS = 28 * 24 * 60 * 60 * 1000;
|
|
276
|
+
if (elapsed <= GRACE_MS) {
|
|
277
|
+
return await verifyRefreshToken(token, oldSecret);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Verify an Admin Token with old-key fallback for JWT key rotation grace period.
|
|
286
|
+
*/
|
|
287
|
+
export async function verifyAdminTokenWithFallback(
|
|
288
|
+
token: string,
|
|
289
|
+
secret: string,
|
|
290
|
+
oldSecret?: string,
|
|
291
|
+
oldAt?: string,
|
|
292
|
+
): Promise<VerifiedToken> {
|
|
293
|
+
try {
|
|
294
|
+
return await verifyAdminToken(token, secret);
|
|
295
|
+
} catch (err) {
|
|
296
|
+
if (!(err instanceof TokenExpiredError) && err instanceof TokenInvalidError && oldSecret && oldAt) {
|
|
297
|
+
const elapsed = Date.now() - new Date(oldAt).getTime();
|
|
298
|
+
const GRACE_MS = 28 * 24 * 60 * 60 * 1000;
|
|
299
|
+
if (elapsed <= GRACE_MS) {
|
|
300
|
+
return await verifyAdminToken(token, oldSecret);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Verify an Admin Refresh Token with old-key fallback for JWT key rotation grace period.
|
|
309
|
+
*/
|
|
310
|
+
export async function verifyAdminRefreshTokenWithFallback(
|
|
311
|
+
token: string,
|
|
312
|
+
secret: string,
|
|
313
|
+
oldSecret?: string,
|
|
314
|
+
oldAt?: string,
|
|
315
|
+
): Promise<VerifiedToken> {
|
|
316
|
+
try {
|
|
317
|
+
return await verifyAdminRefreshToken(token, secret);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
if (!(err instanceof TokenExpiredError) && err instanceof TokenInvalidError && oldSecret && oldAt) {
|
|
320
|
+
const elapsed = Date.now() - new Date(oldAt).getTime();
|
|
321
|
+
const GRACE_MS = 28 * 24 * 60 * 60 * 1000;
|
|
322
|
+
if (elapsed <= GRACE_MS) {
|
|
323
|
+
return await verifyAdminRefreshToken(token, oldSecret);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
throw err;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Decode JWT payload without verification (for extracting userId from Refresh Token).
|
|
333
|
+
* Used for shardId routing where verification happens at the shard level.
|
|
334
|
+
*/
|
|
335
|
+
export function decodeTokenUnsafe(token: string): Record<string, unknown> | null {
|
|
336
|
+
try {
|
|
337
|
+
const parts = token.split('.');
|
|
338
|
+
if (parts.length !== 3) return null;
|
|
339
|
+
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
|
|
340
|
+
return payload;
|
|
341
|
+
} catch {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ─── Error Classes ───
|
|
347
|
+
|
|
348
|
+
export class TokenExpiredError extends Error {
|
|
349
|
+
override name = 'TokenExpiredError';
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export class TokenInvalidError extends Error {
|
|
353
|
+
override name = 'TokenInvalidError';
|
|
354
|
+
}
|