@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.
- package/README.md +41 -0
- package/dist/cli.js +55 -18
- package/dist/cli.js.map +1 -1
- package/dist/server/accessControl.d.ts +29 -0
- package/dist/server/accessControl.d.ts.map +1 -0
- package/dist/server/accessControl.js +161 -0
- package/dist/server/accessControl.js.map +1 -0
- package/dist/server/api/accessHelpers.d.ts +11 -0
- package/dist/server/api/accessHelpers.d.ts.map +1 -0
- package/dist/server/api/accessHelpers.js +45 -0
- package/dist/server/api/accessHelpers.js.map +1 -0
- package/dist/server/api/chat.d.ts.map +1 -1
- package/dist/server/api/chat.js +31 -0
- package/dist/server/api/chat.js.map +1 -1
- package/dist/server/api/diary.d.ts.map +1 -1
- package/dist/server/api/diary.js +61 -1
- package/dist/server/api/diary.js.map +1 -1
- package/dist/server/api/nodeEntities.d.ts.map +1 -1
- package/dist/server/api/nodeEntities.js +31 -0
- package/dist/server/api/nodeEntities.js.map +1 -1
- package/dist/server/api/projectSettings.d.ts +3 -0
- package/dist/server/api/projectSettings.d.ts.map +1 -0
- package/dist/server/api/projectSettings.js +29 -0
- package/dist/server/api/projectSettings.js.map +1 -0
- package/dist/server/api/sandbox.d.ts.map +1 -1
- package/dist/server/api/sandbox.js +35 -1
- package/dist/server/api/sandbox.js.map +1 -1
- package/dist/server/api/settings.d.ts.map +1 -1
- package/dist/server/api/settings.js +25 -1
- package/dist/server/api/settings.js.map +1 -1
- package/dist/server/api/workItem.d.ts.map +1 -1
- package/dist/server/api/workItem.js +59 -0
- package/dist/server/api/workItem.js.map +1 -1
- package/dist/server/config.d.ts +3 -2
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +6 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/index.d.ts +8 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +102 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/projectConfig.d.ts +37 -0
- package/dist/server/projectConfig.d.ts.map +1 -0
- package/dist/server/projectConfig.js +180 -0
- package/dist/server/projectConfig.js.map +1 -0
- package/dist/web/app.js +760 -44
- package/dist/web/index.html +107 -60
- package/dist/web/styles.css +256 -11
- package/dist/web/vueRenderers.js +71 -57
- 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
|
|
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 ||
|
|
7
|
-
host: String(process.env.HOST ||
|
|
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
|
-
|
|
77
|
+
${defaults.commandName}
|
|
45
78
|
|
|
46
79
|
Usage:
|
|
47
|
-
|
|
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:
|
|
52
|
-
--host Set listen host (default:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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":";AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
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":"
|
|
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"}
|
package/dist/server/api/chat.js
CHANGED
|
@@ -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) {
|