sh3-server 0.19.6 → 0.20.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/app/assets/{icons-nOyIoORC.svg → icons-OMmH0JiM.svg} +5 -0
- package/app/assets/index-CgB99H18.js +21 -0
- package/app/assets/index-CgB99H18.js.map +1 -0
- package/app/assets/index-D0Q9JGHo.css +1 -0
- package/app/index.html +2 -2
- package/dist/auth.d.ts +7 -11
- package/dist/auth.js +7 -19
- package/dist/cli.js +2 -2
- package/dist/doc-store/store.d.ts +6 -0
- package/dist/doc-store/store.js +108 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +21 -12
- package/dist/middleware/project-allowlist.d.ts +4 -0
- package/dist/middleware/project-allowlist.js +26 -12
- package/dist/mounts/resolver.d.ts +21 -0
- package/dist/mounts/resolver.js +41 -0
- package/dist/mounts/routes.d.ts +4 -0
- package/dist/mounts/routes.js +136 -0
- package/dist/mounts/store.d.ts +30 -0
- package/dist/mounts/store.js +115 -0
- package/dist/routes/admin.d.ts +3 -1
- package/dist/routes/admin.js +6 -1
- package/dist/routes/boot.d.ts +7 -1
- package/dist/routes/boot.js +13 -4
- package/dist/routes/docs.js +27 -2
- package/dist/routes/projects.d.ts +1 -3
- package/dist/routes/projects.js +1 -3
- package/dist/scope.d.ts +1 -2
- package/dist/scope.js +1 -5
- package/dist/settings.d.ts +0 -1
- package/dist/settings.js +0 -4
- package/dist/shard-router.d.ts +1 -1
- package/dist/shard-router.js +23 -4
- package/dist/tenant-fs/http.d.ts +0 -2
- package/dist/tenant-fs/http.js +1 -1
- package/dist/tenant-fs/session-required.d.ts +1 -3
- package/dist/tenant-fs/session-required.js +1 -4
- package/dist/users.d.ts +14 -1
- package/dist/users.js +34 -0
- package/package.json +1 -1
- package/app/assets/index--m0u3gjJ.js +0 -21
- package/app/assets/index--m0u3gjJ.js.map +0 -1
- package/app/assets/index-DIpoXNrk.css +0 -1
package/dist/scope.d.ts
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* unless their accessibleScopes list includes the requested scope.
|
|
11
11
|
*/
|
|
12
12
|
import type { MiddlewareHandler } from 'hono';
|
|
13
|
-
import type { SettingsStore } from './settings.js';
|
|
14
13
|
export declare function scopeRequired(scope: string): MiddlewareHandler;
|
|
15
14
|
export declare const requireCallerScope: MiddlewareHandler;
|
|
16
|
-
export declare function scopeAccessMatch(paramName: string
|
|
15
|
+
export declare function scopeAccessMatch(paramName: string): MiddlewareHandler;
|
package/dist/scope.js
CHANGED
|
@@ -28,12 +28,8 @@ export const requireCallerScope = async (c, next) => {
|
|
|
28
28
|
return c.json({ error: 'Scope-bound credentials required' }, 401);
|
|
29
29
|
return next();
|
|
30
30
|
};
|
|
31
|
-
export function scopeAccessMatch(paramName
|
|
31
|
+
export function scopeAccessMatch(paramName) {
|
|
32
32
|
return async (c, next) => {
|
|
33
|
-
// Open / no-auth mode: admin has explicitly disabled scope isolation.
|
|
34
|
-
// Mirrors sessionAuth's open-mode bypass; required for Tauri sidecar mode.
|
|
35
|
-
if (settings && !settings.get().auth.required)
|
|
36
|
-
return next();
|
|
37
33
|
const caller = c.get('caller');
|
|
38
34
|
if (!caller)
|
|
39
35
|
return c.json({ error: 'Caller not resolved' }, 500);
|
package/dist/settings.d.ts
CHANGED
package/dist/settings.js
CHANGED
|
@@ -7,7 +7,6 @@ import { dirname } from 'node:path';
|
|
|
7
7
|
const MAX_PACKAGE_CACHE_AGE = 31536000; // 1 year
|
|
8
8
|
const DEFAULTS = {
|
|
9
9
|
auth: {
|
|
10
|
-
required: true,
|
|
11
10
|
guestAllowed: false,
|
|
12
11
|
sessionTTL: 24,
|
|
13
12
|
selfRegistration: false,
|
|
@@ -44,7 +43,6 @@ export class SettingsStore {
|
|
|
44
43
|
const raw = JSON.parse(readFileSync(this.#path, 'utf-8'));
|
|
45
44
|
return {
|
|
46
45
|
auth: {
|
|
47
|
-
required: raw.auth?.required ?? DEFAULTS.auth.required,
|
|
48
46
|
guestAllowed: raw.auth?.guestAllowed ?? DEFAULTS.auth.guestAllowed,
|
|
49
47
|
sessionTTL: raw.auth?.sessionTTL ?? DEFAULTS.auth.sessionTTL,
|
|
50
48
|
selfRegistration: raw.auth?.selfRegistration ?? DEFAULTS.auth.selfRegistration,
|
|
@@ -72,8 +70,6 @@ export class SettingsStore {
|
|
|
72
70
|
/** Patch settings. Only provided fields are updated. */
|
|
73
71
|
update(patch) {
|
|
74
72
|
if (patch.auth) {
|
|
75
|
-
if (patch.auth.required !== undefined)
|
|
76
|
-
this.#settings.auth.required = patch.auth.required;
|
|
77
73
|
if (patch.auth.guestAllowed !== undefined)
|
|
78
74
|
this.#settings.auth.guestAllowed = patch.auth.guestAllowed;
|
|
79
75
|
if (patch.auth.sessionTTL !== undefined)
|
package/dist/shard-router.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface MountContext {
|
|
|
48
48
|
wsRegister(onConnect: (ws: any, c: any) => void): any;
|
|
49
49
|
}
|
|
50
50
|
/** Middleware requiring the caller's scope set to include admin:*. */
|
|
51
|
-
export declare function adminOnly(
|
|
51
|
+
export declare function adminOnly(): MiddlewareHandler;
|
|
52
52
|
/**
|
|
53
53
|
* Dynamic shard route manager. Holds a Map of shard Hono sub-apps
|
|
54
54
|
* and delegates requests from a single wildcard route.
|
package/dist/shard-router.js
CHANGED
|
@@ -5,10 +5,8 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { scopeRequired, requireCallerScope } from './scope.js';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
7
|
/** Middleware requiring the caller's scope set to include admin:*. */
|
|
8
|
-
export function adminOnly(
|
|
8
|
+
export function adminOnly() {
|
|
9
9
|
return async (c, next) => {
|
|
10
|
-
if (!settings.get().auth.required)
|
|
11
|
-
return next();
|
|
12
10
|
const caller = c.get('caller');
|
|
13
11
|
if (caller?.scopes.includes('admin:*'))
|
|
14
12
|
return next();
|
|
@@ -154,6 +152,26 @@ export class ShardRouter {
|
|
|
154
152
|
// Framework built-ins (mountStatic) may have no sibling manifest; default to empty.
|
|
155
153
|
}
|
|
156
154
|
const docStore = ctx.docStore;
|
|
155
|
+
// Symmetric path translator: same (shardId, path) pair that documents()
|
|
156
|
+
// accepts. `mounts/*` walks the MountedPathResolver wired on the docStore;
|
|
157
|
+
// any other shardId resolves to the canonical native-doc location under
|
|
158
|
+
// the host's docs dir. Throws on unresolvable mounts so callers can just
|
|
159
|
+
// hand the result to spawn/streams without branching.
|
|
160
|
+
const resolveFsPath = (tenant, targetShardId, path) => {
|
|
161
|
+
if (targetShardId === 'mounts') {
|
|
162
|
+
const resolver = docStore.mountResolver;
|
|
163
|
+
if (!resolver)
|
|
164
|
+
throw new Error('Mount resolver not configured on docStore');
|
|
165
|
+
const docPath = path ? `mounts/${path}` : 'mounts';
|
|
166
|
+
const resolved = resolver.resolve(tenant, docPath);
|
|
167
|
+
if (resolved.kind === 'mount')
|
|
168
|
+
return resolved.realPath;
|
|
169
|
+
if (resolved.kind === 'mount-unresolved')
|
|
170
|
+
throw new Error(resolved.error);
|
|
171
|
+
throw new Error(`Invalid mount path: ${docPath}`);
|
|
172
|
+
}
|
|
173
|
+
return join(docStore.dataDir, 'docs', tenant, targetShardId, path);
|
|
174
|
+
};
|
|
157
175
|
// Hono's MiddlewareHandler uses a concrete Context generic that isn't
|
|
158
176
|
// assignable to sh3-core's framework-agnostic stand-in (c: unknown).
|
|
159
177
|
// Cast once at assembly — shards only call the handlers, never introspect.
|
|
@@ -161,7 +179,7 @@ export class ShardRouter {
|
|
|
161
179
|
shardId,
|
|
162
180
|
dataDir: shardDataDir,
|
|
163
181
|
permissions,
|
|
164
|
-
adminOnly: adminOnly(
|
|
182
|
+
adminOnly: adminOnly(),
|
|
165
183
|
scopeRequired,
|
|
166
184
|
tenantRequired: requireCallerScope,
|
|
167
185
|
wsRegister: ctx.wsRegister,
|
|
@@ -172,6 +190,7 @@ export class ShardRouter {
|
|
|
172
190
|
return; // silent no-op
|
|
173
191
|
docStore.roles.set(tenant, role);
|
|
174
192
|
},
|
|
193
|
+
resolveFsPath,
|
|
175
194
|
};
|
|
176
195
|
return ctxOut;
|
|
177
196
|
}
|
package/dist/tenant-fs/http.d.ts
CHANGED
|
@@ -5,11 +5,9 @@
|
|
|
5
5
|
* Read-only. Writes are out of scope for this iteration.
|
|
6
6
|
*/
|
|
7
7
|
import type { Hono } from 'hono';
|
|
8
|
-
import type { SettingsStore } from '../settings.js';
|
|
9
8
|
export interface TenantFsRouteContext {
|
|
10
9
|
dataDir: string;
|
|
11
10
|
rootBase: string;
|
|
12
|
-
settings: SettingsStore;
|
|
13
11
|
maxReadBytes: number;
|
|
14
12
|
}
|
|
15
13
|
export declare function registerTenantFsRoutes(app: Hono, ctx: TenantFsRouteContext): void;
|
package/dist/tenant-fs/http.js
CHANGED
|
@@ -16,7 +16,7 @@ function userIdFromContext(c) {
|
|
|
16
16
|
return session.userId;
|
|
17
17
|
}
|
|
18
18
|
export function registerTenantFsRoutes(app, ctx) {
|
|
19
|
-
const sessionRequired = makeSessionRequired(
|
|
19
|
+
const sessionRequired = makeSessionRequired();
|
|
20
20
|
app.get('/api/fs/list', sessionRequired, async (c) => {
|
|
21
21
|
const rel = c.req.query('path') ?? '';
|
|
22
22
|
const root = documentsRoot(ctx.dataDir, userIdFromContext(c), ctx.rootBase);
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { MiddlewareHandler } from 'hono';
|
|
2
|
-
import type { SettingsStore } from '../settings.js';
|
|
3
2
|
/**
|
|
4
3
|
* Requires an authenticated session on the request. Any role passes.
|
|
5
|
-
* When `auth.required` is false (dev/--no-auth), passes through.
|
|
6
4
|
*
|
|
7
5
|
* Contrast with adminOnly: this gate is for tenant-scoped APIs where any
|
|
8
6
|
* logged-in user can operate — scope is enforced by the handler jailing to
|
|
9
7
|
* the caller's own tenant root, not by role.
|
|
10
8
|
*/
|
|
11
|
-
export declare function makeSessionRequired(
|
|
9
|
+
export declare function makeSessionRequired(): MiddlewareHandler;
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Requires an authenticated session on the request. Any role passes.
|
|
3
|
-
* When `auth.required` is false (dev/--no-auth), passes through.
|
|
4
3
|
*
|
|
5
4
|
* Contrast with adminOnly: this gate is for tenant-scoped APIs where any
|
|
6
5
|
* logged-in user can operate — scope is enforced by the handler jailing to
|
|
7
6
|
* the caller's own tenant root, not by role.
|
|
8
7
|
*/
|
|
9
|
-
export function makeSessionRequired(
|
|
8
|
+
export function makeSessionRequired() {
|
|
10
9
|
return async (c, next) => {
|
|
11
|
-
if (!settings.get().auth.required)
|
|
12
|
-
return next();
|
|
13
10
|
const session = c.get('session') ?? c.env?.session;
|
|
14
11
|
if (!session?.userId) {
|
|
15
12
|
return c.json({ error: 'authentication required' }, 401);
|
package/dist/users.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface StoredUser {
|
|
|
6
6
|
id: string;
|
|
7
7
|
username: string;
|
|
8
8
|
displayName: string;
|
|
9
|
-
passwordHash: string;
|
|
9
|
+
passwordHash: string | null;
|
|
10
10
|
role: 'admin' | 'user';
|
|
11
11
|
createdAt: string;
|
|
12
12
|
updatedAt: string;
|
|
@@ -39,6 +39,19 @@ export declare class UserStore {
|
|
|
39
39
|
}): Promise<PublicUser | null>;
|
|
40
40
|
/** Delete a user by ID. Returns true if found and removed. */
|
|
41
41
|
delete(id: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Upsert a synthetic user (no password). Used by `--local` desktop mode to
|
|
44
|
+
* persist the `local` owner without invoking bcrypt (which the SEA sidecar
|
|
45
|
+
* cannot load). Users created via this path have `passwordHash=null` and
|
|
46
|
+
* can never authenticate through the password flow — `authenticate()` filters
|
|
47
|
+
* them out.
|
|
48
|
+
*/
|
|
49
|
+
upsertSynthetic(opts: {
|
|
50
|
+
id: string;
|
|
51
|
+
username: string;
|
|
52
|
+
displayName: string;
|
|
53
|
+
role: 'admin' | 'user';
|
|
54
|
+
}): PublicUser;
|
|
42
55
|
/** Generate a random temporary password. */
|
|
43
56
|
static generatePassword(): string;
|
|
44
57
|
}
|
package/dist/users.js
CHANGED
|
@@ -64,6 +64,8 @@ export class UserStore {
|
|
|
64
64
|
const user = this.#users.find(u => u.username === username.toLowerCase());
|
|
65
65
|
if (!user)
|
|
66
66
|
return null;
|
|
67
|
+
if (!user.passwordHash)
|
|
68
|
+
return null;
|
|
67
69
|
if (!(await (await getBcrypt()).compare(password, user.passwordHash)))
|
|
68
70
|
return null;
|
|
69
71
|
return this.#toPublic(user);
|
|
@@ -102,6 +104,38 @@ export class UserStore {
|
|
|
102
104
|
}
|
|
103
105
|
return false;
|
|
104
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Upsert a synthetic user (no password). Used by `--local` desktop mode to
|
|
109
|
+
* persist the `local` owner without invoking bcrypt (which the SEA sidecar
|
|
110
|
+
* cannot load). Users created via this path have `passwordHash=null` and
|
|
111
|
+
* can never authenticate through the password flow — `authenticate()` filters
|
|
112
|
+
* them out.
|
|
113
|
+
*/
|
|
114
|
+
upsertSynthetic(opts) {
|
|
115
|
+
const lower = opts.username.toLowerCase();
|
|
116
|
+
const existing = this.#users.find((u) => u.id === opts.id);
|
|
117
|
+
const now = new Date().toISOString();
|
|
118
|
+
if (existing) {
|
|
119
|
+
existing.username = lower;
|
|
120
|
+
existing.displayName = opts.displayName;
|
|
121
|
+
existing.role = opts.role;
|
|
122
|
+
existing.updatedAt = now;
|
|
123
|
+
this.#save();
|
|
124
|
+
return this.#toPublic(existing);
|
|
125
|
+
}
|
|
126
|
+
const user = {
|
|
127
|
+
id: opts.id,
|
|
128
|
+
username: lower,
|
|
129
|
+
displayName: opts.displayName,
|
|
130
|
+
passwordHash: null,
|
|
131
|
+
role: opts.role,
|
|
132
|
+
createdAt: now,
|
|
133
|
+
updatedAt: now,
|
|
134
|
+
};
|
|
135
|
+
this.#users.push(user);
|
|
136
|
+
this.#save();
|
|
137
|
+
return this.#toPublic(user);
|
|
138
|
+
}
|
|
105
139
|
/** Generate a random temporary password. */
|
|
106
140
|
static generatePassword() {
|
|
107
141
|
return randomBytes(6).toString('base64url');
|