@bevel-software/platform-core-backend 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +22 -0
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +3 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +10 -1
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/audit/agent-audit.service.d.ts +54 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -0
- package/dist/modules/audit/agent-audit.service.js +290 -0
- package/dist/modules/audit/agent-audit.service.js.map +1 -0
- package/dist/modules/audit/agent-rest-audit.middleware.d.ts +40 -0
- package/dist/modules/audit/agent-rest-audit.middleware.d.ts.map +1 -0
- package/dist/modules/audit/agent-rest-audit.middleware.js +75 -0
- package/dist/modules/audit/agent-rest-audit.middleware.js.map +1 -0
- package/dist/modules/audit/audit.contract.d.ts +145 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -0
- package/dist/modules/audit/audit.contract.js +35 -0
- package/dist/modules/audit/audit.contract.js.map +1 -0
- package/dist/modules/audit/audit.routes.d.ts +21 -0
- package/dist/modules/audit/audit.routes.d.ts.map +1 -0
- package/dist/modules/audit/audit.routes.js +157 -0
- package/dist/modules/audit/audit.routes.js.map +1 -0
- package/dist/modules/audit/event-classifier.d.ts +52 -0
- package/dist/modules/audit/event-classifier.d.ts.map +1 -0
- package/dist/modules/audit/event-classifier.js +123 -0
- package/dist/modules/audit/event-classifier.js.map +1 -0
- package/dist/modules/audit/index.d.ts +7 -0
- package/dist/modules/audit/index.d.ts.map +1 -0
- package/dist/modules/audit/index.js +7 -0
- package/dist/modules/audit/index.js.map +1 -0
- package/dist/modules/audit/request-audit.d.ts +57 -0
- package/dist/modules/audit/request-audit.d.ts.map +1 -0
- package/dist/modules/audit/request-audit.js +117 -0
- package/dist/modules/audit/request-audit.js.map +1 -0
- package/dist/modules/database/core-schema.d.ts +414 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +99 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +6 -1
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +9 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.js +16 -3
- package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +32 -0
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +8 -1
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +9 -1
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +48 -5
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +59 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +180 -8
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +10 -2
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +32 -6
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +21 -5
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +22 -9
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +10 -0
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +1 -0
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +8 -0
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +9 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +8 -0
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +9 -0
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +49 -13
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/git-internals.d.ts +7 -1
- package/dist/shared/git-internals.d.ts.map +1 -1
- package/dist/shared/git-internals.js +67 -9
- package/dist/shared/git-internals.js.map +1 -1
- package/migrations/0012_agent_audit.sql +41 -0
- package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -0
- package/migrations/meta/0012_snapshot.json +2247 -0
- package/migrations/meta/0013_snapshot.json +2259 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +4 -4
- package/src/core/create-core-server.ts +28 -0
- package/src/core/create-core-services.ts +13 -0
- package/src/modules/audit/__tests__/agent-audit.service.test.ts +370 -0
- package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -0
- package/src/modules/audit/__tests__/audit.routes.test.ts +201 -0
- package/src/modules/audit/__tests__/event-classifier.test.ts +121 -0
- package/src/modules/audit/agent-audit.service.ts +316 -0
- package/src/modules/audit/agent-rest-audit.middleware.ts +83 -0
- package/src/modules/audit/audit.contract.ts +164 -0
- package/src/modules/audit/audit.routes.ts +168 -0
- package/src/modules/audit/event-classifier.ts +155 -0
- package/src/modules/audit/index.ts +14 -0
- package/src/modules/audit/request-audit.ts +141 -0
- package/src/modules/database/core-schema.ts +104 -0
- package/src/modules/diff/diff.service.ts +6 -1
- package/src/modules/kb-fs/git-guarded-filesystem.ts +16 -3
- package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +183 -13
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -2
- package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +24 -4
- package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +1 -1
- package/src/modules/mcp/__tests__/mcp.service.test.ts +120 -3
- package/src/modules/mcp/mcp-auth.middleware.ts +31 -0
- package/src/modules/mcp/mcp.routes.ts +11 -1
- package/src/modules/mcp/mcp.service.ts +62 -4
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +184 -6
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +35 -0
- package/src/modules/settings/deployment-settings.service.ts +47 -8
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +29 -8
- package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +6 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +22 -5
- package/src/modules/tool-auth/external-api-key.service.ts +25 -8
- package/src/modules/tool-auth/internal-token.service.ts +11 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +17 -1
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +135 -44
- package/src/modules/workspace/workspace.service.ts +9 -0
- package/src/modules/workspace/workspace.tools.ts +47 -11
- package/src/shared/__tests__/git-internals.test.ts +41 -0
- package/src/shared/git-internals.ts +61 -6
- package/dist/modules/access/access-errors.d.ts +0 -34
- package/dist/modules/access/access-errors.d.ts.map +0 -1
- package/dist/modules/access/access-errors.js +0 -40
- package/dist/modules/access/access-errors.js.map +0 -1
- package/dist/modules/access/access-splice.d.ts +0 -140
- package/dist/modules/access/access-splice.d.ts.map +0 -1
- package/dist/modules/access/access-splice.js +0 -389
- package/dist/modules/access/access-splice.js.map +0 -1
- package/dist/modules/access/group-files.d.ts +0 -83
- package/dist/modules/access/group-files.d.ts.map +0 -1
- package/dist/modules/access/group-files.js +0 -167
- package/dist/modules/access/group-files.js.map +0 -1
- package/dist/modules/access/kb-read-filter.d.ts +0 -41
- package/dist/modules/access/kb-read-filter.d.ts.map +0 -1
- package/dist/modules/access/kb-read-filter.js +0 -60
- package/dist/modules/access/kb-read-filter.js.map +0 -1
- package/dist/modules/access/render-roles-yaml.d.ts +0 -22
- package/dist/modules/access/render-roles-yaml.d.ts.map +0 -1
- package/dist/modules/access/render-roles-yaml.js +0 -56
- package/dist/modules/access/render-roles-yaml.js.map +0 -1
- package/dist/modules/access/roles-yaml-guard.d.ts +0 -56
- package/dist/modules/access/roles-yaml-guard.d.ts.map +0 -1
- package/dist/modules/access/roles-yaml-guard.js +0 -79
- package/dist/modules/access/roles-yaml-guard.js.map +0 -1
- package/dist/modules/diff/diff-paths.d.ts +0 -2
- package/dist/modules/diff/diff-paths.d.ts.map +0 -1
- package/dist/modules/diff/diff-paths.js +0 -9
- package/dist/modules/diff/diff-paths.js.map +0 -1
- package/dist/modules/groups/group-provision.service.d.ts +0 -130
- package/dist/modules/groups/group-provision.service.d.ts.map +0 -1
- package/dist/modules/groups/group-provision.service.js +0 -288
- package/dist/modules/groups/group-provision.service.js.map +0 -1
- package/dist/modules/groups/groups.contract.d.ts +0 -106
- package/dist/modules/groups/groups.contract.d.ts.map +0 -1
- package/dist/modules/groups/groups.contract.js +0 -36
- package/dist/modules/groups/groups.contract.js.map +0 -1
- package/dist/modules/groups/groups.routes.d.ts +0 -42
- package/dist/modules/groups/groups.routes.d.ts.map +0 -1
- package/dist/modules/groups/groups.routes.js +0 -379
- package/dist/modules/groups/groups.routes.js.map +0 -1
- package/dist/modules/groups/groups.service.d.ts +0 -60
- package/dist/modules/groups/groups.service.d.ts.map +0 -1
- package/dist/modules/groups/groups.service.js +0 -172
- package/dist/modules/groups/groups.service.js.map +0 -1
- package/dist/modules/groups/index.d.ts +0 -7
- package/dist/modules/groups/index.d.ts.map +0 -1
- package/dist/modules/groups/index.js +0 -6
- package/dist/modules/groups/index.js.map +0 -1
- package/dist/modules/groups/join-proposals.d.ts +0 -53
- package/dist/modules/groups/join-proposals.d.ts.map +0 -1
- package/dist/modules/groups/join-proposals.js +0 -67
- package/dist/modules/groups/join-proposals.js.map +0 -1
- package/dist/modules/groups/join-requests.service.d.ts +0 -81
- package/dist/modules/groups/join-requests.service.d.ts.map +0 -1
- package/dist/modules/groups/join-requests.service.js +0 -135
- package/dist/modules/groups/join-requests.service.js.map +0 -1
- package/dist/modules/mcp/mcp-session-store.d.ts +0 -74
- package/dist/modules/mcp/mcp-session-store.d.ts.map +0 -1
- package/dist/modules/mcp/mcp-session-store.js +0 -131
- package/dist/modules/mcp/mcp-session-store.js.map +0 -1
- package/dist/modules/workflow/file-change-notifier.d.ts +0 -38
- package/dist/modules/workflow/file-change-notifier.d.ts.map +0 -1
- package/dist/modules/workflow/file-change-notifier.js +0 -22
- package/dist/modules/workflow/file-change-notifier.js.map +0 -1
- package/dist/modules/workflow/git/branch-name.d.ts +0 -10
- package/dist/modules/workflow/git/branch-name.d.ts.map +0 -1
- package/dist/modules/workflow/git/branch-name.js +0 -76
- package/dist/modules/workflow/git/branch-name.js.map +0 -1
- package/dist/modules/workflow/git/clone-config.d.ts +0 -61
- package/dist/modules/workflow/git/clone-config.d.ts.map +0 -1
- package/dist/modules/workflow/git/clone-config.js +0 -69
- package/dist/modules/workflow/git/clone-config.js.map +0 -1
- package/dist/modules/workflow/git/mutex.d.ts +0 -11
- package/dist/modules/workflow/git/mutex.d.ts.map +0 -1
- package/dist/modules/workflow/git/mutex.js +0 -23
- package/dist/modules/workflow/git/mutex.js.map +0 -1
- package/dist/modules/workflow/locking-filesystem.d.ts +0 -137
- package/dist/modules/workflow/locking-filesystem.d.ts.map +0 -1
- package/dist/modules/workflow/locking-filesystem.js +0 -553
- package/dist/modules/workflow/locking-filesystem.js.map +0 -1
- package/dist/modules/workflow/read-only-filesystem.d.ts +0 -24
- package/dist/modules/workflow/read-only-filesystem.d.ts.map +0 -1
- package/dist/modules/workflow/read-only-filesystem.js +0 -39
- package/dist/modules/workflow/read-only-filesystem.js.map +0 -1
- package/dist/modules/workflow/workflow.errors.d.ts +0 -197
- package/dist/modules/workflow/workflow.errors.d.ts.map +0 -1
- package/dist/modules/workflow/workflow.errors.js +0 -298
- package/dist/modules/workflow/workflow.errors.js.map +0 -1
- package/dist/modules/workspace/bevel-ignore.d.ts +0 -30
- package/dist/modules/workspace/bevel-ignore.d.ts.map +0 -1
- package/dist/modules/workspace/bevel-ignore.js +0 -61
- package/dist/modules/workspace/bevel-ignore.js.map +0 -1
- package/dist/modules/workspace/kb-seed.interface.d.ts +0 -36
- package/dist/modules/workspace/kb-seed.interface.d.ts.map +0 -1
- package/dist/modules/workspace/kb-seed.interface.js +0 -2
- package/dist/modules/workspace/kb-seed.interface.js.map +0 -1
- package/dist/modules/workspace/kb-seed.service.d.ts +0 -125
- package/dist/modules/workspace/kb-seed.service.d.ts.map +0 -1
- package/dist/modules/workspace/kb-seed.service.js +0 -535
- package/dist/modules/workspace/kb-seed.service.js.map +0 -1
- package/dist/modules/workspace/plugins-migration.d.ts +0 -50
- package/dist/modules/workspace/plugins-migration.d.ts.map +0 -1
- package/dist/modules/workspace/plugins-migration.js +0 -379
- package/dist/modules/workspace/plugins-migration.js.map +0 -1
- package/dist/shared/fs-errors.d.ts +0 -8
- package/dist/shared/fs-errors.d.ts.map +0 -1
- package/dist/shared/fs-errors.js +0 -11
- package/dist/shared/fs-errors.js.map +0 -1
- package/dist/shared/fs-walk.d.ts +0 -23
- package/dist/shared/fs-walk.d.ts.map +0 -1
- package/dist/shared/fs-walk.js +0 -42
- package/dist/shared/fs-walk.js.map +0 -1
- package/dist/shared/hash-email.d.ts +0 -11
- package/dist/shared/hash-email.d.ts.map +0 -1
- package/dist/shared/hash-email.js +0 -14
- package/dist/shared/hash-email.js.map +0 -1
- package/dist/shared/kb-walk.d.ts +0 -52
- package/dist/shared/kb-walk.d.ts.map +0 -1
- package/dist/shared/kb-walk.js +0 -76
- package/dist/shared/kb-walk.js.map +0 -1
|
@@ -107,7 +107,13 @@ async function resolvedRealPath(absolutePath: string): Promise<string | null> {
|
|
|
107
107
|
* Refuse a path under `rootDir` that names the git folder or resolves into it.
|
|
108
108
|
*
|
|
109
109
|
* `inputPath` is the caller's own spelling (checked lexically, encoded forms
|
|
110
|
-
* included
|
|
110
|
+
* included, and probed on disk every way it could land — see
|
|
111
|
+
* {@link candidateTargets}); `absolutePath`, when a layer has already
|
|
112
|
+
* decided where the path goes, is that place, judged AS WELL. Both in one
|
|
113
|
+
* call, because the two answer different questions — a link reached by a
|
|
114
|
+
* reading this layer does not use is caught only by the spelling, a link the
|
|
115
|
+
* layer's own resolution follows only by the place — and one call walks the
|
|
116
|
+
* root once and probes a place that both name once. Only the part below
|
|
111
117
|
* `rootDir` is judged, so a workspaces root that itself sits under some
|
|
112
118
|
* `.git` directory does not refuse everything. A link that leaves the root
|
|
113
119
|
* is judged on its target's path relative to the root, so a link into
|
|
@@ -116,9 +122,58 @@ async function resolvedRealPath(absolutePath: string): Promise<string | null> {
|
|
|
116
122
|
*/
|
|
117
123
|
export async function assertNotGitInternals(rootDir: string, inputPath: string, absolutePath?: string): Promise<void> {
|
|
118
124
|
assertNoGitInternalsSegment(inputPath);
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
const root = path.resolve(rootDir);
|
|
126
|
+
const realRoot = await resolvedRealPath(root);
|
|
127
|
+
// Each place once, remembering whether a layer resolved it (probed as
|
|
128
|
+
// given) or only a spelling named it (probed only inside the root).
|
|
129
|
+
const targets = new Map<string, boolean>();
|
|
130
|
+
for (const target of candidateTargets(root, inputPath)) targets.set(target, false);
|
|
131
|
+
if (absolutePath !== undefined) targets.set(absolutePath, true);
|
|
132
|
+
for (const [target, resolved] of targets) {
|
|
133
|
+
const relative = path.relative(root, target);
|
|
134
|
+
if (hasGitInternalsSegment(relative)) throw new GitInternalsError();
|
|
135
|
+
if (realRoot === null) continue;
|
|
136
|
+
// A raw spelling fans out into candidates, and one of them can name a
|
|
137
|
+
// place outside the workspace entirely — `/etc/…`, a deep climb. Such a
|
|
138
|
+
// candidate is judged by the lexical reading above (a link into another
|
|
139
|
+
// checkout's `.git` is named there) but NOT probed on disk: probing it
|
|
140
|
+
// would let an unreadable external directory answer with its own
|
|
141
|
+
// `EACCES` instead of the path rule's typed refusal for a spelling that
|
|
142
|
+
// was never a workspace path. A path a layer already resolved is the one
|
|
143
|
+
// place it is, so it is probed as given.
|
|
144
|
+
// `..` and `../…` are the climbs; `..link` is an ordinary name that merely
|
|
145
|
+
// starts with those characters, and a link by that name can point into the
|
|
146
|
+
// folder — so it is probed like any other entry under the root.
|
|
147
|
+
const climbsOut = relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
|
|
148
|
+
if (!resolved && (relative === '' || climbsOut)) continue;
|
|
149
|
+
const realTarget = await resolvedRealPath(target);
|
|
150
|
+
if (realTarget !== null && hasGitInternalsSegment(path.relative(realRoot, realTarget))) throw new GitInternalsError();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Every place one raw spelling could LAND, because a spelling is not yet a
|
|
156
|
+
* path until something decides how to read it.
|
|
157
|
+
*
|
|
158
|
+
* A backslash separates segments on Windows and is an ordinary character in a
|
|
159
|
+
* filename here, so `kb\\Notes\\..\\link` is read BOTH ways; an absolute path
|
|
160
|
+
* is both the path it names and — as every workspace caller reads it — that
|
|
161
|
+
* path with its leading slashes dropped, under the workspace; and a leading
|
|
162
|
+
* climb (`../kb/link`) is both the climb it spells and the path left when the
|
|
163
|
+
* climb is dropped, which is how a lenient reader takes it. The spelling is
|
|
164
|
+
* refused when ANY reading lands in the git folder: which reading a later
|
|
165
|
+
* layer picks is not something this rule should have to predict.
|
|
166
|
+
*
|
|
167
|
+
* This is what a CALLER's own spelling needs. A path some layer has already
|
|
168
|
+
* resolved arrives as `absolutePath` and is judged as the one place it is.
|
|
169
|
+
*/
|
|
170
|
+
function candidateTargets(root: string, inputPath: string): string[] {
|
|
171
|
+
const targets = new Set<string>();
|
|
172
|
+
for (const spelling of new Set([inputPath, inputPath.replace(/\\/g, '/')])) {
|
|
173
|
+
const anchored = spelling.replace(/^[\\/]+/, '');
|
|
174
|
+
targets.add(path.resolve(root, anchored));
|
|
175
|
+
targets.add(path.resolve(root, anchored.replace(/^(\.\.?[\\/])+/, '')));
|
|
176
|
+
if (/^[\\/]/.test(spelling)) targets.add(path.resolve(spelling));
|
|
177
|
+
}
|
|
178
|
+
return [...targets];
|
|
124
179
|
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { WorkflowDomainError } from '../workflow/workflow.errors.js';
|
|
2
|
-
export interface AccessDeniedDetails {
|
|
3
|
-
/** repo-relative POSIX path the caller tried to write */
|
|
4
|
-
path: string;
|
|
5
|
-
/** Display names of roles that would grant write at this path. */
|
|
6
|
-
eligibleRoles: string[];
|
|
7
|
-
/** Direct user grants at this path, in `{name, email}` form. */
|
|
8
|
-
eligibleUsers: {
|
|
9
|
-
name: string;
|
|
10
|
-
email: string;
|
|
11
|
-
}[];
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Thrown when a user lacks the `write` permission on a path under the
|
|
15
|
-
* access-control rules in `roles.yaml` + `access.md`.
|
|
16
|
-
*
|
|
17
|
-
* Carries enough detail in the JSON payload for the frontend to render
|
|
18
|
-
* "you don't have permission to write to <path>; eligible: <roles + users>"
|
|
19
|
-
* without a follow-up roundtrip.
|
|
20
|
-
*/
|
|
21
|
-
export declare class AccessDeniedError extends WorkflowDomainError {
|
|
22
|
-
readonly access: AccessDeniedDetails;
|
|
23
|
-
constructor(details: AccessDeniedDetails);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Thrown when the access-control config (roles.yaml or access.md) is missing
|
|
27
|
-
* or malformed at runtime — distinct from AccessDeniedError because the cause
|
|
28
|
-
* is a config bug, not a permission decision.
|
|
29
|
-
*/
|
|
30
|
-
export declare class AccessConfigError extends WorkflowDomainError {
|
|
31
|
-
readonly errors: string[];
|
|
32
|
-
constructor(errors: string[]);
|
|
33
|
-
}
|
|
34
|
-
//# sourceMappingURL=access-errors.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"access-errors.d.ts","sourceRoot":"","sources":["../../../src/modules/access/access-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gEAAgE;IAChE,aAAa,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAClD;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,mBAAmB;IACxD,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;gBAEzB,OAAO,EAAE,mBAAmB;CAkBzC;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,mBAAmB;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAEd,MAAM,EAAE,MAAM,EAAE;CAS7B"}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { WorkflowDomainError } from '../workflow/workflow.errors.js';
|
|
2
|
-
/**
|
|
3
|
-
* Thrown when a user lacks the `write` permission on a path under the
|
|
4
|
-
* access-control rules in `roles.yaml` + `access.md`.
|
|
5
|
-
*
|
|
6
|
-
* Carries enough detail in the JSON payload for the frontend to render
|
|
7
|
-
* "you don't have permission to write to <path>; eligible: <roles + users>"
|
|
8
|
-
* without a follow-up roundtrip.
|
|
9
|
-
*/
|
|
10
|
-
export class AccessDeniedError extends WorkflowDomainError {
|
|
11
|
-
access;
|
|
12
|
-
constructor(details) {
|
|
13
|
-
const rolesPart = details.eligibleRoles.length
|
|
14
|
-
? details.eligibleRoles.join(', ')
|
|
15
|
-
: 'none';
|
|
16
|
-
const usersPart = details.eligibleUsers.length
|
|
17
|
-
? details.eligibleUsers
|
|
18
|
-
.map((u) => (u.name ? `${u.name} <${u.email}>` : u.email))
|
|
19
|
-
.join(', ')
|
|
20
|
-
: '';
|
|
21
|
-
const eligible = [rolesPart, usersPart].filter(Boolean).join('; ');
|
|
22
|
-
super(`You don't have permission to write to "${details.path}". Eligible: ${eligible}.`, 403, { access: details });
|
|
23
|
-
this.name = 'AccessDeniedError';
|
|
24
|
-
this.access = details;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Thrown when the access-control config (roles.yaml or access.md) is missing
|
|
29
|
-
* or malformed at runtime — distinct from AccessDeniedError because the cause
|
|
30
|
-
* is a config bug, not a permission decision.
|
|
31
|
-
*/
|
|
32
|
-
export class AccessConfigError extends WorkflowDomainError {
|
|
33
|
-
errors;
|
|
34
|
-
constructor(errors) {
|
|
35
|
-
super(`Access-control config is invalid: ${errors.join('; ')}`, 500, { accessConfigErrors: errors });
|
|
36
|
-
this.name = 'AccessConfigError';
|
|
37
|
-
this.errors = errors;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
//# sourceMappingURL=access-errors.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"access-errors.js","sourceRoot":"","sources":["../../../src/modules/access/access-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAWrE;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAkB,SAAQ,mBAAmB;IAC/C,MAAM,CAAsB;IAErC,YAAY,OAA4B;QACtC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM;YAC5C,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;YAClC,CAAC,CAAC,MAAM,CAAC;QACX,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM;YAC5C,CAAC,CAAC,OAAO,CAAC,aAAa;iBAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;iBACzD,IAAI,CAAC,IAAI,CAAC;YACf,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,QAAQ,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,KAAK,CACH,0CAA0C,OAAO,CAAC,IAAI,gBAAgB,QAAQ,GAAG,EACjF,GAAG,EACH,EAAE,MAAM,EAAE,OAAO,EAAE,CACpB,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;IACxB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,iBAAkB,SAAQ,mBAAmB;IAC/C,MAAM,CAAW;IAE1B,YAAY,MAAgB;QAC1B,KAAK,CACH,qCAAqC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EACxD,GAAG,EACH,EAAE,kBAAkB,EAAE,MAAM,EAAE,CAC/B,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Surgical text-splice editor for `access.md` frontmatter and node-frontmatter
|
|
3
|
-
* access verbs.
|
|
4
|
-
*
|
|
5
|
-
* WHY a splice and not parse→model→emit: the resolver's parser
|
|
6
|
-
* (`parseYamlSubset` / `extractFrontmatter` / `stripComment`) deliberately
|
|
7
|
-
* throws away comments, blank lines, key order, AND the markdown body below the
|
|
8
|
-
* closing `---`. Re-serialising from the parsed model would silently DELETE the
|
|
9
|
-
* prose bodies the real `access.md` files carry (the repo-root one is a whole
|
|
10
|
-
* README) — and a `parse(emit(x))` round-trip test would PASS anyway because
|
|
11
|
-
* parse ignores the body. So every mutation here edits the RAW text: we locate
|
|
12
|
-
* the verb's block, splice exactly one list line in or out, and leave every
|
|
13
|
-
* other byte — comments, ordering, blank lines, the body — untouched.
|
|
14
|
-
*
|
|
15
|
-
* ---
|
|
16
|
-
* write: (verb key line, indent 0)
|
|
17
|
-
* - Admin (list item, indent 2) <- splice point
|
|
18
|
-
* - Felix <f@x.eu> (list item)
|
|
19
|
-
* # a comment (preserved verbatim)
|
|
20
|
-
* owner: [] (inline-empty list)
|
|
21
|
-
* ---
|
|
22
|
-
* # Body prose (preserved verbatim)
|
|
23
|
-
*
|
|
24
|
-
* The same engine serves node-frontmatter, which additionally accepts a scalar
|
|
25
|
-
* form (`owner: Felix <f@x.eu>`). Adding a second principal to a scalar verb
|
|
26
|
-
* normalises it to the block-list form.
|
|
27
|
-
*/
|
|
28
|
-
import { type Verb } from './access-control.service.js';
|
|
29
|
-
/**
|
|
30
|
-
* Which rule block a mutation edits.
|
|
31
|
-
*
|
|
32
|
-
* - `'node'` (default): the file's YAML frontmatter — node files, and every
|
|
33
|
-
* call that predates the two-format `access.md` story.
|
|
34
|
-
* - `'folder'`: the block that governs the CONTAINING FOLDER of an
|
|
35
|
-
* `access.md` — the body when the file is in the new (body-governed)
|
|
36
|
-
* format, the frontmatter otherwise. Callers mutating folder rules pass
|
|
37
|
-
* this so a grant can never land in a new-format file's frontmatter
|
|
38
|
-
* (which governs the FILE itself, not the folder).
|
|
39
|
-
*/
|
|
40
|
-
export type SpliceTarget = 'node' | 'folder';
|
|
41
|
-
/** A principal to grant/revoke, in the resolver's canonical terms. */
|
|
42
|
-
export type Principal = {
|
|
43
|
-
kind: 'user';
|
|
44
|
-
email: string;
|
|
45
|
-
displayName: string;
|
|
46
|
-
} | {
|
|
47
|
-
kind: 'role';
|
|
48
|
-
role: string;
|
|
49
|
-
};
|
|
50
|
-
/** Result of a splice — the new full file text and whether anything changed. */
|
|
51
|
-
export interface SpliceResult {
|
|
52
|
-
text: string;
|
|
53
|
-
changed: boolean;
|
|
54
|
-
}
|
|
55
|
-
/** Thrown when the input can't be spliced safely (malformed frontmatter, etc.). */
|
|
56
|
-
export declare class AccessSpliceError extends Error {
|
|
57
|
-
constructor(message: string);
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Render a principal as the canonical access-entry line value the parser reads
|
|
61
|
-
* back (`Name <email>` or a bare role display name). The caller is responsible
|
|
62
|
-
* for having validated the principal first (see `validatePrincipal`).
|
|
63
|
-
*
|
|
64
|
-
* `deny` prefixes the entry with `deny ` so the resolver reads it as a denial
|
|
65
|
-
* (the `deny-here` per-item override) — same line shape, opposite verdict.
|
|
66
|
-
*/
|
|
67
|
-
export declare function renderEntry(p: Principal, deny?: boolean): string;
|
|
68
|
-
/**
|
|
69
|
-
* Validate a principal before it is spliced into a file. Rejects anything that
|
|
70
|
-
* could break frontmatter or smuggle in extra YAML — newlines, control chars,
|
|
71
|
-
* and emails / names that don't match the resolver's own regexes. This is the
|
|
72
|
-
* injection guard: a principal that survives this is safe to render verbatim
|
|
73
|
-
* into a ` - ` list line.
|
|
74
|
-
*
|
|
75
|
-
* Returns the canonical principal (lowercased email / canonical role) on
|
|
76
|
-
* success, or throws `AccessSpliceError`.
|
|
77
|
-
*/
|
|
78
|
-
export declare function validatePrincipal(p: Principal): Principal;
|
|
79
|
-
/**
|
|
80
|
-
* How a role-shaped principal's TOKEN is compared against a file entry:
|
|
81
|
-
*
|
|
82
|
-
* - `'exact'`: the entry must carry the SAME spelling — a bare token and a
|
|
83
|
-
* `role/<name>` token are DIFFERENT principals (bare = whoever owns the
|
|
84
|
-
* bare key under group-first precedence, `role/` = the role). Grants
|
|
85
|
-
* always use this: granting the group `X` while `role/X` is present (or
|
|
86
|
-
* vice versa) must NOT be treated as already-granted, or the other
|
|
87
|
-
* principal silently never receives the grant.
|
|
88
|
-
* - `'name'`: both spellings of the name match. Revokes use this ONLY when
|
|
89
|
-
* no group shadows the bare name — then both spellings resolve to the
|
|
90
|
-
* role, and revoking the role must also remove legacy bare spellings.
|
|
91
|
-
*/
|
|
92
|
-
export type TokenMatch = 'exact' | 'name';
|
|
93
|
-
/**
|
|
94
|
-
* Add `principal` under `verb`. Idempotent: if an entry with the EXACT same
|
|
95
|
-
* token spelling already exists with the SAME deny-ness (a grant when
|
|
96
|
-
* granting, a deny when denying), returns `changed: false`. Idempotency is
|
|
97
|
-
* deliberately exact-token, never name-level: a bare token and a `role/`
|
|
98
|
-
* token are different principals (group-first precedence gives the bare key
|
|
99
|
-
* to a same-named group), so a bare-`X` GROUP grant must go through even when
|
|
100
|
-
* `role/X` is present, and vice versa. Creates the verb key (and the
|
|
101
|
-
* frontmatter fence, for a fresh file) as needed. Preserves everything else
|
|
102
|
-
* byte-for-byte.
|
|
103
|
-
*
|
|
104
|
-
* `allowScalar` controls whether a fresh single-entry verb may be written in
|
|
105
|
-
* the node-frontmatter scalar form (`owner: Name <email>`). access.md always
|
|
106
|
-
* uses the block-list form; node frontmatter prefers scalar for a lone entry.
|
|
107
|
-
*
|
|
108
|
-
* `deny` writes the entry as a `deny <principal>` denial instead of a grant
|
|
109
|
-
* (the `deny-here` per-item override). The block/scalar/empty-list handling is
|
|
110
|
-
* identical — only the rendered line and the idempotency check differ — so the
|
|
111
|
-
* deny path reuses this engine rather than a parallel `spliceDeny`. NOTE: the
|
|
112
|
-
* resolver lets a same-scope grant override a same-scope deny of the same
|
|
113
|
-
* principal, so a caller adding a deny where a grant already exists must strip
|
|
114
|
-
* that grant first (see `AccessMutationService`); this function only writes the
|
|
115
|
-
* one entry it's asked to.
|
|
116
|
-
*/
|
|
117
|
-
export declare function spliceGrant(text: string, verb: Verb, rawPrincipal: Principal, opts?: {
|
|
118
|
-
allowScalar?: boolean;
|
|
119
|
-
deny?: boolean;
|
|
120
|
-
target?: SpliceTarget;
|
|
121
|
-
}): SpliceResult;
|
|
122
|
-
/**
|
|
123
|
-
* Remove every entry naming `principal` under `verb` (grant or deny). Idempotent:
|
|
124
|
-
* returns `changed: false` when the principal isn't present. If removing the
|
|
125
|
-
* last item empties the verb, the `verb:` key is collapsed to `verb: []` so the
|
|
126
|
-
* file still parses cleanly (the resolver reads `[]` as an empty list).
|
|
127
|
-
* Preserves everything else.
|
|
128
|
-
*
|
|
129
|
-
* Token matching for role-shaped principals is `tokenMatch` (default `'name'`:
|
|
130
|
-
* BOTH spellings — revoking the role removes legacy bare spellings too).
|
|
131
|
-
* Callers pass `'exact'` when a GROUP shadows the principal's bare name: the
|
|
132
|
-
* bare token then belongs to the group and the `role/` token to the role, so a
|
|
133
|
-
* revoke of one must never strip the other (see AccessMutationService, which
|
|
134
|
-
* derives the flag from the merged principal index).
|
|
135
|
-
*/
|
|
136
|
-
export declare function spliceRevoke(text: string, verb: Verb, rawPrincipal: Principal, opts?: {
|
|
137
|
-
target?: SpliceTarget;
|
|
138
|
-
tokenMatch?: TokenMatch;
|
|
139
|
-
}): SpliceResult;
|
|
140
|
-
//# sourceMappingURL=access-splice.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"access-splice.d.ts","sourceRoot":"","sources":["../../../src/modules/access/access-splice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EACL,KAAK,IAAI,EAMV,MAAM,6BAA6B,CAAC;AAErC;;;;;;;;;;GAUG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE7C,sEAAsE;AACtE,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,mFAAmF;AACnF,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAMD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,UAAQ,GAAG,MAAM,CAG9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,SAAS,GAAG,SAAS,CAgDzD;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AA+L1C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,SAAS,EACvB,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,CAAA;CAAO,GAC1E,YAAY,CA0Dd;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,SAAS,EACvB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,YAAY,CAAC;IAAC,UAAU,CAAC,EAAE,UAAU,CAAA;CAAO,GAC5D,YAAY,CAkCd"}
|