@oneentry/mcp-platform-server 0.1.1 → 0.1.4
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/LICENSE +21 -0
- package/README.md +62 -61
- package/dist/api/audit.d.ts +0 -11
- package/dist/api/audit.js +0 -10
- package/dist/api/auth.d.ts +0 -18
- package/dist/api/auth.js +0 -20
- package/dist/api/build-catalog.d.ts +1 -14
- package/dist/api/build-catalog.js +16 -77
- package/dist/api/catalog.d.ts +5 -27
- package/dist/api/catalog.d.ts.map +1 -1
- package/dist/api/catalog.js +31 -43
- package/dist/api/catalog.js.map +1 -1
- package/dist/api/client.d.ts +0 -24
- package/dist/api/client.js +6 -28
- package/dist/api/normalize-schema.d.ts +0 -11
- package/dist/api/normalize-schema.js +0 -18
- package/dist/api/policy.d.ts +0 -23
- package/dist/api/policy.js +0 -0
- package/dist/api/shape.d.ts +0 -13
- package/dist/api/shape.js +0 -21
- package/dist/api/swagger-source.d.ts +13 -12
- package/dist/api/swagger-source.js +93 -50
- package/dist/api/types.d.ts +0 -34
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js +0 -1
- package/dist/bin/cli.d.ts +0 -1
- package/dist/bin/cli.js +1 -5
- package/dist/bin/cli.js.map +1 -1
- package/dist/config/config.d.ts +0 -54
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +0 -60
- package/dist/config/config.js.map +1 -1
- package/dist/index.d.ts +2 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/knowledge/chunk.d.ts +0 -15
- package/dist/knowledge/chunk.js +0 -16
- package/dist/knowledge/github.d.ts +0 -25
- package/dist/knowledge/github.js +0 -40
- package/dist/knowledge/loader.d.ts +0 -28
- package/dist/knowledge/loader.d.ts.map +1 -1
- package/dist/knowledge/loader.js +0 -26
- package/dist/knowledge/loader.js.map +1 -1
- package/dist/knowledge/search.d.ts +0 -22
- package/dist/knowledge/search.d.ts.map +1 -1
- package/dist/knowledge/search.js +0 -34
- package/dist/knowledge/search.js.map +1 -1
- package/dist/knowledge/tar.d.ts +0 -15
- package/dist/knowledge/tar.js +0 -33
- package/dist/knowledge/types.d.ts +0 -35
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js +0 -1
- package/dist/server.d.ts +0 -12
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +29 -15
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +0 -23
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +0 -18
- package/dist/session.js.map +1 -1
- package/dist/tools/api-call.d.ts +0 -2
- package/dist/tools/api-call.js +0 -23
- package/dist/tools/api-discovery.d.ts +0 -2
- package/dist/tools/api-discovery.js +2 -5
- package/dist/tools/docs.d.ts +0 -2
- package/dist/tools/docs.js +1 -5
- package/dist/tools/guide.d.ts +0 -7
- package/dist/tools/guide.d.ts.map +1 -1
- package/dist/tools/guide.js +2 -9
- package/dist/tools/guide.js.map +1 -1
- package/dist/tools/result.d.ts +0 -8
- package/dist/tools/result.js +0 -7
- package/dist/tools/whoami.d.ts +0 -2
- package/dist/tools/whoami.d.ts.map +1 -1
- package/dist/tools/whoami.js +1 -3
- package/dist/tools/whoami.js.map +1 -1
- package/dist/transports/http.d.ts +0 -6
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +1 -27
- package/dist/transports/http.js.map +1 -1
- package/dist/transports/stdio.d.ts +0 -6
- package/dist/transports/stdio.js +0 -6
- package/dist/transports/stdio.js.map +1 -1
- package/knowledge/operating-rules.md +70 -125
- package/package.json +18 -5
- package/dist/api/build-catalog.d.ts.map +0 -1
- package/dist/api/build-catalog.js.map +0 -1
- package/dist/api/normalize-schema.d.ts.map +0 -1
- package/dist/api/normalize-schema.js.map +0 -1
- package/dist/api/swagger-source.d.ts.map +0 -1
- package/dist/api/swagger-source.js.map +0 -1
- package/dist/knowledge/github.d.ts.map +0 -1
- package/dist/knowledge/github.js.map +0 -1
- package/dist/knowledge/tar.d.ts.map +0 -1
- package/dist/knowledge/tar.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OneEntry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @oneentry/mcp-platform-server
|
|
2
2
|
|
|
3
|
-
MCP server that lets an AI agent **operate the OneEntry Admin API** — grounded in
|
|
4
|
-
rules rather than in guesswork.
|
|
3
|
+
MCP server that lets an AI agent **operate the OneEntry Admin API** — grounded in published
|
|
4
|
+
operating rules rather than in guesswork.
|
|
5
5
|
|
|
6
6
|
Nothing is frozen into the package. Both halves are fetched at runtime and cached:
|
|
7
7
|
|
|
@@ -9,14 +9,15 @@ Nothing is frozen into the package. Both halves are fetched at runtime and cache
|
|
|
9
9
|
([ONEENTRY-PLATFORM/oneentry-platform-rules](https://github.com/ONEENTRY-PLATFORM/oneentry-platform-rules)),
|
|
10
10
|
chunked into searchable sections. Editing a doc there — through the GitHub UI, a PR, whatever — takes effect on the next
|
|
11
11
|
restart of any server, with no npm release.
|
|
12
|
-
- **Actuation.** Every Admin API operation, built from the
|
|
12
|
+
- **Actuation.** Every Admin API operation, built from the instance's own API document at
|
|
13
13
|
`GET /api/admin/workflows/nodes/admin-api/swagger.json`, with parameter and body schemas, the
|
|
14
14
|
permission each one requires, and a risk classification — reachable through one generic invoke
|
|
15
15
|
tool instead of one tool per endpoint. The catalog always describes the API that is actually
|
|
16
16
|
running, not a snapshot of it.
|
|
17
17
|
|
|
18
|
-
The one thing still committed here is `data/permissions.json`:
|
|
19
|
-
and never reaches
|
|
18
|
+
The one thing still committed here is `data/permissions.json`: the permission an operation requires
|
|
19
|
+
is declared in platform code and never reaches the API document, so that map has to be recovered
|
|
20
|
+
separately.
|
|
20
21
|
|
|
21
22
|
Only the Admin API is exposed.
|
|
22
23
|
|
|
@@ -26,7 +27,7 @@ Only the Admin API is exposed.
|
|
|
26
27
|
npm i -g @oneentry/mcp-platform-server # or use npx, below
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
## Local mode (agent and
|
|
30
|
+
## Local mode (agent and instance on the same machine)
|
|
30
31
|
|
|
31
32
|
Add to `.mcp.json` in the repo you work from:
|
|
32
33
|
|
|
@@ -46,21 +47,21 @@ Add to `.mcp.json` in the repo you work from:
|
|
|
46
47
|
}
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
Nothing has to be checked out locally. On first run the knowledge repository is downloaded once and
|
|
50
51
|
cached; later runs re-use the cache and only ask GitHub whether the commit moved, at most once an
|
|
51
52
|
hour.
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
The base URL must point at an instance that exposes the Admin API under `/api/admin`.
|
|
54
55
|
|
|
55
|
-
## Remote mode (hosted, many agents, one
|
|
56
|
+
## Remote mode (hosted, many agents, one instance)
|
|
56
57
|
|
|
57
58
|
```bash
|
|
58
59
|
oneentry-mcp-platform --http --port 8931 --audit /var/log/oneentry-mcp-platform-audit.jsonl \
|
|
59
|
-
--base-url https://
|
|
60
|
+
--base-url https://your-instance.example/api/admin \
|
|
60
61
|
--allowed-origins https://agent.example
|
|
61
62
|
```
|
|
62
63
|
|
|
63
|
-
Each MCP session carries its own
|
|
64
|
+
Each MCP session carries its own identity, passed as connection headers — never as tool
|
|
64
65
|
arguments, so a prompt-injected instruction cannot swap identities:
|
|
65
66
|
|
|
66
67
|
| header | meaning |
|
|
@@ -78,7 +79,7 @@ Sessions never share tokens or confirm tokens. `GET /health` reports liveness an
|
|
|
78
79
|
| `cms_docs_search` | search the knowledge base, returns sections with snippets |
|
|
79
80
|
| `cms_docs_read` | read one section, with its sibling sections listed for paging |
|
|
80
81
|
| `cms_api_search` | find operations by keyword / tag / method / mutating |
|
|
81
|
-
| `cms_api_describe` | params, body schema, required permission, risk,
|
|
82
|
+
| `cms_api_describe` | params, body schema, required permission, risk, confirm gating |
|
|
82
83
|
| `cms_api_call` | execute one operation; `dryRun` and confirm-gating for mutations |
|
|
83
84
|
| `cms_whoami` | mode, base URL, admin + permissions, knowledge commit, catalog state and warnings |
|
|
84
85
|
|
|
@@ -100,8 +101,8 @@ Read-only by default. `--allow` (or `ONEENTRY_MCP_ALLOW`) raises it:
|
|
|
100
101
|
`auth/logout/all-users`), require a **confirm token**: the first call returns the token *and the
|
|
101
102
|
current state of the target*, fetched via the sibling GET, so a human can see what is about to
|
|
102
103
|
change. The token is bound to `hash(opId + arguments)`, single-use, valid 5 minutes.
|
|
103
|
-
- Permissions are checked locally first: if the admin lacks the
|
|
104
|
-
|
|
104
|
+
- Permissions are checked locally first: if the admin lacks the permission the operation declares,
|
|
105
|
+
the call is refused without being sent.
|
|
105
106
|
- Every non-GET call is appended to a JSONL audit log (arguments are hashed, not stored). Required
|
|
106
107
|
in remote mode.
|
|
107
108
|
|
|
@@ -111,10 +112,10 @@ A public GitHub repository of small markdown files. Layout:
|
|
|
111
112
|
|
|
112
113
|
```
|
|
113
114
|
knowledge/
|
|
114
|
-
mcp/operating-rules.md
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
mcp/operating-rules.md -> docId "mcp/operating-rules"
|
|
116
|
+
mcp/docs/api/orders.md -> docId "mcp/docs/api/orders"
|
|
117
|
+
mcp/docs/server/cms-api-call.md -> docId "mcp/docs/server/cms-api-call"
|
|
118
|
+
mcp/docs/api/orders/index.md -> docId "mcp/docs/api/orders"
|
|
118
119
|
```
|
|
119
120
|
|
|
120
121
|
The docId is the path under `knowledge/` without `.md`; `<name>/index.md` collapses back to
|
|
@@ -140,10 +141,10 @@ restart is instant and the previous commit is pruned.
|
|
|
140
141
|
Which repository and which commit is a configuration choice, not a constant: `--knowledge-repo`
|
|
141
142
|
(`ONEENTRY_MCP_KNOWLEDGE_REPO`, default `ONEENTRY-PLATFORM/oneentry-platform-rules`) and
|
|
142
143
|
`--knowledge-ref`
|
|
143
|
-
(`ONEENTRY_MCP_KNOWLEDGE_REF`, default `main`) — a ref may be a branch, a tag or a commit sha, so
|
|
144
|
-
|
|
145
|
-
API rate limit; the repo is public. `cms_whoami` reports the repo, ref and 12-character commit
|
|
146
|
-
running server actually loaded.
|
|
144
|
+
(`ONEENTRY_MCP_KNOWLEDGE_REF`, default `main`) — a ref may be a branch, a tag or a commit sha, so an
|
|
145
|
+
instance can be frozen on a known-good corpus. `ONEENTRY_GITHUB_TOKEN` is optional and only raises
|
|
146
|
+
the API rate limit; the repo is public. `cms_whoami` reports the repo, ref and 12-character commit
|
|
147
|
+
the running server actually loaded.
|
|
147
148
|
|
|
148
149
|
### Reading the docs from a local folder instead
|
|
149
150
|
|
|
@@ -165,67 +166,67 @@ look like an empty corpus.
|
|
|
165
166
|
|
|
166
167
|
### Known limitation: retrieval is single-language
|
|
167
168
|
|
|
168
|
-
Search does not translate
|
|
169
|
-
find an English-only document no matter how it is weighted
|
|
170
|
-
content, not code: give such a document a short summary section in the other language, or ask in the
|
|
171
|
-
language the document is written in.
|
|
169
|
+
Search does not translate, and the corpus is English. A question asked in another language will not
|
|
170
|
+
find an English-only document no matter how it is weighted. The fix is content, not code.
|
|
172
171
|
|
|
173
|
-
###
|
|
172
|
+
### The corpus is hand-authored
|
|
174
173
|
|
|
175
|
-
The knowledge
|
|
176
|
-
|
|
174
|
+
The knowledge repository is written for a public audience, one file at a time. There is no import
|
|
175
|
+
script and one must not be added: bulk-copying internal documentation into it would publish source
|
|
176
|
+
paths, storage identifiers and tracker ids in a single command, and a repository that is public and
|
|
177
|
+
mirrored cannot be un-published. `npm run publish:knowledge` exists only to refuse.
|
|
177
178
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
It copies `.claude/docs`, `.claude/tests` and `.claude/rules` from both checkouts, splits anything
|
|
183
|
-
over 12 KB at `##` boundaries, and reports files in the target it did not produce. It **does not
|
|
184
|
-
commit or push** — that diff is the moment internal text becomes public, and a human owns it.
|
|
185
|
-
`.claude/fixes` and the CMS `CLAUDE.md` files are deliberately not published.
|
|
179
|
+
At release time, copy `knowledge/mcp/operating-rules.md` from the rules repository over this
|
|
180
|
+
package's `knowledge/operating-rules.md` — the rules repository is the source, and the bundled seed
|
|
181
|
+
is never hand-edited.
|
|
186
182
|
|
|
187
183
|
## Where the catalog comes from
|
|
188
184
|
|
|
189
185
|
Two sources, because neither alone is enough:
|
|
190
186
|
|
|
191
187
|
1. `GET {baseUrl}/workflows/nodes/admin-api/swagger.json?langCode=en_US` — paths, operation ids and
|
|
192
|
-
schemas, straight from the
|
|
193
|
-
|
|
194
|
-
2. `data/permissions.json` — the
|
|
195
|
-
Regenerate it when the
|
|
188
|
+
schemas, straight from the instance you are pointed at. Cached per base URL, so an unreachable
|
|
189
|
+
instance still yields the last known catalog.
|
|
190
|
+
2. `data/permissions.json` — the permission each operation requires, which the API document does not
|
|
191
|
+
express. Regenerate it when the platform changes its permissions:
|
|
196
192
|
|
|
197
193
|
```bash
|
|
198
|
-
npm run sync:permissions -- --back
|
|
194
|
+
npm run sync:permissions -- --back <path to a platform checkout>
|
|
199
195
|
```
|
|
200
196
|
|
|
201
|
-
That pass walks base classes too, since
|
|
202
|
-
|
|
197
|
+
That pass walks base classes too, since the operation id is derived from the *registered* class
|
|
198
|
+
while the handler often lives in a parent.
|
|
203
199
|
|
|
204
200
|
Inconsistencies are surfaced as catalog `warnings` (visible in `cms_whoami`) rather than swallowed:
|
|
205
201
|
paths outside `/api/admin` (dropped — this server exposes the Admin API only), operations with no
|
|
206
|
-
`operationId`,
|
|
207
|
-
permissions
|
|
208
|
-
those, so such endpoints answer 403 forever.
|
|
202
|
+
`operationId`, operations whose permission is known but which the instance does not expose, and
|
|
203
|
+
permissions that operations require but no admin can hold — such endpoints answer 403 forever.
|
|
209
204
|
|
|
210
|
-
If the
|
|
211
|
-
is safe — there is nothing to call — but silence would read as "the
|
|
205
|
+
If the instance does not serve its API document at all, the catalog is **empty** and says so loudly.
|
|
206
|
+
Empty is safe — there is nothing to call — but silence would read as "the platform has no such
|
|
207
|
+
endpoint".
|
|
212
208
|
|
|
213
|
-
### The
|
|
209
|
+
### The API document is not valid JSON Schema
|
|
214
210
|
|
|
215
|
-
Many `type` values in the source
|
|
216
|
-
`CommonLocalizeInfos`, `AttributesSets`, `image`, `date`). The build
|
|
217
|
-
marks the rest `"x-loose": true` with the original under `x-source-type`.
|
|
218
|
-
`example` is the contract, and client-side body validation is deliberately
|
|
219
|
-
is the real validator, so a call is never blocked because a loose field
|
|
211
|
+
Many `type` values in the source document are expressions in the platform's own type language
|
|
212
|
+
(`Record<string, any>`, `CommonLocalizeInfos`, `AttributesSets`, `image`, `date`). The build
|
|
213
|
+
normalizes what it can and marks the rest `"x-loose": true` with the original under `x-source-type`.
|
|
214
|
+
For loose fields the `example` is the contract, and client-side body validation is deliberately
|
|
215
|
+
advisory — the instance is the real validator, so a call is never blocked because a loose field
|
|
216
|
+
could not be checked.
|
|
220
217
|
|
|
221
218
|
## Tests
|
|
222
219
|
|
|
223
220
|
```bash
|
|
224
|
-
npm test # unit + MCP protocol + remote-mode transport (no
|
|
225
|
-
npm run test:integration # against a live
|
|
221
|
+
npm test # unit + MCP protocol + remote-mode transport (no instance needed)
|
|
222
|
+
npm run test:integration # against a live instance; skips cleanly if unreachable
|
|
226
223
|
```
|
|
227
224
|
|
|
228
|
-
The integration suite needs
|
|
229
|
-
`ONEENTRY_CMS_LOGIN`/`ONEENTRY_CMS_PASSWORD`. It walks search → describe → call, asserts a
|
|
230
|
-
sends nothing, and runs a full create → confirm-gated delete cycle on a throwaway
|
|
231
|
-
checking the audit log.
|
|
225
|
+
The integration suite needs `ONEENTRY_CMS_BASE_URL` pointing at an instance that exposes the Admin
|
|
226
|
+
API, plus `ONEENTRY_CMS_LOGIN`/`ONEENTRY_CMS_PASSWORD`. It walks search → describe → call, asserts a
|
|
227
|
+
read-only refusal sends nothing, and runs a full create → confirm-gated delete cycle on a throwaway
|
|
228
|
+
template while checking the audit log.
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT — see [LICENSE](LICENSE).
|
package/dist/api/audit.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Одна запись аудита изменяющего вызова. */
|
|
2
1
|
export interface AuditEntry {
|
|
3
2
|
at: string;
|
|
4
3
|
mode: string;
|
|
@@ -10,11 +9,6 @@ export interface AuditEntry {
|
|
|
10
9
|
outcome: 'denied' | 'needs-confirm' | 'sent';
|
|
11
10
|
status?: number;
|
|
12
11
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Журнал изменяющих вызовов в формате JSONL.
|
|
15
|
-
* Пишем хэш аргументов, а не сами аргументы: тела запросов содержат
|
|
16
|
-
* пользовательские данные, а для разбора инцидента достаточно связать вызовы между собой.
|
|
17
|
-
*/
|
|
18
12
|
export declare class AuditLog {
|
|
19
13
|
private readonly path;
|
|
20
14
|
private readonly mode;
|
|
@@ -23,10 +17,5 @@ export declare class AuditLog {
|
|
|
23
17
|
mode: string;
|
|
24
18
|
});
|
|
25
19
|
static hashArgs(args: unknown): string;
|
|
26
|
-
/**
|
|
27
|
-
* Пишет запись, не роняя вызов при недоступности файла: аудит важен,
|
|
28
|
-
* но потеря строки лога — не причина провалить операцию, о которой уже отчитались.
|
|
29
|
-
*/
|
|
30
20
|
record(entry: Omit<AuditEntry, 'at' | 'mode'>): void;
|
|
31
21
|
}
|
|
32
|
-
//# sourceMappingURL=audit.d.ts.map
|
package/dist/api/audit.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { appendFileSync } from 'node:fs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
/**
|
|
4
|
-
* Журнал изменяющих вызовов в формате JSONL.
|
|
5
|
-
* Пишем хэш аргументов, а не сами аргументы: тела запросов содержат
|
|
6
|
-
* пользовательские данные, а для разбора инцидента достаточно связать вызовы между собой.
|
|
7
|
-
*/
|
|
8
3
|
export class AuditLog {
|
|
9
4
|
path;
|
|
10
5
|
mode;
|
|
@@ -15,10 +10,6 @@ export class AuditLog {
|
|
|
15
10
|
static hashArgs(args) {
|
|
16
11
|
return createHash('sha256').update(JSON.stringify(args ?? null)).digest('hex').slice(0, 16);
|
|
17
12
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Пишет запись, не роняя вызов при недоступности файла: аудит важен,
|
|
20
|
-
* но потеря строки лога — не причина провалить операцию, о которой уже отчитались.
|
|
21
|
-
*/
|
|
22
13
|
record(entry) {
|
|
23
14
|
if (!this.path) {
|
|
24
15
|
return;
|
|
@@ -32,4 +23,3 @@ export class AuditLog {
|
|
|
32
23
|
}
|
|
33
24
|
}
|
|
34
25
|
}
|
|
35
|
-
//# sourceMappingURL=audit.js.map
|
package/dist/api/auth.d.ts
CHANGED
|
@@ -1,31 +1,21 @@
|
|
|
1
1
|
import type { Config } from '../config/config.js';
|
|
2
|
-
/** Пара токенов Admin API, как её отдаёт `POST /auth/login`. */
|
|
3
2
|
export interface TokenPair {
|
|
4
3
|
accessToken: string;
|
|
5
4
|
refreshToken: string;
|
|
6
5
|
}
|
|
7
|
-
/** Данные аутентифицированного админа, восстановленные из токена и API. */
|
|
8
6
|
export interface AdminIdentity {
|
|
9
7
|
id: number;
|
|
10
8
|
login?: string;
|
|
11
9
|
permissions: string[];
|
|
12
10
|
}
|
|
13
|
-
/** Учётные данные одной сессии. В remote-режиме у каждой сессии свои. */
|
|
14
11
|
export interface Credentials {
|
|
15
12
|
login?: string;
|
|
16
13
|
password?: string;
|
|
17
14
|
token?: string;
|
|
18
15
|
}
|
|
19
|
-
/** Ошибка аутентификации с текстом, пригодным для показа модели. */
|
|
20
16
|
export declare class AuthError extends Error {
|
|
21
17
|
}
|
|
22
|
-
/** Достаёт идентификатор админа из payload access-токена. */
|
|
23
18
|
export declare const adminIdFromToken: (token: string) => number;
|
|
24
|
-
/**
|
|
25
|
-
* Держит пару токенов для одного набора учётных данных.
|
|
26
|
-
* Токены живут только в памяти и только внутри сессии: в remote-режиме
|
|
27
|
-
* это то, что не даёт токену одной сессии утечь в другую.
|
|
28
|
-
*/
|
|
29
19
|
export declare class TokenStore {
|
|
30
20
|
private pair;
|
|
31
21
|
private readonly baseUrl;
|
|
@@ -36,18 +26,10 @@ export declare class TokenStore {
|
|
|
36
26
|
timeoutMs: number;
|
|
37
27
|
credentials: Credentials;
|
|
38
28
|
});
|
|
39
|
-
/** Есть ли чем аутентифицироваться вообще. */
|
|
40
29
|
get isConfigured(): boolean;
|
|
41
30
|
private post;
|
|
42
31
|
private static readPair;
|
|
43
|
-
/** Возвращает access-токен, логинясь при первом обращении. */
|
|
44
32
|
accessToken(): Promise<string>;
|
|
45
|
-
/**
|
|
46
|
-
* Обновляет пару после 401. Если refresh невозможен (передан готовый токен
|
|
47
|
-
* или refresh отвергнут) — пробует полный логин, и только потом сдаётся.
|
|
48
|
-
*/
|
|
49
33
|
refresh(): Promise<string>;
|
|
50
34
|
}
|
|
51
|
-
/** Учётные данные из конфигурации — источник по умолчанию для local-режима. */
|
|
52
35
|
export declare const credentialsFromConfig: (config: Config) => Credentials;
|
|
53
|
-
//# sourceMappingURL=auth.d.ts.map
|
package/dist/api/auth.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Ошибка аутентификации с текстом, пригодным для показа модели. */
|
|
2
1
|
export class AuthError extends Error {
|
|
3
2
|
}
|
|
4
3
|
const decodeJwtPayload = (token) => {
|
|
@@ -13,7 +12,6 @@ const decodeJwtPayload = (token) => {
|
|
|
13
12
|
}
|
|
14
13
|
return parsed;
|
|
15
14
|
};
|
|
16
|
-
/** Достаёт идентификатор админа из payload access-токена. */
|
|
17
15
|
export const adminIdFromToken = (token) => {
|
|
18
16
|
const payload = decodeJwtPayload(token);
|
|
19
17
|
const id = payload['id'] ?? payload['sub'] ?? payload['userId'];
|
|
@@ -23,11 +21,6 @@ export const adminIdFromToken = (token) => {
|
|
|
23
21
|
}
|
|
24
22
|
return numeric;
|
|
25
23
|
};
|
|
26
|
-
/**
|
|
27
|
-
* Держит пару токенов для одного набора учётных данных.
|
|
28
|
-
* Токены живут только в памяти и только внутри сессии: в remote-режиме
|
|
29
|
-
* это то, что не даёт токену одной сессии утечь в другую.
|
|
30
|
-
*/
|
|
31
24
|
export class TokenStore {
|
|
32
25
|
pair;
|
|
33
26
|
baseUrl;
|
|
@@ -41,7 +34,6 @@ export class TokenStore {
|
|
|
41
34
|
this.pair = { accessToken: params.credentials.token, refreshToken: '' };
|
|
42
35
|
}
|
|
43
36
|
}
|
|
44
|
-
/** Есть ли чем аутентифицироваться вообще. */
|
|
45
37
|
get isConfigured() {
|
|
46
38
|
return Boolean(this.credentials.token || (this.credentials.login && this.credentials.password));
|
|
47
39
|
}
|
|
@@ -54,11 +46,6 @@ export class TokenStore {
|
|
|
54
46
|
});
|
|
55
47
|
const text = await response.text();
|
|
56
48
|
if (!response.ok) {
|
|
57
|
-
/**
|
|
58
|
-
* Admin API отвечает 401 «Login or password incorrect» и на developer-аккаунт:
|
|
59
|
-
* контроллер отбрасывает админов с isDeveloper. Без этой подсказки агент
|
|
60
|
-
* будет бесконечно перепроверять пароль.
|
|
61
|
-
*/
|
|
62
49
|
const hint = response.status === 401
|
|
63
50
|
? ' The Admin API also returns 401 for accounts flagged isDeveloper — those belong to the Developer API, which this server does not expose.'
|
|
64
51
|
: '';
|
|
@@ -81,7 +68,6 @@ export class TokenStore {
|
|
|
81
68
|
refreshToken: typeof refreshToken === 'string' ? refreshToken : '',
|
|
82
69
|
};
|
|
83
70
|
}
|
|
84
|
-
/** Возвращает access-токен, логинясь при первом обращении. */
|
|
85
71
|
async accessToken() {
|
|
86
72
|
if (this.pair) {
|
|
87
73
|
return this.pair.accessToken;
|
|
@@ -95,10 +81,6 @@ export class TokenStore {
|
|
|
95
81
|
}));
|
|
96
82
|
return this.pair.accessToken;
|
|
97
83
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Обновляет пару после 401. Если refresh невозможен (передан готовый токен
|
|
100
|
-
* или refresh отвергнут) — пробует полный логин, и только потом сдаётся.
|
|
101
|
-
*/
|
|
102
84
|
async refresh() {
|
|
103
85
|
const refreshToken = this.pair?.refreshToken;
|
|
104
86
|
this.pair = undefined;
|
|
@@ -117,10 +99,8 @@ export class TokenStore {
|
|
|
117
99
|
return this.accessToken();
|
|
118
100
|
}
|
|
119
101
|
}
|
|
120
|
-
/** Учётные данные из конфигурации — источник по умолчанию для local-режима. */
|
|
121
102
|
export const credentialsFromConfig = (config) => ({
|
|
122
103
|
...(config.login ? { login: config.login } : {}),
|
|
123
104
|
...(config.password ? { password: config.password } : {}),
|
|
124
105
|
...(config.token ? { token: config.token } : {}),
|
|
125
106
|
});
|
|
126
|
-
//# sourceMappingURL=auth.js.map
|
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
import type { Catalog } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Связь операция -> требуемое право и полный список прав.
|
|
4
|
-
* Собирается на сборке проходом по исходникам dnk-back: декоратор
|
|
5
|
-
* `@GrantByPermission` в swagger не попадает никогда, ни в файловый, ни в живой.
|
|
6
|
-
*/
|
|
7
2
|
export interface PermissionMap {
|
|
8
3
|
byOpId: Record<string, string>;
|
|
9
4
|
permissions: string[];
|
|
10
5
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Строит каталог операций из swagger стенда и карты прав.
|
|
13
|
-
* Два источника, потому что ни одного не хватает: swagger даёт пути и схемы,
|
|
14
|
-
* а требуемое право объявлено декоратором и в swagger не попадает.
|
|
15
|
-
* Классификация риска и список навсегда подтверждаемых путей вычисляются здесь,
|
|
16
|
-
* то есть в коде пакета, — swagger приходит по сети и не должен на них влиять.
|
|
17
|
-
*/
|
|
18
6
|
export declare const buildCatalog: (params: {
|
|
19
|
-
/** Тело `swagger.json` как строка: по нему же считается хэш. */
|
|
20
7
|
rawSwagger: string;
|
|
21
8
|
permissions: PermissionMap;
|
|
22
9
|
builtAt: string;
|
|
10
|
+
sourceUrl?: string;
|
|
23
11
|
}) => Catalog;
|
|
24
|
-
//# sourceMappingURL=build-catalog.d.ts.map
|
|
@@ -2,11 +2,6 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { capSchema, normalizeSchema } from './normalize-schema.js';
|
|
3
3
|
const API_PREFIX = '/api/admin';
|
|
4
4
|
const METHODS = ['get', 'post', 'put', 'patch', 'delete'];
|
|
5
|
-
/**
|
|
6
|
-
* Операции, которые остаются за подтверждением при любом уровне `--allow`.
|
|
7
|
-
* Список путей, а не прав: право можно выдать админу по недоразумению,
|
|
8
|
-
* а этот барьер снять нельзя.
|
|
9
|
-
*/
|
|
10
5
|
const ALWAYS_CONFIRM = [
|
|
11
6
|
/^\/immutable-settings/,
|
|
12
7
|
/^\/admins/,
|
|
@@ -17,42 +12,6 @@ const ALWAYS_CONFIRM = [
|
|
|
17
12
|
/^\/settings-general/,
|
|
18
13
|
/^\/system\/captcha-keys/,
|
|
19
14
|
];
|
|
20
|
-
/** Какие разделы документации объясняют операции конкретного тега. */
|
|
21
|
-
const TAG_DOCS = {
|
|
22
|
-
Orders: ['back/docs/orders-module', 'back/docs/order-status-axis'],
|
|
23
|
-
Products: [
|
|
24
|
-
'back/docs/postgres-db-with-attribute-index',
|
|
25
|
-
'back/docs/product-relations',
|
|
26
|
-
'back/docs/isPrice-attribute',
|
|
27
|
-
],
|
|
28
|
-
Payments: ['back/docs/payments-module', 'back/docs/isPrice-attribute'],
|
|
29
|
-
Blocks: ['back/docs/blocks-module'],
|
|
30
|
-
Pages: ['back/docs/pages-module'],
|
|
31
|
-
Discounts: ['back/docs/orders-module'],
|
|
32
|
-
Filters: ['back/docs/filters'],
|
|
33
|
-
'Attributes Sets': [
|
|
34
|
-
'back/docs/postgres-db-with-attribute-index',
|
|
35
|
-
'back/docs/attribute-indexing',
|
|
36
|
-
'back/tests/attributes-sets-e2e',
|
|
37
|
-
],
|
|
38
|
-
Menus: ['back/docs/menus-module', 'back/tests/menus-e2e'],
|
|
39
|
-
'Index Attributes': ['back/docs/attribute-indexing'],
|
|
40
|
-
Modules: ['back/docs/user-permissions'],
|
|
41
|
-
Subscriptions: ['back/docs/subscriptions-module'],
|
|
42
|
-
Forms: ['back/docs/forms-and-form-data'],
|
|
43
|
-
FormData: ['back/docs/forms-and-form-data'],
|
|
44
|
-
'User Permissions': ['back/docs/user-permissions', 'front/docs/user-permissions'],
|
|
45
|
-
'User Groups': ['back/docs/user-permissions'],
|
|
46
|
-
Admins: ['back/docs/user-permissions', 'back/tests/admins-e2e'],
|
|
47
|
-
'Global Search': ['back/docs/global-search'],
|
|
48
|
-
'AI Gateway': ['back/docs/ai-gateway'],
|
|
49
|
-
'AI Gateway / settings': ['back/docs/ai-gateway'],
|
|
50
|
-
'File Uploading': ['back/docs/file-upload-dev-mode'],
|
|
51
|
-
System: ['back/docs/captcha-recaptcha'],
|
|
52
|
-
Templates: ['back/tests/templates-e2e'],
|
|
53
|
-
'Template previews': ['back/tests/template-previews-e2e'],
|
|
54
|
-
Import: ['back/tests/import-e2e'],
|
|
55
|
-
};
|
|
56
15
|
const riskOf = (method) => {
|
|
57
16
|
if (method === 'get') {
|
|
58
17
|
return 'read';
|
|
@@ -61,7 +20,6 @@ const riskOf = (method) => {
|
|
|
61
20
|
};
|
|
62
21
|
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
63
22
|
const asString = (value) => typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
64
|
-
/** Собирает одну человекочитаемую строку описания успешного ответа. */
|
|
65
23
|
const responseSummary = (responses) => {
|
|
66
24
|
if (!isRecord(responses)) {
|
|
67
25
|
return undefined;
|
|
@@ -124,13 +82,6 @@ const buildBody = (raw, components) => {
|
|
|
124
82
|
required: raw['required'] === true,
|
|
125
83
|
};
|
|
126
84
|
};
|
|
127
|
-
/**
|
|
128
|
-
* Строит каталог операций из swagger стенда и карты прав.
|
|
129
|
-
* Два источника, потому что ни одного не хватает: swagger даёт пути и схемы,
|
|
130
|
-
* а требуемое право объявлено декоратором и в swagger не попадает.
|
|
131
|
-
* Классификация риска и список навсегда подтверждаемых путей вычисляются здесь,
|
|
132
|
-
* то есть в коде пакета, — swagger приходит по сети и не должен на них влиять.
|
|
133
|
-
*/
|
|
134
85
|
export const buildCatalog = (params) => {
|
|
135
86
|
const { rawSwagger } = params;
|
|
136
87
|
const swagger = JSON.parse(rawSwagger);
|
|
@@ -143,19 +94,12 @@ export const buildCatalog = (params) => {
|
|
|
143
94
|
: {};
|
|
144
95
|
const { byOpId, permissions } = params.permissions;
|
|
145
96
|
const operations = [];
|
|
146
|
-
/** Пути не из Admin API и операции без operationId — оба случая идут в warnings. */
|
|
147
97
|
const foreignPaths = [];
|
|
148
98
|
const withoutOpId = [];
|
|
149
99
|
for (const [fullPath, pathItem] of Object.entries(swagger['paths'])) {
|
|
150
100
|
if (!isRecord(pathItem)) {
|
|
151
101
|
continue;
|
|
152
102
|
}
|
|
153
|
-
/**
|
|
154
|
-
* Путь вне `/api/admin` в каталог не попадает никогда: сервер по замыслу
|
|
155
|
-
* не выпускает наружу Content и Developer API. Документ приходит по сети,
|
|
156
|
-
* поэтому здесь фильтр, а не исключение — одна лишняя запись не должна
|
|
157
|
-
* лишать агента всего каталога.
|
|
158
|
-
*/
|
|
159
103
|
if (!fullPath.startsWith(API_PREFIX)) {
|
|
160
104
|
foreignPaths.push(fullPath);
|
|
161
105
|
continue;
|
|
@@ -194,7 +138,6 @@ export const buildCatalog = (params) => {
|
|
|
194
138
|
...(responseSummary(operation['responses'])
|
|
195
139
|
? { responseSummary: responseSummary(operation['responses']) }
|
|
196
140
|
: {}),
|
|
197
|
-
docLinks: [...(TAG_DOCS[tag] ?? []), 'mcp/operating-rules'],
|
|
198
141
|
searchText: '',
|
|
199
142
|
};
|
|
200
143
|
entry.searchText = [opId, method.toUpperCase(), path, tag, summary, permission ?? '']
|
|
@@ -213,26 +156,23 @@ export const buildCatalog = (params) => {
|
|
|
213
156
|
warnings.push(`${String(withoutOpId.length)} operation(s) have no operationId and cannot be called ` +
|
|
214
157
|
`(e.g. ${withoutOpId.slice(0, 3).join(', ')}). Report it against the CMS.`);
|
|
215
158
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Обработчик объявляет право, но живой swagger о нём не знает.
|
|
218
|
-
* Раньше это означало устаревший снимок swagger; теперь документ приходит
|
|
219
|
-
* со стенда, поэтому смысл другой: карта прав собрана с другой версии кода,
|
|
220
|
-
* чем развёрнута на стенде. Агенту это важно так же — иначе он получит 404
|
|
221
|
-
* на существующий, по его данным, эндпоинт.
|
|
222
|
-
*/
|
|
223
159
|
const known = new Set(operations.map((o) => o.opId));
|
|
224
160
|
const missing = Object.keys(byOpId).filter((opId) => !known.has(opId) && !opId.startsWith('Developer') && !opId.startsWith('Content'));
|
|
225
161
|
if (missing.length > 0) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
162
|
+
const isFiltered = missing.length > Object.keys(byOpId).length / 2;
|
|
163
|
+
warnings.push(`${String(missing.length)} admin operation(s) declare a permission but are absent from the ` +
|
|
164
|
+
`document served by ${params.sourceUrl ?? "the instance's API document"} ` +
|
|
165
|
+
`(e.g. ${missing.slice(0, 3).join(', ')}). ` +
|
|
166
|
+
(isFiltered
|
|
167
|
+
? 'Most of the Admin API is missing, so that URL is serving the operation subset ' +
|
|
168
|
+
'whitelisted for the workflow "admin-api" node rather than the whole API. The ' +
|
|
169
|
+
'complete document is published by the instance itself at the base URL with ' +
|
|
170
|
+
'"-json" appended, but only while it runs with CORS_MODE=DEV. Until this ' +
|
|
171
|
+
'instance supports the "full" parameter on the node endpoint, expect the ' +
|
|
172
|
+
'catalog to cover only a fraction of the endpoints that exist.'
|
|
173
|
+
: 'The bundled permission map was generated from a different platform revision than ' +
|
|
174
|
+
'this instance runs — update the package if the catalog looks incomplete.'));
|
|
230
175
|
}
|
|
231
|
-
/**
|
|
232
|
-
* Право, объявленное контроллером, но отсутствующее в `AdminPermissionsEnum`,
|
|
233
|
-
* никогда не будет выдано администратору — такой эндпоинт отвечает 403 всегда.
|
|
234
|
-
* Это дефект dnk-back, и о нём должен знать и агент, и человек.
|
|
235
|
-
*/
|
|
236
176
|
const knownPermissions = new Set(permissions);
|
|
237
177
|
const orphanPermissions = [
|
|
238
178
|
...new Set(operations
|
|
@@ -240,9 +180,9 @@ export const buildCatalog = (params) => {
|
|
|
240
180
|
.filter((value) => value !== undefined && !knownPermissions.has(value))),
|
|
241
181
|
];
|
|
242
182
|
if (orphanPermissions.length > 0) {
|
|
243
|
-
warnings.push(`${orphanPermissions.length} permission(s) are required by
|
|
244
|
-
`
|
|
245
|
-
'
|
|
183
|
+
warnings.push(`${orphanPermissions.length} permission(s) are required by operations but cannot be held by ` +
|
|
184
|
+
`any admin (${orphanPermissions.join(', ')}). Those operations answer 403 permanently — ` +
|
|
185
|
+
'treat them as unavailable and report it rather than retrying.');
|
|
246
186
|
}
|
|
247
187
|
return {
|
|
248
188
|
version: 1,
|
|
@@ -254,4 +194,3 @@ export const buildCatalog = (params) => {
|
|
|
254
194
|
operations,
|
|
255
195
|
};
|
|
256
196
|
};
|
|
257
|
-
//# sourceMappingURL=build-catalog.js.map
|