@karmaniverous/jeeves-server-core 0.2.1-3 → 0.4.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 CHANGED
@@ -14,26 +14,34 @@ Shared workspace package providing cryptographic utilities and API types consume
14
14
  | `computeDeepShareKey(seed, path, params)` | HMAC-SHA256 key for deep directory shares with depth/dirs/stack/expiry |
15
15
  | `timingSafeEqual(a, b)` | Constant-time string comparison to prevent timing attacks |
16
16
 
17
+ ### URL Key Parameter
18
+
19
+ | Export | Description |
20
+ | --- | --- |
21
+ | `URL_KEY_PARAM` | `'k'`: the query parameter every generated URL carries its auth key in |
22
+ | `LEGACY_URL_KEY_PARAM` | `'key'`: the old name, still accepted everywhere |
23
+ | `getUrlKey(query)` | Read the auth key from a parsed query object or `URLSearchParams`: `k` wins when both names are present; empty values count as absent; the first of repeated values is used |
24
+
17
25
  ### Config Schema
18
26
 
19
27
  | Export | Description |
20
28
  | --- | --- |
21
29
  | `loggingConfigSchema` | Zod schema for `logging` config block (`level`, `file`, both optional) |
22
30
  | `LoggingConfig` | Inferred TypeScript type from `loggingConfigSchema` |
23
- | `jeevesConfigSchema` | Full server config Zod schema (includes `logging`, `oauth`, all other fields) |
31
+ | `jeevesConfigSchema` | Full server config Zod schema (includes `logging`, `oauth`, `publicUrl`, `trustProxyHeaders` and all other fields; requires `publicUrl` or `trustProxyHeaders: true` when email, Google or OAuth login is configured) |
24
32
 
25
33
  ### Endpoint Catalog
26
34
 
27
35
  | Export | Description |
28
36
  | --- | --- |
29
37
  | `EndpointEntry` | Interface describing a single API operation (method, path, description, auth flags) |
30
- | `serverEndpoints` | Complete declarative catalog of all jeeves-server API endpoints (38 entries) |
38
+ | `serverEndpoints` | Complete declarative catalog of all jeeves-server API endpoints (39 entries) |
31
39
 
32
40
  ### API Types
33
41
 
34
42
  | Type | Description |
35
43
  | --- | --- |
36
- | `ShareRequest` / `ShareResponse` | `POST /api/share` payloads |
44
+ | `ShareRequest` / `ShareResponse` | `POST /api/share` payloads (`ShareResponse.isDirectory`, optional: whether the shared path is an existing directory; sent by servers from 3.15.0) |
37
45
  | `ShareForRequest` / `ShareForResponse` | `POST /api/util/share-for` payloads (audience-based sharing) |
38
46
  | `AuthStatusResponse` | `GET /api/auth/status` response |
39
47
  | `RotateKeyResponse` | `POST /api/rotate-key` response |
@@ -19,6 +19,12 @@ const serverEndpoints = {
19
19
  description: 'Server health: name, version, uptime, status, health details.',
20
20
  unauthenticated: true,
21
21
  },
22
+ health: {
23
+ method: 'GET',
24
+ path: '/health',
25
+ description: 'Liveness check: returns { ok: true }, no downstream checks.',
26
+ unauthenticated: true,
27
+ },
22
28
  config: {
23
29
  method: 'GET',
24
30
  path: '/config',
@@ -122,7 +128,7 @@ const serverEndpoints = {
122
128
  share: {
123
129
  method: 'POST',
124
130
  path: '/api/share',
125
- description: 'Generate HMAC-signed share link for a path.',
131
+ description: 'Generate HMAC-signed share link for a path; the response reports whether the path is a directory (isDirectory).',
126
132
  insiderOnly: true,
127
133
  },
128
134
  'share-for': {
@@ -148,6 +154,12 @@ const serverEndpoints = {
148
154
  path: '/api/link-info/*',
149
155
  description: 'Query available link types for a path (page, raw, PDF, DOCX, etc.).',
150
156
  },
157
+ 'resolve-path': {
158
+ method: 'GET',
159
+ path: '/api/resolve-path',
160
+ description: 'Map an absolute filesystem path to its browse path and URLs (?fsPath=).',
161
+ insiderOnly: true,
162
+ },
151
163
  'rotate-key': {
152
164
  method: 'POST',
153
165
  path: '/api/rotate-key',
@@ -163,7 +175,8 @@ const serverEndpoints = {
163
175
  events: {
164
176
  method: 'GET',
165
177
  path: '/api/events',
166
- description: 'Query recent event log entries (limit capped at 100).',
178
+ description: 'Query recent event log entries (insiders with no scopes configured; limit capped at 100).',
179
+ insiderOnly: true,
167
180
  },
168
181
  // --- Search ---
169
182
  search: {
@@ -7,3 +7,4 @@ export { computeDeepShareKey, computeInsiderKey, computeOutsiderKeyWithExpiry, c
7
7
  export { type EndpointEntry, serverEndpoints } from './endpoints.js';
8
8
  export { type AuthMode, authModeSchema, authSchema, type Branding, brandingSchema, DEFAULT_BRANDING, DEPRECATED_CONFIG_PROPS, emailAuthSchema, eventConfigSchema, googleAuthSchema, insiderEntrySchema, isScopeName, type JeevesConfig, jeevesConfigSchema, keyEntrySchema, type LoggingConfig, loggingConfigSchema, oauthProviderSchema, oauthSchema, scopesObjectSchema, scopesSchema, themeOverridesSchema, } from './schema.js';
9
9
  export type { AuthStatusResponse, DriveEntry, ExportCacheClearResponse, ExportLink, FileMutationAction, LinkInfoResponse, OAuthStartRequest, OAuthStartResponse, RotateKeyResponse, ShareForRequest, ShareForResponse, ShareRequest, ShareResponse, } from './types.js';
10
+ export { getUrlKey, LEGACY_URL_KEY_PARAM, URL_KEY_PARAM } from './urlKey.js';
package/dist/src/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { computeDeepShareKey, computeInsiderKey, computeOutsiderKeyWithExpiry, computePathKey, timingSafeEqual } from './crypto.js';
2
2
  export { serverEndpoints } from './endpoints.js';
3
3
  export { DEFAULT_BRANDING, DEPRECATED_CONFIG_PROPS, authModeSchema, authSchema, brandingSchema, emailAuthSchema, eventConfigSchema, googleAuthSchema, insiderEntrySchema, isScopeName, jeevesConfigSchema, keyEntrySchema, loggingConfigSchema, oauthProviderSchema, oauthSchema, scopesObjectSchema, scopesSchema, themeOverridesSchema } from './schema.js';
4
+ export { LEGACY_URL_KEY_PARAM, URL_KEY_PARAM, getUrlKey } from './urlKey.js';
@@ -212,6 +212,7 @@ export declare const jeevesConfigSchema: z.ZodObject<{
212
212
  eventQueue: z.ZodOptional<z.ZodString>;
213
213
  eventQueueConcurrency: z.ZodDefault<z.ZodNumber>;
214
214
  publicUrl: z.ZodOptional<z.ZodURL>;
215
+ trustProxyHeaders: z.ZodDefault<z.ZodBoolean>;
215
216
  }, z.core.$strip>;
216
217
  /** Inferred config type */
217
218
  export type JeevesConfig = z.infer<typeof jeevesConfigSchema>;
@@ -239,6 +239,12 @@ const jeevesConfigSchema = z
239
239
  eventQueueConcurrency: z.number().int().positive().default(3),
240
240
  /** Public base URL (e.g. https://jeeves.johngalt.id). When set, API responses include full public URLs. */
241
241
  publicUrl: z.url().optional(),
242
+ /**
243
+ * Trust `X-Forwarded-Proto` / `X-Forwarded-Host` / `Host` to build the
244
+ * public origin when `publicUrl` is unset. Enable only behind a proxy
245
+ * that sets or strips these headers itself. Default: false.
246
+ */
247
+ trustProxyHeaders: z.boolean().default(false),
242
248
  })
243
249
  .superRefine((config, ctx) => {
244
250
  // Google auth mode requires google config + sessionSecret
@@ -275,6 +281,22 @@ const jeevesConfigSchema = z
275
281
  });
276
282
  }
277
283
  }
284
+ // Login links and OAuth redirects are absolute URLs on a trusted public
285
+ // origin: publicUrl, or forwarding headers only when explicitly trusted.
286
+ const loginFlows = [
287
+ config.auth.modes.includes('email') ? 'email login' : null,
288
+ config.auth.modes.includes('google') ? 'Google login' : null,
289
+ config.oauth ? 'OAuth (oauth)' : null,
290
+ ].filter((flow) => flow !== null);
291
+ if (loginFlows.length > 0 &&
292
+ !config.publicUrl &&
293
+ !config.trustProxyHeaders) {
294
+ ctx.addIssue({
295
+ code: 'custom',
296
+ message: `publicUrl is required when ${loginFlows.join(', ')} is configured (or set trustProxyHeaders: true behind a proxy that sets X-Forwarded-* headers)`,
297
+ path: ['publicUrl'],
298
+ });
299
+ }
278
300
  // Keys auth mode requires at least one key
279
301
  if (config.auth.modes.includes('keys') &&
280
302
  Object.keys(config.keys).length === 0) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -17,6 +17,11 @@ export interface ShareResponse {
17
17
  exp: string | null;
18
18
  depth?: number;
19
19
  dirs?: boolean;
20
+ /**
21
+ * Whether the shared path is an existing directory. Sent by servers from
22
+ * 3.15.0; absent from older servers.
23
+ */
24
+ isDirectory?: boolean;
20
25
  }
21
26
  /** POST /api/util/share-for request body. */
22
27
  export interface ShareForRequest {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * URL auth-key query parameter names and a reader that accepts both: `k`
3
+ * (generated since 3.15.0) and the legacy `key`. Pure; no I/O.
4
+ *
5
+ * @remarks
6
+ * OpenClaw masks the value of any query parameter named `key` in
7
+ * model-visible tool output, so agents could not relay share links (#279).
8
+ * Generated URLs therefore carry the key as `k`. `key` is still accepted
9
+ * everywhere so existing links, bookmarks and webhook URLs keep working.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ /** Query parameter name used for auth keys in every generated URL. */
14
+ export declare const URL_KEY_PARAM = "k";
15
+ /** Legacy query parameter name, still accepted for backward compatibility. */
16
+ export declare const LEGACY_URL_KEY_PARAM = "key";
17
+ /**
18
+ * Read the auth key from a parsed query object or `URLSearchParams`.
19
+ * `k` wins when both names are present; empty values count as absent.
20
+ *
21
+ * @param query - Parsed query (e.g. Fastify `request.query`) or search params.
22
+ * @returns The key, or `undefined` when neither parameter carries one.
23
+ */
24
+ export declare function getUrlKey(query: unknown): string | undefined;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * URL auth-key query parameter names and a reader that accepts both: `k`
3
+ * (generated since 3.15.0) and the legacy `key`. Pure; no I/O.
4
+ *
5
+ * @remarks
6
+ * OpenClaw masks the value of any query parameter named `key` in
7
+ * model-visible tool output, so agents could not relay share links (#279).
8
+ * Generated URLs therefore carry the key as `k`. `key` is still accepted
9
+ * everywhere so existing links, bookmarks and webhook URLs keep working.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ /** Query parameter name used for auth keys in every generated URL. */
14
+ const URL_KEY_PARAM = 'k';
15
+ /** Legacy query parameter name, still accepted for backward compatibility. */
16
+ const LEGACY_URL_KEY_PARAM = 'key';
17
+ /** First non-empty string in a parsed query value (string or repeated). */
18
+ function firstString(value) {
19
+ const candidate = Array.isArray(value) ? value[0] : value;
20
+ return typeof candidate === 'string' && candidate !== ''
21
+ ? candidate
22
+ : undefined;
23
+ }
24
+ /**
25
+ * Read the auth key from a parsed query object or `URLSearchParams`.
26
+ * `k` wins when both names are present; empty values count as absent.
27
+ *
28
+ * @param query - Parsed query (e.g. Fastify `request.query`) or search params.
29
+ * @returns The key, or `undefined` when neither parameter carries one.
30
+ */
31
+ function getUrlKey(query) {
32
+ if (query instanceof URLSearchParams) {
33
+ return (firstString(query.get(URL_KEY_PARAM)) ??
34
+ firstString(query.get(LEGACY_URL_KEY_PARAM)));
35
+ }
36
+ if (query === null || typeof query !== 'object')
37
+ return undefined;
38
+ const record = query;
39
+ return (firstString(record[URL_KEY_PARAM]) ??
40
+ firstString(record[LEGACY_URL_KEY_PARAM]));
41
+ }
42
+
43
+ export { LEGACY_URL_KEY_PARAM, URL_KEY_PARAM, getUrlKey };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "author": "Jeeves",
3
3
  "dependencies": {
4
- "@karmaniverous/jeeves": "0.6.0-9",
5
- "zod": "^4.6.5"
4
+ "@karmaniverous/jeeves": "0.7.0",
5
+ "zod": "^4.6.7"
6
6
  },
7
7
  "description": "Shared cryptographic utilities and API types for jeeves-server packages.",
8
8
  "devDependencies": {
9
- "@dotenvx/dotenvx": "^2.30.0",
9
+ "@dotenvx/dotenvx": "^2.35.0",
10
10
  "@rollup/plugin-commonjs": "^29.0.3",
11
11
  "@rollup/plugin-json": "^6.1.0",
12
12
  "@rollup/plugin-node-resolve": "^16.0.3",
13
13
  "@rollup/plugin-typescript": "^12.3.0",
14
14
  "cross-env": "^10.1.0",
15
- "release-it": "^21.1.0",
16
- "rollup": "^4.63.5",
15
+ "release-it": "^21.1.1",
16
+ "rollup": "^4.64.6",
17
17
  "tslib": "^2.8.1",
18
- "vitest": "^5.0.2"
18
+ "vitest": "^5.0.4"
19
19
  },
20
20
  "engines": {
21
21
  "node": ">=22"
@@ -44,10 +44,13 @@
44
44
  "tagName": "core/${version}"
45
45
  },
46
46
  "github": {
47
- "release": true
47
+ "release": true,
48
+ "releaseName": "${name.split('/').pop()}@${version}",
49
+ "releaseNotes": "npx git-cliff --tag core/${version} --unreleased --strip header"
48
50
  },
49
51
  "hooks": {
50
52
  "after:bump": [
53
+ "npx git-cliff --tag core/${version} -o CHANGELOG.md",
51
54
  "npm install --package-lock-only --ignore-scripts",
52
55
  "git add ../../package-lock.json"
53
56
  ],
@@ -63,7 +66,6 @@
63
66
  "git switch ${branchName}"
64
67
  ],
65
68
  "before:npm:release": [
66
- "npx git-cliff -o CHANGELOG.md",
67
69
  "git add -A"
68
70
  ]
69
71
  },
@@ -90,5 +92,5 @@
90
92
  },
91
93
  "type": "module",
92
94
  "types": "dist/src/index.d.ts",
93
- "version": "0.2.1-3"
95
+ "version": "0.4.0"
94
96
  }