@syncular/server-hono 0.0.6-96 → 0.2.0
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 +5 -23
- package/dist/admin-page.d.ts +14 -0
- package/dist/admin-page.js +217 -0
- package/dist/admin.d.ts +39 -0
- package/dist/admin.js +142 -0
- package/dist/index.d.ts +16 -14
- package/dist/index.js +134 -23
- package/package.json +19 -53
- package/src/admin-page.ts +217 -0
- package/src/admin.ts +193 -0
- package/src/index.ts +175 -31
- package/dist/api-key-auth.d.ts +0 -49
- package/dist/api-key-auth.d.ts.map +0 -1
- package/dist/api-key-auth.js +0 -110
- package/dist/api-key-auth.js.map +0 -1
- package/dist/blobs.d.ts +0 -69
- package/dist/blobs.d.ts.map +0 -1
- package/dist/blobs.js +0 -383
- package/dist/blobs.js.map +0 -1
- package/dist/console/gateway.d.ts +0 -33
- package/dist/console/gateway.d.ts.map +0 -1
- package/dist/console/gateway.js +0 -2534
- package/dist/console/gateway.js.map +0 -1
- package/dist/console/index.d.ts +0 -11
- package/dist/console/index.d.ts.map +0 -1
- package/dist/console/index.js +0 -11
- package/dist/console/index.js.map +0 -1
- package/dist/console/routes.d.ts +0 -33
- package/dist/console/routes.d.ts.map +0 -1
- package/dist/console/routes.js +0 -3029
- package/dist/console/routes.js.map +0 -1
- package/dist/console/schemas.d.ts +0 -490
- package/dist/console/schemas.d.ts.map +0 -1
- package/dist/console/schemas.js +0 -303
- package/dist/console/schemas.js.map +0 -1
- package/dist/console/types.d.ts +0 -175
- package/dist/console/types.d.ts.map +0 -1
- package/dist/console/types.js +0 -2
- package/dist/console/types.js.map +0 -1
- package/dist/console/ui.d.ts +0 -38
- package/dist/console/ui.d.ts.map +0 -1
- package/dist/console/ui.js +0 -43
- package/dist/console/ui.js.map +0 -1
- package/dist/create-server.d.ts +0 -68
- package/dist/create-server.d.ts.map +0 -1
- package/dist/create-server.js +0 -110
- package/dist/create-server.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/openapi.d.ts +0 -45
- package/dist/openapi.d.ts.map +0 -1
- package/dist/openapi.js +0 -59
- package/dist/openapi.js.map +0 -1
- package/dist/proxy/connection-manager.d.ts +0 -78
- package/dist/proxy/connection-manager.d.ts.map +0 -1
- package/dist/proxy/connection-manager.js +0 -251
- package/dist/proxy/connection-manager.js.map +0 -1
- package/dist/proxy/index.d.ts +0 -8
- package/dist/proxy/index.d.ts.map +0 -1
- package/dist/proxy/index.js +0 -8
- package/dist/proxy/index.js.map +0 -1
- package/dist/proxy/routes.d.ts +0 -74
- package/dist/proxy/routes.d.ts.map +0 -1
- package/dist/proxy/routes.js +0 -147
- package/dist/proxy/routes.js.map +0 -1
- package/dist/rate-limit.d.ts +0 -101
- package/dist/rate-limit.d.ts.map +0 -1
- package/dist/rate-limit.js +0 -186
- package/dist/rate-limit.js.map +0 -1
- package/dist/routes.d.ts +0 -161
- package/dist/routes.d.ts.map +0 -1
- package/dist/routes.js +0 -1493
- package/dist/routes.js.map +0 -1
- package/dist/ws.d.ts +0 -235
- package/dist/ws.d.ts.map +0 -1
- package/dist/ws.js +0 -614
- package/dist/ws.js.map +0 -1
- package/src/__tests__/blob-routes.test.ts +0 -424
- package/src/__tests__/console-gateway-live-routes.test.ts +0 -297
- package/src/__tests__/console-gateway-routes.test.ts +0 -1364
- package/src/__tests__/console-routes.test.ts +0 -1626
- package/src/__tests__/console-ui.test.ts +0 -114
- package/src/__tests__/create-server.test.ts +0 -485
- package/src/__tests__/pull-chunk-storage.test.ts +0 -576
- package/src/__tests__/rate-limit.test.ts +0 -78
- package/src/__tests__/realtime-bridge.test.ts +0 -135
- package/src/__tests__/sync-rate-limit-routing.test.ts +0 -185
- package/src/__tests__/ws-connection-manager.test.ts +0 -176
- package/src/api-key-auth.ts +0 -179
- package/src/blobs.ts +0 -534
- package/src/console/gateway.ts +0 -3603
- package/src/console/index.ts +0 -11
- package/src/console/routes.ts +0 -3936
- package/src/console/schemas.ts +0 -434
- package/src/console/types.ts +0 -185
- package/src/console/ui.ts +0 -100
- package/src/create-server.ts +0 -207
- package/src/openapi.ts +0 -74
- package/src/proxy/connection-manager.ts +0 -340
- package/src/proxy/index.ts +0 -8
- package/src/proxy/routes.ts +0 -223
- package/src/rate-limit.ts +0 -321
- package/src/routes.ts +0 -2059
- package/src/ws.ts +0 -802
package/src/index.ts
CHANGED
|
@@ -1,42 +1,186 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Hono adapter (REVISE B2): a thin wrapper proving the embed boundary.
|
|
3
|
+
* Hono is a dependency of this adapter only, never of the server core.
|
|
4
|
+
* Mounts the §1.1 routes: POST /sync and GET /segments/:segmentId
|
|
5
|
+
* (realtime upgrades are runtime-specific and stay with the host).
|
|
6
6
|
*/
|
|
7
|
+
import {
|
|
8
|
+
encodeSegmentBody,
|
|
9
|
+
errorBody,
|
|
10
|
+
handleBlobDownload,
|
|
11
|
+
handleBlobUpload,
|
|
12
|
+
handleBlobUploadGrant,
|
|
13
|
+
handleSegmentDownload,
|
|
14
|
+
handleSyncRequest,
|
|
15
|
+
SSP2_CONTENT_TYPE,
|
|
16
|
+
SyncError,
|
|
17
|
+
type SyncServerConfig,
|
|
18
|
+
} from '@syncular/server';
|
|
19
|
+
import { Hono } from 'hono';
|
|
7
20
|
|
|
8
|
-
|
|
9
|
-
export * from './api-key-auth';
|
|
21
|
+
export * from './admin';
|
|
10
22
|
|
|
11
|
-
|
|
12
|
-
|
|
23
|
+
export interface SyncularHonoOptions {
|
|
24
|
+
readonly config: SyncServerConfig;
|
|
25
|
+
/** Host authentication (§1.1); `null` ⇒ 401 `sync.auth_required`. */
|
|
26
|
+
readonly authenticate: (
|
|
27
|
+
request: Request,
|
|
28
|
+
) => Promise<{ actorId: string; partition: string } | null>;
|
|
29
|
+
}
|
|
13
30
|
|
|
14
|
-
|
|
15
|
-
|
|
31
|
+
function errorResponse(error: unknown): Response {
|
|
32
|
+
const sync =
|
|
33
|
+
error instanceof SyncError
|
|
34
|
+
? error
|
|
35
|
+
: new SyncError('sync.invalid_request', String(error));
|
|
36
|
+
return Response.json(errorBody(sync), { status: sync.httpStatus });
|
|
37
|
+
}
|
|
16
38
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
createSyncServer,
|
|
20
|
-
type SyncServerOptions,
|
|
21
|
-
type SyncServerResult,
|
|
22
|
-
} from './create-server';
|
|
39
|
+
export function createSyncularHono(options: SyncularHonoOptions): Hono {
|
|
40
|
+
const app = new Hono();
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
app.post('/sync', async (c) => {
|
|
43
|
+
const contentType = c.req.header('content-type')?.split(';')[0]?.trim();
|
|
44
|
+
if (contentType !== SSP2_CONTENT_TYPE) {
|
|
45
|
+
// §1.1: any other content type is rejected with HTTP 415.
|
|
46
|
+
return Response.json(
|
|
47
|
+
errorBody(
|
|
48
|
+
new SyncError('sync.invalid_request', 'unsupported content type'),
|
|
49
|
+
),
|
|
50
|
+
{ status: 415 },
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const auth = await options.authenticate(c.req.raw);
|
|
54
|
+
if (auth === null)
|
|
55
|
+
return errorResponse(new SyncError('sync.auth_required'));
|
|
56
|
+
try {
|
|
57
|
+
const bytes = new Uint8Array(await c.req.arrayBuffer());
|
|
58
|
+
const out = await handleSyncRequest(bytes, {
|
|
59
|
+
...options.config,
|
|
60
|
+
...auth,
|
|
61
|
+
});
|
|
62
|
+
return c.body(out.slice().buffer as ArrayBuffer, 200, {
|
|
63
|
+
'Content-Type': SSP2_CONTENT_TYPE,
|
|
64
|
+
});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return errorResponse(error);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
26
69
|
|
|
27
|
-
|
|
28
|
-
|
|
70
|
+
app.get('/segments/:segmentId', async (c) => {
|
|
71
|
+
const auth = await options.authenticate(c.req.raw);
|
|
72
|
+
if (auth === null)
|
|
73
|
+
return errorResponse(new SyncError('sync.auth_required'));
|
|
74
|
+
try {
|
|
75
|
+
const result = await handleSegmentDownload(
|
|
76
|
+
{ ...options.config, ...auth },
|
|
77
|
+
{
|
|
78
|
+
segmentId: c.req.param('segmentId'),
|
|
79
|
+
scopesHeader: c.req.header('x-syncular-scopes') ?? '{}',
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
if (c.req.header('if-none-match') === result.headers.ETag) {
|
|
83
|
+
return c.body(null, 304, result.headers);
|
|
84
|
+
}
|
|
85
|
+
// §5.8 shipped default: compress the body per Accept-Encoding
|
|
86
|
+
// (zstd preferred, gzip fallback, identity otherwise). Content
|
|
87
|
+
// addresses are over the uncompressed bytes (§5.1) — fetch
|
|
88
|
+
// decodes transparently on the client.
|
|
89
|
+
const encoded = encodeSegmentBody(
|
|
90
|
+
result.bytes,
|
|
91
|
+
c.req.header('accept-encoding'),
|
|
92
|
+
);
|
|
93
|
+
return c.body(encoded.bytes.slice().buffer as ArrayBuffer, 200, {
|
|
94
|
+
...result.headers,
|
|
95
|
+
...(encoded.contentEncoding !== undefined
|
|
96
|
+
? { 'Content-Encoding': encoded.contentEncoding }
|
|
97
|
+
: {}),
|
|
98
|
+
});
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return errorResponse(error);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
29
103
|
|
|
30
|
-
//
|
|
31
|
-
|
|
104
|
+
// §5.9.3: blob upload with server-side content-address verification.
|
|
105
|
+
app.put('/blobs/:blobId', async (c) => {
|
|
106
|
+
const auth = await options.authenticate(c.req.raw);
|
|
107
|
+
if (auth === null)
|
|
108
|
+
return errorResponse(new SyncError('sync.auth_required'));
|
|
109
|
+
try {
|
|
110
|
+
const bytes = new Uint8Array(await c.req.arrayBuffer());
|
|
111
|
+
const contentType = c.req.header('content-type')?.split(';')[0]?.trim();
|
|
112
|
+
await handleBlobUpload(
|
|
113
|
+
{ ...options.config, ...auth },
|
|
114
|
+
{
|
|
115
|
+
blobId: c.req.param('blobId'),
|
|
116
|
+
bytes,
|
|
117
|
+
...(contentType !== undefined &&
|
|
118
|
+
contentType !== 'application/octet-stream'
|
|
119
|
+
? { mediaType: contentType }
|
|
120
|
+
: {}),
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
return c.body(null, 200);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return errorResponse(error);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
32
128
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
129
|
+
// §5.9.3: presigned-upload grant — mint a direct-to-storage PUT URL.
|
|
130
|
+
app.post('/blobs/:blobId/upload-grant', async (c) => {
|
|
131
|
+
const auth = await options.authenticate(c.req.raw);
|
|
132
|
+
if (auth === null)
|
|
133
|
+
return errorResponse(new SyncError('sync.auth_required'));
|
|
134
|
+
try {
|
|
135
|
+
const body = (await c.req.json().catch(() => ({}))) as {
|
|
136
|
+
byteLength?: number;
|
|
137
|
+
mediaType?: string;
|
|
138
|
+
};
|
|
139
|
+
const grant = await handleBlobUploadGrant(
|
|
140
|
+
{ ...options.config, ...auth },
|
|
141
|
+
{
|
|
142
|
+
blobId: c.req.param('blobId'),
|
|
143
|
+
byteLength: Number(body.byteLength ?? 0),
|
|
144
|
+
...(typeof body.mediaType === 'string'
|
|
145
|
+
? { mediaType: body.mediaType }
|
|
146
|
+
: {}),
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
return c.json(grant, 200);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return errorResponse(error);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
40
154
|
|
|
41
|
-
//
|
|
42
|
-
|
|
155
|
+
// §5.9.5: blob download, re-authorized against referencing rows. When the
|
|
156
|
+
// host configured presigned URLs, the result carries `url` (no bytes) and
|
|
157
|
+
// the client fetches it directly (§5.9.5 always-issue).
|
|
158
|
+
app.get('/blobs/:blobId', async (c) => {
|
|
159
|
+
const auth = await options.authenticate(c.req.raw);
|
|
160
|
+
if (auth === null)
|
|
161
|
+
return errorResponse(new SyncError('sync.auth_required'));
|
|
162
|
+
try {
|
|
163
|
+
const result = await handleBlobDownload(
|
|
164
|
+
{ ...options.config, ...auth },
|
|
165
|
+
c.req.param('blobId'),
|
|
166
|
+
);
|
|
167
|
+
if (result.url !== undefined) {
|
|
168
|
+
return c.json(
|
|
169
|
+
{ url: result.url, urlExpiresAtMs: result.urlExpiresAtMs },
|
|
170
|
+
200,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (c.req.header('if-none-match') === result.headers.ETag) {
|
|
174
|
+
return c.body(null, 304, result.headers);
|
|
175
|
+
}
|
|
176
|
+
const bytes = result.bytes ?? new Uint8Array(0);
|
|
177
|
+
return c.body(bytes.slice().buffer as ArrayBuffer, 200, {
|
|
178
|
+
...result.headers,
|
|
179
|
+
});
|
|
180
|
+
} catch (error) {
|
|
181
|
+
return errorResponse(error);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return app;
|
|
186
|
+
}
|
package/dist/api-key-auth.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/server-hono - API Key Authentication Helper
|
|
3
|
-
*
|
|
4
|
-
* Provides utilities for validating API keys in relay/proxy routes.
|
|
5
|
-
*/
|
|
6
|
-
import type { ServerSyncDialect } from '@syncular/server';
|
|
7
|
-
import type { Context } from 'hono';
|
|
8
|
-
import { type Kysely } from 'kysely';
|
|
9
|
-
import { type ApiKeyType } from './console/schemas';
|
|
10
|
-
interface SyncApiKeysTable {
|
|
11
|
-
key_id: string;
|
|
12
|
-
key_hash: string;
|
|
13
|
-
key_prefix: string;
|
|
14
|
-
name: string;
|
|
15
|
-
key_type: string;
|
|
16
|
-
scope_keys: unknown | null;
|
|
17
|
-
actor_id: string | null;
|
|
18
|
-
created_at: string;
|
|
19
|
-
expires_at: string | null;
|
|
20
|
-
last_used_at: string | null;
|
|
21
|
-
revoked_at: string | null;
|
|
22
|
-
}
|
|
23
|
-
type ApiKeyDb = {
|
|
24
|
-
sync_api_keys: SyncApiKeysTable;
|
|
25
|
-
};
|
|
26
|
-
interface ValidateApiKeyResult {
|
|
27
|
-
keyId: string;
|
|
28
|
-
keyType: ApiKeyType;
|
|
29
|
-
actorId: string | null;
|
|
30
|
-
scopeKeys: string[];
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Validates an API key from Authorization header.
|
|
34
|
-
* Updates last_used_at on successful validation.
|
|
35
|
-
*/
|
|
36
|
-
export declare function validateApiKey<DB extends ApiKeyDb>(db: Kysely<DB>, dialect: ServerSyncDialect, authHeader: string | undefined): Promise<ValidateApiKeyResult | null>;
|
|
37
|
-
/**
|
|
38
|
-
* Creates an authenticator for relay/proxy routes.
|
|
39
|
-
* Returns actorId from the API key if valid and allowed.
|
|
40
|
-
*/
|
|
41
|
-
export declare function createApiKeyAuthenticator<DB extends ApiKeyDb>(db: Kysely<DB>, dialect: ServerSyncDialect, allowedTypes: ApiKeyType[]): (c: Context) => Promise<{
|
|
42
|
-
actorId: string;
|
|
43
|
-
} | null>;
|
|
44
|
-
/**
|
|
45
|
-
* Middleware that validates API key and attaches result to context.
|
|
46
|
-
*/
|
|
47
|
-
export declare function apiKeyAuthMiddleware<DB extends ApiKeyDb>(db: Kysely<DB>, dialect: ServerSyncDialect, allowedTypes: ApiKeyType[]): (c: Context<any, any, {}>, next: () => Promise<void>) => Promise<Response | undefined>;
|
|
48
|
-
export {};
|
|
49
|
-
//# sourceMappingURL=api-key-auth.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-key-auth.d.ts","sourceRoot":"","sources":["../src/api-key-auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,KAAK,MAAM,EAAO,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,mBAAmB,CAAC;AAEtE,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,KAAK,QAAQ,GAAG;IACd,aAAa,EAAE,gBAAgB,CAAC;CACjC,CAAC;AAEF,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,EAAE,SAAS,QAAQ,EACtD,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EACd,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAkEtC;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,SAAS,QAAQ,EAC3D,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EACd,OAAO,EAAE,iBAAiB,EAC1B,YAAY,EAAE,UAAU,EAAE,GACzB,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAmBrD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,QAAQ,EACtD,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EACd,OAAO,EAAE,iBAAiB,EAC1B,YAAY,EAAE,UAAU,EAAE,0FAsB3B"}
|
package/dist/api-key-auth.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/server-hono - API Key Authentication Helper
|
|
3
|
-
*
|
|
4
|
-
* Provides utilities for validating API keys in relay/proxy routes.
|
|
5
|
-
*/
|
|
6
|
-
import { sql } from 'kysely';
|
|
7
|
-
import { ApiKeyTypeSchema } from './console/schemas.js';
|
|
8
|
-
/**
|
|
9
|
-
* Validates an API key from Authorization header.
|
|
10
|
-
* Updates last_used_at on successful validation.
|
|
11
|
-
*/
|
|
12
|
-
export async function validateApiKey(db, dialect, authHeader) {
|
|
13
|
-
if (!authHeader?.startsWith('Bearer ')) {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
const secretKey = authHeader.slice(7);
|
|
17
|
-
if (!secretKey || !secretKey.startsWith('sk_')) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
// Hash the provided key
|
|
21
|
-
const keyHash = await hashApiKey(secretKey);
|
|
22
|
-
// Look up key by hash
|
|
23
|
-
const rowResult = await sql `
|
|
24
|
-
select key_id, key_type, actor_id, scope_keys, expires_at, revoked_at
|
|
25
|
-
from ${sql.table('sync_api_keys')}
|
|
26
|
-
where key_hash = ${keyHash}
|
|
27
|
-
limit 1
|
|
28
|
-
`.execute(db);
|
|
29
|
-
const row = rowResult.rows[0];
|
|
30
|
-
if (!row) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
const parsedKeyType = ApiKeyTypeSchema.safeParse(row.key_type);
|
|
34
|
-
if (!parsedKeyType.success)
|
|
35
|
-
return null;
|
|
36
|
-
// Check if revoked
|
|
37
|
-
if (row.revoked_at) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
// Check if expired
|
|
41
|
-
if (row.expires_at) {
|
|
42
|
-
const expiresAt = new Date(row.expires_at);
|
|
43
|
-
if (expiresAt < new Date()) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
// Update last_used_at
|
|
48
|
-
const now = new Date().toISOString();
|
|
49
|
-
await sql `
|
|
50
|
-
update ${sql.table('sync_api_keys')}
|
|
51
|
-
set last_used_at = ${now}
|
|
52
|
-
where key_id = ${row.key_id}
|
|
53
|
-
`.execute(db);
|
|
54
|
-
// Parse scopes
|
|
55
|
-
const scopeKeys = dialect.dbToArray(row.scope_keys);
|
|
56
|
-
return {
|
|
57
|
-
keyId: row.key_id,
|
|
58
|
-
keyType: parsedKeyType.data,
|
|
59
|
-
actorId: row.actor_id,
|
|
60
|
-
scopeKeys,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Creates an authenticator for relay/proxy routes.
|
|
65
|
-
* Returns actorId from the API key if valid and allowed.
|
|
66
|
-
*/
|
|
67
|
-
export function createApiKeyAuthenticator(db, dialect, allowedTypes) {
|
|
68
|
-
return async (c) => {
|
|
69
|
-
const authHeader = c.req.header('Authorization');
|
|
70
|
-
const result = await validateApiKey(db, dialect, authHeader);
|
|
71
|
-
if (!result) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
// Check if key type is allowed
|
|
75
|
-
if (!allowedTypes.includes(result.keyType)) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
// Return actorId (use key's actorId if set, otherwise use a default)
|
|
79
|
-
return {
|
|
80
|
-
actorId: result.actorId ?? `api-key:${result.keyId}`,
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Middleware that validates API key and attaches result to context.
|
|
86
|
-
*/
|
|
87
|
-
export function apiKeyAuthMiddleware(db, dialect, allowedTypes) {
|
|
88
|
-
return async (c, next) => {
|
|
89
|
-
const authHeader = c.req.header('Authorization');
|
|
90
|
-
const result = await validateApiKey(db, dialect, authHeader);
|
|
91
|
-
if (!result) {
|
|
92
|
-
return c.json({ error: 'UNAUTHENTICATED' }, 401);
|
|
93
|
-
}
|
|
94
|
-
if (!allowedTypes.includes(result.keyType)) {
|
|
95
|
-
return c.json({ error: 'FORBIDDEN', message: 'Invalid key type' }, 403);
|
|
96
|
-
}
|
|
97
|
-
// Attach to context for downstream handlers
|
|
98
|
-
c.set('apiKey', result);
|
|
99
|
-
await next();
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
// Hash function (same as in routes.ts)
|
|
103
|
-
async function hashApiKey(secretKey) {
|
|
104
|
-
const encoder = new TextEncoder();
|
|
105
|
-
const data = encoder.encode(secretKey);
|
|
106
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
107
|
-
const hashArray = new Uint8Array(hashBuffer);
|
|
108
|
-
return Array.from(hashArray, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
109
|
-
}
|
|
110
|
-
//# sourceMappingURL=api-key-auth.js.map
|
package/dist/api-key-auth.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-key-auth.js","sourceRoot":"","sources":["../src/api-key-auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAe,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAmB,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AA2BtE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,EAAc,EACd,OAA0B,EAC1B,UAA8B,EACQ;IACtC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtC,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,wBAAwB;IACxB,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,CAAC;IAE5C,sBAAsB;IACtB,MAAM,SAAS,GAAG,MAAM,GAAG,CAOzB;;WAEO,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC;uBACd,OAAO;;GAE3B,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACd,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE9B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,aAAa,GAAG,gBAAgB,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC/D,IAAI,CAAC,aAAa,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAExC,mBAAmB;IACnB,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,mBAAmB;IACnB,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QACnB,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC3C,IAAI,SAAS,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,sBAAsB;IACtB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,GAAG,CAAA;aACE,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC;yBACd,GAAG;qBACP,GAAG,CAAC,MAAM;GAC5B,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAEd,eAAe;IACf,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAEpD,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,MAAM;QACjB,OAAO,EAAE,aAAa,CAAC,IAAI;QAC3B,OAAO,EAAE,GAAG,CAAC,QAAQ;QACrB,SAAS;KACV,CAAC;AAAA,CACH;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CACvC,EAAc,EACd,OAA0B,EAC1B,YAA0B,EAC2B;IACrD,OAAO,KAAK,EAAE,CAAU,EAAE,EAAE,CAAC;QAC3B,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;QAE7D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC;QACd,CAAC;QAED,+BAA+B;QAC/B,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,qEAAqE;QACrE,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,WAAW,MAAM,CAAC,KAAK,EAAE;SACrD,CAAC;IAAA,CACH,CAAC;AAAA,CACH;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAClC,EAAc,EACd,OAA0B,EAC1B,YAA0B,EAC1B;IACA,OAAO,KAAK,EACV,CAAU,EACV,IAAyB,EACM,EAAE,CAAC;QAClC,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;QAE7D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,GAAG,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3C,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,GAAG,CAAC,CAAC;QAC1E,CAAC;QAED,4CAA4C;QAC5C,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAExB,MAAM,IAAI,EAAE,CAAC;IAAA,CACd,CAAC;AAAA,CACH;AAED,uCAAuC;AACvC,KAAK,UAAU,UAAU,CAAC,SAAiB,EAAmB;IAC5D,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;IAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAAA,CAC/E"}
|
package/dist/blobs.d.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/server-hono - Blob routes for media/binary handling
|
|
3
|
-
*
|
|
4
|
-
* Provides:
|
|
5
|
-
* - POST /blobs/upload - Initiate a blob upload (get presigned URL)
|
|
6
|
-
* - POST /blobs/:hash/complete - Complete a blob upload
|
|
7
|
-
* - GET /blobs/:hash/url - Get a presigned download URL
|
|
8
|
-
* - PUT /blobs/:hash/upload - Direct upload (for database adapter)
|
|
9
|
-
* - GET /blobs/:hash/download - Direct download (for database adapter)
|
|
10
|
-
*/
|
|
11
|
-
import type { BlobManager } from '@syncular/server';
|
|
12
|
-
import { type BlobTokenSigner, type SyncBlobsDb } from '@syncular/server';
|
|
13
|
-
import type { Context } from 'hono';
|
|
14
|
-
import { Hono } from 'hono';
|
|
15
|
-
import type { Kysely } from 'kysely';
|
|
16
|
-
interface BlobAuthResult {
|
|
17
|
-
actorId: string;
|
|
18
|
-
}
|
|
19
|
-
export interface CreateBlobRoutesOptions<DB extends SyncBlobsDb = SyncBlobsDb> {
|
|
20
|
-
/** Blob manager instance */
|
|
21
|
-
blobManager: BlobManager;
|
|
22
|
-
/** Authentication function */
|
|
23
|
-
authenticate: (c: Context) => Promise<BlobAuthResult | null>;
|
|
24
|
-
/**
|
|
25
|
-
* Token signer for database adapter direct uploads/downloads.
|
|
26
|
-
* Required if using the database blob storage adapter.
|
|
27
|
-
*/
|
|
28
|
-
tokenSigner?: BlobTokenSigner;
|
|
29
|
-
/**
|
|
30
|
-
* Database instance for direct blob storage.
|
|
31
|
-
* Required if using the database blob storage adapter.
|
|
32
|
-
*/
|
|
33
|
-
db?: Kysely<DB>;
|
|
34
|
-
/**
|
|
35
|
-
* Optional: Check if actor can access a blob.
|
|
36
|
-
* By default, any authenticated actor can access any completed blob.
|
|
37
|
-
* Provide this to implement scope-based access control.
|
|
38
|
-
*/
|
|
39
|
-
canAccessBlob?: (args: {
|
|
40
|
-
actorId: string;
|
|
41
|
-
hash: string;
|
|
42
|
-
}) => Promise<boolean>;
|
|
43
|
-
/**
|
|
44
|
-
* Maximum upload size in bytes.
|
|
45
|
-
* Default: 100MB (104857600)
|
|
46
|
-
*/
|
|
47
|
-
maxUploadSize?: number;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Create blob routes for Hono.
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```typescript
|
|
54
|
-
* const blobRoutes = createBlobRoutes({
|
|
55
|
-
* blobManager,
|
|
56
|
-
* authenticate: async (c) => {
|
|
57
|
-
* const token = c.req.header('Authorization')?.replace('Bearer ', '');
|
|
58
|
-
* if (!token) return null;
|
|
59
|
-
* const user = await verifyToken(token);
|
|
60
|
-
* return user ? { actorId: user.id } : null;
|
|
61
|
-
* },
|
|
62
|
-
* });
|
|
63
|
-
*
|
|
64
|
-
* app.route('/api/sync', blobRoutes);
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
export declare function createBlobRoutes<DB extends SyncBlobsDb>(options: CreateBlobRoutesOptions<DB>): Hono;
|
|
68
|
-
export {};
|
|
69
|
-
//# sourceMappingURL=blobs.d.ts.map
|
package/dist/blobs.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"blobs.d.ts","sourceRoot":"","sources":["../src/blobs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,OAAO,KAAK,EACV,WAAW,EAGZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,KAAK,eAAe,EAEpB,KAAK,WAAW,EAEjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGrC,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB,CAAC,EAAE,SAAS,WAAW,GAAG,WAAW;IAC3E,4BAA4B;IAC5B,WAAW,EAAE,WAAW,CAAC;IACzB,8BAA8B;IAC9B,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC7D;;;OAGG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IAChB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAUD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,SAAS,WAAW,EACrD,OAAO,EAAE,uBAAuB,CAAC,EAAE,CAAC,GACnC,IAAI,CAwZN"}
|