@qnote/q-ai-note 1.0.5 → 1.0.7

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.
Files changed (50) hide show
  1. package/README.md +41 -0
  2. package/dist/cli.js +55 -18
  3. package/dist/cli.js.map +1 -1
  4. package/dist/server/accessControl.d.ts +29 -0
  5. package/dist/server/accessControl.d.ts.map +1 -0
  6. package/dist/server/accessControl.js +161 -0
  7. package/dist/server/accessControl.js.map +1 -0
  8. package/dist/server/api/accessHelpers.d.ts +11 -0
  9. package/dist/server/api/accessHelpers.d.ts.map +1 -0
  10. package/dist/server/api/accessHelpers.js +45 -0
  11. package/dist/server/api/accessHelpers.js.map +1 -0
  12. package/dist/server/api/chat.d.ts.map +1 -1
  13. package/dist/server/api/chat.js +31 -0
  14. package/dist/server/api/chat.js.map +1 -1
  15. package/dist/server/api/diary.d.ts.map +1 -1
  16. package/dist/server/api/diary.js +61 -1
  17. package/dist/server/api/diary.js.map +1 -1
  18. package/dist/server/api/nodeEntities.d.ts.map +1 -1
  19. package/dist/server/api/nodeEntities.js +31 -0
  20. package/dist/server/api/nodeEntities.js.map +1 -1
  21. package/dist/server/api/projectSettings.d.ts +3 -0
  22. package/dist/server/api/projectSettings.d.ts.map +1 -0
  23. package/dist/server/api/projectSettings.js +29 -0
  24. package/dist/server/api/projectSettings.js.map +1 -0
  25. package/dist/server/api/sandbox.d.ts.map +1 -1
  26. package/dist/server/api/sandbox.js +35 -1
  27. package/dist/server/api/sandbox.js.map +1 -1
  28. package/dist/server/api/settings.d.ts.map +1 -1
  29. package/dist/server/api/settings.js +25 -1
  30. package/dist/server/api/settings.js.map +1 -1
  31. package/dist/server/api/workItem.d.ts.map +1 -1
  32. package/dist/server/api/workItem.js +59 -0
  33. package/dist/server/api/workItem.js.map +1 -1
  34. package/dist/server/config.d.ts +3 -2
  35. package/dist/server/config.d.ts.map +1 -1
  36. package/dist/server/config.js +6 -1
  37. package/dist/server/config.js.map +1 -1
  38. package/dist/server/index.d.ts +8 -2
  39. package/dist/server/index.d.ts.map +1 -1
  40. package/dist/server/index.js +102 -4
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/server/projectConfig.d.ts +37 -0
  43. package/dist/server/projectConfig.d.ts.map +1 -0
  44. package/dist/server/projectConfig.js +180 -0
  45. package/dist/server/projectConfig.js.map +1 -0
  46. package/dist/web/app.js +760 -44
  47. package/dist/web/index.html +107 -60
  48. package/dist/web/styles.css +256 -11
  49. package/dist/web/vueRenderers.js +71 -57
  50. package/package.json +2 -2
package/README.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  AI-assisted personal work sandbox and diary system.
4
4
 
5
+ ## Documentation
6
+
7
+ Project specs and development guides are organized under `docs/`:
8
+
9
+ - `docs/spec/functional-spec.md`
10
+ - `docs/spec/design-spec.md`
11
+ - `docs/spec/ui-spec.md`
12
+ - `docs/process/development-guide.md`
13
+
5
14
  ## Install
6
15
 
7
16
  Use without global install:
@@ -23,16 +32,23 @@ q-ai-note-server
23
32
  ```bash
24
33
  q-ai-note --port 3200
25
34
  q-ai-note .
35
+ q-ai-note --readonly
26
36
  ```
27
37
 
28
38
  Then open `http://localhost:3200`.
29
39
 
40
+ ### Command differences
41
+
42
+ - `q-ai-note`: local-first, defaults `127.0.0.1:3000`
43
+ - `q-ai-note-server`: public server, defaults `0.0.0.0:8614`
44
+
30
45
  ### Public server mode
31
46
 
32
47
  If you need external access, use:
33
48
 
34
49
  ```bash
35
50
  q-ai-note-server
51
+ q-ai-note-server --readonly
36
52
  ```
37
53
 
38
54
  Defaults:
@@ -41,10 +57,35 @@ Defaults:
41
57
 
42
58
  Then open `http://<server-ip>:8614`.
43
59
 
60
+ ### Access control model
61
+
62
+ The app now uses two-level configuration:
63
+
64
+ - system config (`~/.q-ai-note/config.json`): model settings + `editable_ip_allowlist`
65
+ - project config (`<data-dir>/project-config.json`): `users` / `profiles` / `bindings`
66
+
67
+ Rules:
68
+ - IP matching is exact match only (no CIDR)
69
+ - local loopback and local NIC IPs are system-admin by default
70
+ - system-admin IPs can access all pages and full read/write (unless global `--readonly`)
71
+ - non-admin IPs are controlled by project profiles (page visibility + sandbox-level read/write)
72
+ - `/api/settings` (system settings) is only accessible to system-admin IPs
73
+
74
+ ### Readonly mode
75
+
76
+ Use `--readonly` (or env `Q_AI_NOTE_READONLY=true`) to start in readonly mode:
77
+
78
+ - hide chat UI and chat buttons
79
+ - hide settings page
80
+ - hide add/edit/delete operations
81
+ - reject all write API requests (`403`)
82
+ - reject settings API (`/api/settings`) for readonly requests (`403`)
83
+
44
84
  ## Data and config paths
45
85
 
46
86
  - Config: `~/.q-ai-note/config.json`
47
87
  - Data dir: `~/.q-ai-note/data/`
88
+ - `project-config.json`
48
89
  - `sandboxes.json`
49
90
  - `work-items.json`
50
91
  - `diaries.json`
package/dist/cli.js CHANGED
@@ -1,10 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'path';
3
+ import { basename } from 'path';
3
4
  import { startServer } from './server/index.js';
4
- function parseArgs(argv) {
5
+ function resolveCliDefaults(argv0) {
6
+ const commandName = basename(argv0 || 'q-ai-note');
7
+ const isServerMode = commandName === 'q-ai-note-server';
8
+ return {
9
+ commandName,
10
+ defaultPort: isServerMode ? 8614 : 3000,
11
+ defaultHost: isServerMode ? '0.0.0.0' : '127.0.0.1',
12
+ localWriteOnly: false,
13
+ };
14
+ }
15
+ function parseArgs(argv, defaults) {
5
16
  const options = {
6
- port: Number(process.env.PORT || 3000),
7
- host: String(process.env.HOST || '127.0.0.1'),
17
+ port: Number(process.env.PORT || defaults.defaultPort),
18
+ host: String(process.env.HOST || defaults.defaultHost),
19
+ readonly: String(process.env.Q_AI_NOTE_READONLY || '').toLowerCase() === 'true',
8
20
  help: false,
9
21
  dataDir: undefined,
10
22
  };
@@ -33,46 +45,71 @@ function parseArgs(argv) {
33
45
  }
34
46
  continue;
35
47
  }
48
+ if (arg === '--readonly') {
49
+ options.readonly = true;
50
+ continue;
51
+ }
36
52
  if (!arg.startsWith('-') && !options.dataDir) {
37
53
  options.dataDir = path.resolve(arg);
38
54
  }
39
55
  }
40
56
  return options;
41
57
  }
42
- function printHelp() {
58
+ function printHelp(defaults) {
59
+ const isServerMode = defaults.commandName === 'q-ai-note-server';
60
+ const defaultPortText = isServerMode ? '8614' : '3000 or PORT env';
61
+ const examples = isServerMode
62
+ ? [
63
+ ' q-ai-note-server',
64
+ ' q-ai-note-server --port 8614',
65
+ ' q-ai-note-server --host 0.0.0.0 --port 8614',
66
+ ' q-ai-note-server ./my-data --port 8614',
67
+ ]
68
+ : [
69
+ ' npx @qnote/q-ai-note',
70
+ ' q-ai-note .',
71
+ ' q-ai-note --port 3200',
72
+ ' q-ai-note --host 127.0.0.1 --port 3200',
73
+ ' q-ai-note ./my-data --port 3200',
74
+ ' PORT=3200 q-ai-note',
75
+ ];
43
76
  console.log(`
44
- q-ai-note
77
+ ${defaults.commandName}
45
78
 
46
79
  Usage:
47
- q-ai-note [data-dir] [--port <port>] [--host <host>]
80
+ ${defaults.commandName} [data-dir] [--port <port>] [--host <host>] [--readonly]
48
81
 
49
82
  Options:
50
83
  data-dir Directory for JSON data files (optional)
51
- -p, --port Set server port (default: 3000 or PORT env)
52
- --host Set listen host (default: 127.0.0.1)
84
+ -p, --port Set server port (default: ${defaultPortText})
85
+ --host Set listen host (default: ${defaults.defaultHost})
86
+ --readonly Start in readonly mode
53
87
  -h, --help Show this help message
54
88
 
55
89
  Examples:
56
- npx @qnote/q-ai-note
57
- q-ai-note .
58
- q-ai-note --port 3200
59
- q-ai-note --host 127.0.0.1 --port 3200
60
- q-ai-note ./my-data --port 3200
61
- PORT=3200 q-ai-note
90
+ ${examples.join('\n')}
62
91
  `);
63
92
  }
64
93
  function bootstrap() {
65
- const options = parseArgs(process.argv.slice(2));
94
+ const defaults = resolveCliDefaults(process.argv[1]);
95
+ const options = parseArgs(process.argv.slice(2), defaults);
66
96
  if (options.help) {
67
- printHelp();
97
+ printHelp(defaults);
68
98
  return;
69
99
  }
70
100
  if (options.dataDir) {
71
101
  process.env.Q_AI_NOTE_DATA_DIR = options.dataDir;
72
102
  }
73
- startServer(options.port, options.host);
74
- console.log(`q-ai-note is ready at http://localhost:${options.port}`);
103
+ startServer(options.port, options.host, {
104
+ readonly: options.readonly,
105
+ localWriteOnly: defaults.localWriteOnly && !options.readonly,
106
+ });
107
+ console.log(`${defaults.commandName} is ready at http://localhost:${options.port}`);
75
108
  console.log(`listen: ${options.host}:${options.port}`);
109
+ const modeText = options.readonly
110
+ ? 'readonly'
111
+ : 'readwrite';
112
+ console.log(`mode: ${modeText}`);
76
113
  if (options.dataDir) {
77
114
  console.log(`data dir: ${options.dataDir}`);
78
115
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { AccessPolicy } from './index.js';
2
+ import { type PageKey, type SandboxAccessLevel } from './projectConfig.js';
3
+ export interface RequestAccessContext {
4
+ ip: string;
5
+ readonly: boolean;
6
+ full_access: boolean;
7
+ can_access_system_settings: boolean;
8
+ page_access: Record<PageKey, boolean>;
9
+ sandbox_access_all: SandboxAccessLevel;
10
+ sandbox_read_ids: string[];
11
+ sandbox_write_ids: string[];
12
+ project_config_version: number;
13
+ }
14
+ export declare function invalidateAccessCache(): void;
15
+ export declare function normalizeIpAddress(ip: string): string;
16
+ export declare function computeAccessContext(params: {
17
+ ip: string;
18
+ policy: Required<AccessPolicy>;
19
+ localIpSet: Set<string>;
20
+ }): RequestAccessContext;
21
+ export declare function getAccessContext(params: {
22
+ ip: string;
23
+ policy: Required<AccessPolicy>;
24
+ localIpSet: Set<string>;
25
+ }): RequestAccessContext;
26
+ export declare function canReadSandbox(ctx: RequestAccessContext, sandboxId: string): boolean;
27
+ export declare function canWriteSandbox(ctx: RequestAccessContext, sandboxId: string): boolean;
28
+ export declare function canCreateSandbox(ctx: RequestAccessContext): boolean;
29
+ //# sourceMappingURL=accessControl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessControl.d.ts","sourceRoot":"","sources":["../../src/server/accessControl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,EAAqB,KAAK,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAElH,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,0BAA0B,EAAE,OAAO,CAAC;IACpC,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACtC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;CAChC;AASD,wBAAgB,qBAAqB,SAEpC;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAIrD;AA0ED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACzB,GAAG,oBAAoB,CA0DvB;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACzB,GAAG,oBAAoB,CASvB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAIpF;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAKrF;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAEnE"}
@@ -0,0 +1,161 @@
1
+ import { getSetting } from './config.js';
2
+ import { readProjectConfig } from './projectConfig.js';
3
+ let accessCache = new Map();
4
+ export function invalidateAccessCache() {
5
+ accessCache = new Map();
6
+ }
7
+ export function normalizeIpAddress(ip) {
8
+ const raw = String(ip || '').trim();
9
+ if (!raw)
10
+ return '';
11
+ return raw.startsWith('::ffff:') ? raw.slice(7) : raw;
12
+ }
13
+ function normalizeIpList(values) {
14
+ if (!Array.isArray(values))
15
+ return [];
16
+ return values
17
+ .map((value) => normalizeIpAddress(String(value || '').trim()))
18
+ .filter((value) => Boolean(value) && !value.includes('/'));
19
+ }
20
+ function buildSystemEditableIps(localIps) {
21
+ const configured = normalizeIpList(getSetting('editable_ip_allowlist'));
22
+ const result = new Set(Array.from(localIps).map((ip) => normalizeIpAddress(ip)));
23
+ configured.forEach((ip) => result.add(ip));
24
+ return result;
25
+ }
26
+ function mergeLevel(a, b) {
27
+ const rank = { none: 0, read: 1, write: 2 };
28
+ return rank[b] > rank[a] ? b : a;
29
+ }
30
+ function resolveProfileContext(ip, projectConfig) {
31
+ const normalizedIp = normalizeIpAddress(ip);
32
+ const matchedUserIds = projectConfig.users
33
+ .filter((user) => user.ips.includes(normalizedIp))
34
+ .map((user) => user.id);
35
+ const boundProfileIds = new Set();
36
+ projectConfig.bindings.forEach((binding) => {
37
+ if (matchedUserIds.includes(binding.user_id)) {
38
+ binding.profile_ids.forEach((id) => boundProfileIds.add(id));
39
+ }
40
+ });
41
+ const selectedProfiles = projectConfig.profiles.filter((profile) => {
42
+ if (profile.apply_to_all_users)
43
+ return true;
44
+ return boundProfileIds.has(profile.id);
45
+ });
46
+ const pages = {
47
+ sandboxes: false,
48
+ diaries: false,
49
+ changes: false,
50
+ settings: false,
51
+ };
52
+ let sandboxAll = 'none';
53
+ const sandboxItemAccess = new Map();
54
+ selectedProfiles.forEach((profile) => {
55
+ pages.sandboxes = pages.sandboxes || profile.page_access.sandboxes;
56
+ pages.diaries = pages.diaries || profile.page_access.diaries;
57
+ pages.changes = pages.changes || profile.page_access.changes;
58
+ pages.settings = pages.settings || profile.page_access.settings;
59
+ sandboxAll = mergeLevel(sandboxAll, profile.sandbox_access.all);
60
+ profile.sandbox_access.items.forEach((item) => {
61
+ const prev = sandboxItemAccess.get(item.sandbox_id) || 'none';
62
+ sandboxItemAccess.set(item.sandbox_id, mergeLevel(prev, item.access));
63
+ });
64
+ });
65
+ const readSet = new Set();
66
+ const writeSet = new Set();
67
+ sandboxItemAccess.forEach((access, sandboxId) => {
68
+ if (access === 'read' || access === 'write')
69
+ readSet.add(sandboxId);
70
+ if (access === 'write')
71
+ writeSet.add(sandboxId);
72
+ });
73
+ return { pages, sandboxAll, readSet, writeSet };
74
+ }
75
+ export function computeAccessContext(params) {
76
+ const normalizedIp = normalizeIpAddress(params.ip);
77
+ const projectConfig = readProjectConfig();
78
+ const systemEditableIps = buildSystemEditableIps(params.localIpSet);
79
+ const canAccessSystemSettings = !params.policy.readonly && systemEditableIps.has(normalizedIp);
80
+ const fullAccess = canAccessSystemSettings;
81
+ if (params.policy.readonly) {
82
+ return {
83
+ ip: normalizedIp,
84
+ readonly: true,
85
+ full_access: false,
86
+ can_access_system_settings: false,
87
+ page_access: {
88
+ sandboxes: true,
89
+ diaries: true,
90
+ changes: true,
91
+ settings: false,
92
+ },
93
+ sandbox_access_all: 'read',
94
+ sandbox_read_ids: [],
95
+ sandbox_write_ids: [],
96
+ project_config_version: projectConfig.version,
97
+ };
98
+ }
99
+ if (fullAccess) {
100
+ return {
101
+ ip: normalizedIp,
102
+ readonly: false,
103
+ full_access: true,
104
+ can_access_system_settings: true,
105
+ page_access: {
106
+ sandboxes: true,
107
+ diaries: true,
108
+ changes: true,
109
+ settings: true,
110
+ },
111
+ sandbox_access_all: 'write',
112
+ sandbox_read_ids: [],
113
+ sandbox_write_ids: [],
114
+ project_config_version: projectConfig.version,
115
+ };
116
+ }
117
+ const profileContext = resolveProfileContext(normalizedIp, projectConfig);
118
+ const readonly = profileContext.sandboxAll !== 'write' && profileContext.writeSet.size === 0;
119
+ return {
120
+ ip: normalizedIp,
121
+ readonly,
122
+ full_access: false,
123
+ can_access_system_settings: false,
124
+ page_access: profileContext.pages,
125
+ sandbox_access_all: profileContext.sandboxAll,
126
+ sandbox_read_ids: Array.from(profileContext.readSet),
127
+ sandbox_write_ids: Array.from(profileContext.writeSet),
128
+ project_config_version: projectConfig.version,
129
+ };
130
+ }
131
+ export function getAccessContext(params) {
132
+ const normalizedIp = normalizeIpAddress(params.ip);
133
+ const projectVersion = readProjectConfig().version;
134
+ const cacheKey = `${normalizedIp}|${params.policy.readonly ? '1' : '0'}|${params.policy.localWriteOnly ? '1' : '0'}|${projectVersion}`;
135
+ const cached = accessCache.get(cacheKey);
136
+ if (cached)
137
+ return cached.context;
138
+ const context = computeAccessContext(params);
139
+ accessCache.set(cacheKey, { key: cacheKey, context });
140
+ return context;
141
+ }
142
+ export function canReadSandbox(ctx, sandboxId) {
143
+ if (ctx.full_access)
144
+ return true;
145
+ if (ctx.sandbox_access_all === 'read' || ctx.sandbox_access_all === 'write')
146
+ return true;
147
+ return ctx.sandbox_read_ids.includes(sandboxId) || ctx.sandbox_write_ids.includes(sandboxId);
148
+ }
149
+ export function canWriteSandbox(ctx, sandboxId) {
150
+ if (ctx.readonly)
151
+ return false;
152
+ if (ctx.full_access)
153
+ return true;
154
+ if (ctx.sandbox_access_all === 'write')
155
+ return true;
156
+ return ctx.sandbox_write_ids.includes(sandboxId);
157
+ }
158
+ export function canCreateSandbox(ctx) {
159
+ return !ctx.readonly && ctx.full_access;
160
+ }
161
+ //# sourceMappingURL=accessControl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessControl.js","sourceRoot":"","sources":["../../src/server/accessControl.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Response } from 'express';
2
+ import type { RequestAccessContext } from '../accessControl.js';
3
+ export declare function getAccessContextFromResponse(res: Response): RequestAccessContext;
4
+ export declare function getSandboxIdByWorkItemId(workItemId: string): string | null;
5
+ export declare function getSandboxIdByDiaryId(diaryId: string): string | null;
6
+ export declare function getSandboxIdByOperationId(operationId: string): string | null;
7
+ export declare function forbid(res: Response, message?: string): Response<any, Record<string, any>>;
8
+ export declare function ensurePageAccess(res: Response, page: 'sandboxes' | 'diaries' | 'changes' | 'settings'): boolean;
9
+ export declare function ensureSandboxReadAccess(res: Response, sandboxId: string): boolean;
10
+ export declare function ensureSandboxWriteAccess(res: Response, sandboxId: string): boolean;
11
+ //# sourceMappingURL=accessHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessHelpers.d.ts","sourceRoot":"","sources":["../../../src/server/api/accessHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,QAAQ,GAAG,oBAAoB,CAYhF;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG1E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIpE;AAED,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5E;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,SAAc,sCAE1D;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAG/G;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAGjF;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAGlF"}
@@ -0,0 +1,45 @@
1
+ import { db } from '../db.js';
2
+ import { canReadSandbox, canWriteSandbox } from '../accessControl.js';
3
+ export function getAccessContextFromResponse(res) {
4
+ return (res.locals.accessContext || {
5
+ readonly: true,
6
+ full_access: false,
7
+ page_access: { sandboxes: false, diaries: false, changes: false, settings: false },
8
+ sandbox_access_all: 'none',
9
+ sandbox_read_ids: [],
10
+ sandbox_write_ids: [],
11
+ can_access_system_settings: false,
12
+ project_config_version: 0,
13
+ ip: '',
14
+ });
15
+ }
16
+ export function getSandboxIdByWorkItemId(workItemId) {
17
+ const row = db.prepare('SELECT * FROM work_items WHERE id = ?').get(workItemId);
18
+ return row?.sandbox_id ? String(row.sandbox_id) : null;
19
+ }
20
+ export function getSandboxIdByDiaryId(diaryId) {
21
+ const row = db.prepare('SELECT * FROM diaries WHERE id = ?').get(diaryId);
22
+ if (!row)
23
+ return null;
24
+ return row.sandbox_id ? String(row.sandbox_id) : '';
25
+ }
26
+ export function getSandboxIdByOperationId(operationId) {
27
+ const row = db.prepare('SELECT * FROM operations WHERE id = ?').get(operationId);
28
+ return row?.sandbox_id ? String(row.sandbox_id) : null;
29
+ }
30
+ export function forbid(res, message = 'Forbidden') {
31
+ return res.status(403).json({ success: false, error: message });
32
+ }
33
+ export function ensurePageAccess(res, page) {
34
+ const access = getAccessContextFromResponse(res);
35
+ return access.full_access || Boolean(access.page_access?.[page]);
36
+ }
37
+ export function ensureSandboxReadAccess(res, sandboxId) {
38
+ const access = getAccessContextFromResponse(res);
39
+ return canReadSandbox(access, sandboxId);
40
+ }
41
+ export function ensureSandboxWriteAccess(res, sandboxId) {
42
+ const access = getAccessContextFromResponse(res);
43
+ return canWriteSandbox(access, sandboxId);
44
+ }
45
+ //# sourceMappingURL=accessHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessHelpers.js","sourceRoot":"","sources":["../../../src/server/api/accessHelpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAE9B,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtE,MAAM,UAAU,4BAA4B,CAAC,GAAa;IACxD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI;QAClC,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,KAAK;QAClB,WAAW,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE;QAClF,kBAAkB,EAAE,MAAM;QAC1B,gBAAgB,EAAE,EAAE;QACpB,iBAAiB,EAAE,EAAE;QACrB,0BAA0B,EAAE,KAAK;QACjC,sBAAsB,EAAE,CAAC;QACzB,EAAE,EAAE,EAAE;KACP,CAAyB,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,UAAkB;IACzD,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAwC,CAAC;IACvH,OAAO,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,CAAC,OAAO,CAA+C,CAAC;IACxH,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,OAAO,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,WAAmB;IAC3D,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAwC,CAAC;IACxH,OAAO,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,GAAa,EAAE,OAAO,GAAG,WAAW;IACzD,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAa,EAAE,IAAsD;IACpG,MAAM,MAAM,GAAG,4BAA4B,CAAC,GAAG,CAAC,CAAC;IACjD,OAAO,MAAM,CAAC,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,GAAa,EAAE,SAAiB;IACtE,MAAM,MAAM,GAAG,4BAA4B,CAAC,GAAG,CAAC,CAAC;IACjD,OAAO,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,GAAa,EAAE,SAAiB;IACvE,MAAM,MAAM,GAAG,4BAA4B,CAAC,GAAG,CAAC,CAAC;IACjD,OAAO,eAAe,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC5C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/server/api/chat.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAkaxB,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/server/api/chat.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAgcxB,eAAe,MAAM,CAAC"}
@@ -4,6 +4,7 @@ import { db } from '../db.js';
4
4
  import { processMessage } from '../react/agent.js';
5
5
  import { callAiText } from '../aiClient.js';
6
6
  import { supplementBatchCreateActionsByContent } from './batchRecovery.js';
7
+ import { ensurePageAccess, ensureSandboxReadAccess, ensureSandboxWriteAccess, forbid } from './accessHelpers.js';
7
8
  const router = Router();
8
9
  function isExecuteIntent(text) {
9
10
  const normalized = text.trim().toLowerCase();
@@ -30,11 +31,17 @@ router.post('/', (_req, res) => {
30
31
  });
31
32
  router.post('/react', async (req, res) => {
32
33
  try {
34
+ if (!ensurePageAccess(res, 'sandboxes')) {
35
+ return forbid(res, 'No permission to use sandbox chat');
36
+ }
33
37
  const { content, sandbox_id, history, pending_action } = req.body;
34
38
  if (!content) {
35
39
  const response = { success: false, error: 'Content is required' };
36
40
  return res.status(400).json(response);
37
41
  }
42
+ if (!sandbox_id || !ensureSandboxWriteAccess(res, String(sandbox_id))) {
43
+ return forbid(res, 'No permission to write sandbox chat');
44
+ }
38
45
  const timestamp = now();
39
46
  const chatId = uuidv4();
40
47
  db.prepare('INSERT INTO chats (id, role, content, created_at) VALUES (?, ?, ?, ?)').run(chatId, 'user', content, timestamp);
@@ -202,11 +209,17 @@ router.post('/react', async (req, res) => {
202
209
  });
203
210
  router.post('/confirm', async (req, res) => {
204
211
  try {
212
+ if (!ensurePageAccess(res, 'sandboxes')) {
213
+ return forbid(res, 'No permission to confirm sandbox actions');
214
+ }
205
215
  const { sandbox_id, confirm_items } = req.body;
206
216
  if (!sandbox_id || !confirm_items || !Array.isArray(confirm_items)) {
207
217
  const response = { success: false, error: 'Invalid parameters' };
208
218
  return res.status(400).json(response);
209
219
  }
220
+ if (!ensureSandboxWriteAccess(res, String(sandbox_id))) {
221
+ return forbid(res, 'No permission to confirm sandbox actions');
222
+ }
210
223
  const { executeTool } = await import('../react/tools.js');
211
224
  const results = [];
212
225
  for (const item of confirm_items) {
@@ -258,11 +271,17 @@ router.post('/confirm', async (req, res) => {
258
271
  });
259
272
  router.post('/execute', async (req, res) => {
260
273
  try {
274
+ if (!ensurePageAccess(res, 'sandboxes')) {
275
+ return forbid(res, 'No permission to execute sandbox actions');
276
+ }
261
277
  const { sandbox_id, operations } = req.body;
262
278
  if (!sandbox_id || !operations || !Array.isArray(operations)) {
263
279
  const response = { success: false, error: 'Invalid parameters' };
264
280
  return res.status(400).json(response);
265
281
  }
282
+ if (!ensureSandboxWriteAccess(res, String(sandbox_id))) {
283
+ return forbid(res, 'No permission to execute sandbox actions');
284
+ }
266
285
  const timestamp = now();
267
286
  const results = [];
268
287
  for (const op of operations) {
@@ -305,7 +324,13 @@ router.post('/execute', async (req, res) => {
305
324
  });
306
325
  router.get('/sandbox/:sandboxId/insight', async (req, res) => {
307
326
  try {
327
+ if (!ensurePageAccess(res, 'sandboxes')) {
328
+ return forbid(res, 'No permission to view sandbox insight');
329
+ }
308
330
  const { sandboxId } = req.params;
331
+ if (!ensureSandboxReadAccess(res, sandboxId)) {
332
+ return forbid(res, 'No permission to view sandbox insight');
333
+ }
309
334
  const sandbox = db.prepare('SELECT id, name FROM sandboxes WHERE id = ?').get(sandboxId);
310
335
  if (!sandbox) {
311
336
  const response = { success: false, error: 'Sandbox not found' };
@@ -347,7 +372,13 @@ router.get('/sandbox/:sandboxId/insight', async (req, res) => {
347
372
  });
348
373
  router.get('/sandbox/:sandboxId', (req, res) => {
349
374
  try {
375
+ if (!ensurePageAccess(res, 'sandboxes')) {
376
+ return forbid(res, 'No permission to view sandbox chat');
377
+ }
350
378
  const { sandboxId } = req.params;
379
+ if (!ensureSandboxReadAccess(res, sandboxId)) {
380
+ return forbid(res, 'No permission to view sandbox chat');
381
+ }
351
382
  const relations = db.prepare('SELECT chat_id FROM chat_sandbox_relations WHERE sandbox_id = ?').all(sandboxId);
352
383
  const chatIds = relations.map(r => r.chat_id);
353
384
  if (chatIds.length === 0) {