sh3-server 0.8.1 → 0.8.2

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.
@@ -1,30 +1,51 @@
1
1
  // packages/sh3-server/src/shard-router.ts
2
2
  import { Hono } from 'hono';
3
- import { mkdirSync } from 'node:fs';
3
+ import { mkdirSync, readFileSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
+ import { scopeRequired, tenantRequired } from './scope.js';
5
6
  import { pathToFileURL } from 'node:url';
6
- /** Middleware that requires admin session OR valid API key. */
7
- export function adminOnly(keys, settings) {
7
+ import { getSyncBundle, createSyncHandle, createSyncRegistry } from 'sh3-core/server-sync';
8
+ /** Middleware requiring the caller's scope set to include admin:*. */
9
+ export function adminOnly(_keys, settings) {
8
10
  return async (c, next) => {
9
- // When auth is disabled (--no-auth), skip admin checks entirely
10
- if (!settings.get().auth.required) {
11
+ if (!settings.get().auth.required)
11
12
  return next();
12
- }
13
- // Session-based admin — forwarded from upstream sessionAuth via env
14
- const session = c.get('session') ?? c.env?.session;
15
- if (session?.role === 'admin') {
13
+ const caller = c.get('caller');
14
+ if (caller?.scopes.includes('admin:*'))
16
15
  return next();
16
+ return c.json({ error: 'Admin privileges required' }, 403);
17
+ };
18
+ }
19
+ const PERMISSION_DOCUMENTS_SYNC = 'documents:sync';
20
+ /**
21
+ * Build a ServerShardContext object for the given shard, wiring
22
+ * sync/syncRegistry based on the declared permissions.
23
+ */
24
+ export function buildShardCtx(shardId, dataDir, permissions, keys, settings, wsRegister, documentBackend) {
25
+ const hasSync = permissions.includes(PERMISSION_DOCUMENTS_SYNC);
26
+ const ctx = {
27
+ shardId,
28
+ dataDir,
29
+ permissions,
30
+ adminOnly: adminOnly(keys, settings),
31
+ scopeRequired,
32
+ tenantRequired,
33
+ wsRegister,
34
+ };
35
+ ctx.sync = async (tenantId, connectorId) => {
36
+ if (!hasSync) {
37
+ throw new Error(`Shard "${shardId}" cannot call ctx.sync — missing '${PERMISSION_DOCUMENTS_SYNC}' permission in manifest.`);
17
38
  }
18
- // Fallback: API key (for external tools / CLI)
19
- const authHeader = c.req.header('Authorization');
20
- if (authHeader?.startsWith('Bearer sh3_')) {
21
- const token = authHeader.slice(7);
22
- if (keys.validate(token)) {
23
- return next();
24
- }
39
+ const { engine, registry } = await getSyncBundle(documentBackend, tenantId);
40
+ return createSyncHandle({ tenantId, connectorId, engine, registry });
41
+ };
42
+ ctx.syncRegistry = (tenantId) => {
43
+ if (!hasSync) {
44
+ throw new Error(`Shard "${shardId}" cannot call ctx.syncRegistry — missing '${PERMISSION_DOCUMENTS_SYNC}' permission in manifest.`);
25
45
  }
26
- return c.json({ error: 'Admin privileges required' }, 403);
46
+ return createSyncRegistry(documentBackend, tenantId);
27
47
  };
48
+ return ctx;
28
49
  }
29
50
  /**
30
51
  * Dynamic shard route manager. Holds a Map of shard Hono sub-apps
@@ -44,13 +65,17 @@ export class ShardRouter {
44
65
  throw new Error(`${shardId}/server.js invalid export shape — expected { id, routes }`);
45
66
  }
46
67
  const shardDataDir = join(ctx.pkgDir, 'data');
68
+ const manifestPath = join(ctx.pkgDir, 'manifest.json');
69
+ let permissions = [];
70
+ try {
71
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
72
+ permissions = Array.isArray(manifest.permissions) ? manifest.permissions : [];
73
+ }
74
+ catch {
75
+ // Framework built-ins (mountStatic) may have no sibling manifest; default to empty.
76
+ }
47
77
  const router = new Hono();
48
- const shardCtx = {
49
- shardId: shard.id,
50
- dataDir: shardDataDir,
51
- adminOnly: adminOnly(ctx.keys, ctx.settings),
52
- wsRegister: ctx.wsRegister,
53
- };
78
+ const shardCtx = buildShardCtx(shard.id, shardDataDir, permissions, ctx.keys, ctx.settings, ctx.wsRegister, ctx.documentBackend);
54
79
  await shard.routes(router, shardCtx);
55
80
  // Create data dir only after routes() succeeds — so a failure
56
81
  // doesn't leave behind a dir that prevents install rollback cleanup.
@@ -68,13 +93,17 @@ export class ShardRouter {
68
93
  throw new Error(`${shardId} static mount — expected { id, routes }, got ${typeof mod}`);
69
94
  }
70
95
  const shardDataDir = join(ctx.pkgDir, 'data');
96
+ const manifestPath = join(ctx.pkgDir, 'manifest.json');
97
+ let permissions = [];
98
+ try {
99
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
100
+ permissions = Array.isArray(manifest.permissions) ? manifest.permissions : [];
101
+ }
102
+ catch {
103
+ // Framework built-ins (mountStatic) may have no sibling manifest; default to empty.
104
+ }
71
105
  const router = new Hono();
72
- const shardCtx = {
73
- shardId: mod.id,
74
- dataDir: shardDataDir,
75
- adminOnly: adminOnly(ctx.keys, ctx.settings),
76
- wsRegister: ctx.wsRegister,
77
- };
106
+ const shardCtx = buildShardCtx(mod.id, shardDataDir, permissions, ctx.keys, ctx.settings, ctx.wsRegister, ctx.documentBackend);
78
107
  await mod.routes(router, shardCtx);
79
108
  mkdirSync(shardDataDir, { recursive: true });
80
109
  this.shards.set(shardId, router);
@@ -1,5 +1,5 @@
1
1
  import { Hono } from 'hono';
2
- import type { Context } from 'hono';
2
+ import type { Context, MiddlewareHandler } from 'hono';
3
3
  import type { WsLike } from './session-manager.js';
4
4
  export interface ShellServerContext {
5
5
  shardId: string;
@@ -8,6 +8,9 @@ export interface ShellServerContext {
8
8
  tenantRootBase?: string;
9
9
  adminOnly: any;
10
10
  wsRegister: (onConnect: (ws: WsLike, c: Context) => void) => any;
11
+ permissions: string[];
12
+ scopeRequired: (scope: string) => MiddlewareHandler;
13
+ tenantRequired: MiddlewareHandler;
11
14
  }
12
15
  declare const _default: {
13
16
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh3-server",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sh3-server": "dist/cli.js"