@braidhq/server 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -0
- package/dist/app.d.ts +19 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +121 -3
- package/dist/app.js.map +1 -1
- package/dist/composeFs.d.ts +41 -6
- package/dist/composeFs.d.ts.map +1 -1
- package/dist/composeFs.js +234 -52
- package/dist/composeFs.js.map +1 -1
- package/dist/composition.d.ts +86 -2
- package/dist/composition.d.ts.map +1 -1
- package/dist/composition.js +71 -1
- package/dist/composition.js.map +1 -1
- package/dist/infrastructure/agent/SubprocessSkillRunner.d.ts +21 -0
- package/dist/infrastructure/agent/SubprocessSkillRunner.d.ts.map +1 -1
- package/dist/infrastructure/agent/SubprocessSkillRunner.js +35 -3
- package/dist/infrastructure/agent/SubprocessSkillRunner.js.map +1 -1
- package/dist/infrastructure/agent/mcpConfig.d.ts +25 -5
- package/dist/infrastructure/agent/mcpConfig.d.ts.map +1 -1
- package/dist/infrastructure/agent/mcpConfig.js +22 -14
- package/dist/infrastructure/agent/mcpConfig.js.map +1 -1
- package/dist/infrastructure/auth/AccessPolicy.d.ts +112 -0
- package/dist/infrastructure/auth/AccessPolicy.d.ts.map +1 -0
- package/dist/infrastructure/auth/AccessPolicy.js +184 -0
- package/dist/infrastructure/auth/AccessPolicy.js.map +1 -0
- package/dist/infrastructure/auth/SessionStore.d.ts +40 -0
- package/dist/infrastructure/auth/SessionStore.d.ts.map +1 -0
- package/dist/infrastructure/auth/SessionStore.js +107 -0
- package/dist/infrastructure/auth/SessionStore.js.map +1 -0
- package/dist/infrastructure/auth/ensureApprovedEmails.d.ts +16 -0
- package/dist/infrastructure/auth/ensureApprovedEmails.d.ts.map +1 -0
- package/dist/infrastructure/auth/ensureApprovedEmails.js +20 -0
- package/dist/infrastructure/auth/ensureApprovedEmails.js.map +1 -0
- package/dist/infrastructure/env.d.ts +11 -0
- package/dist/infrastructure/env.d.ts.map +1 -0
- package/dist/infrastructure/env.js +20 -0
- package/dist/infrastructure/env.js.map +1 -0
- package/dist/infrastructure/fs/FsBatchPlanRepository.d.ts +300 -0
- package/dist/infrastructure/fs/FsBatchPlanRepository.d.ts.map +1 -0
- package/dist/infrastructure/fs/FsBatchPlanRepository.js +42 -0
- package/dist/infrastructure/fs/FsBatchPlanRepository.js.map +1 -0
- package/dist/infrastructure/fs/FsClarifyTicketRepository.d.ts.map +1 -1
- package/dist/infrastructure/fs/FsClarifyTicketRepository.js +7 -1
- package/dist/infrastructure/fs/FsClarifyTicketRepository.js.map +1 -1
- package/dist/infrastructure/fs/FsGraphSerializer.d.ts +629 -0
- package/dist/infrastructure/fs/FsGraphSerializer.d.ts.map +1 -0
- package/dist/infrastructure/fs/FsGraphSerializer.js +60 -0
- package/dist/infrastructure/fs/FsGraphSerializer.js.map +1 -0
- package/dist/infrastructure/fs/FsProposalRepository.d.ts.map +1 -1
- package/dist/infrastructure/fs/FsProposalRepository.js +8 -0
- package/dist/infrastructure/fs/FsProposalRepository.js.map +1 -1
- package/dist/infrastructure/fs/FsRunRepository.d.ts +4 -1
- package/dist/infrastructure/fs/FsRunRepository.d.ts.map +1 -1
- package/dist/infrastructure/fs/FsRunRepository.js +75 -3
- package/dist/infrastructure/fs/FsRunRepository.js.map +1 -1
- package/dist/infrastructure/fs/FsSkillRegistry.d.ts.map +1 -1
- package/dist/infrastructure/fs/FsSkillRegistry.js +15 -3
- package/dist/infrastructure/fs/FsSkillRegistry.js.map +1 -1
- package/dist/infrastructure/fs/FsSourceUnitDigest.d.ts +19 -0
- package/dist/infrastructure/fs/FsSourceUnitDigest.d.ts.map +1 -0
- package/dist/infrastructure/fs/FsSourceUnitDigest.js +58 -0
- package/dist/infrastructure/fs/FsSourceUnitDigest.js.map +1 -0
- package/dist/infrastructure/fs/FsSourceUnitStateRepository.d.ts +30 -0
- package/dist/infrastructure/fs/FsSourceUnitStateRepository.d.ts.map +1 -0
- package/dist/infrastructure/fs/FsSourceUnitStateRepository.js +87 -0
- package/dist/infrastructure/fs/FsSourceUnitStateRepository.js.map +1 -0
- package/dist/infrastructure/fs/FsWorkspaceRepository.d.ts.map +1 -1
- package/dist/infrastructure/fs/FsWorkspaceRepository.js +12 -3
- package/dist/infrastructure/fs/FsWorkspaceRepository.js.map +1 -1
- package/dist/infrastructure/fs/WorkspaceDiscovery.d.ts +15 -0
- package/dist/infrastructure/fs/WorkspaceDiscovery.d.ts.map +1 -0
- package/dist/infrastructure/fs/WorkspaceDiscovery.js +46 -0
- package/dist/infrastructure/fs/WorkspaceDiscovery.js.map +1 -0
- package/dist/infrastructure/fs/WorkspaceRegistryFile.d.ts +26 -2
- package/dist/infrastructure/fs/WorkspaceRegistryFile.d.ts.map +1 -1
- package/dist/infrastructure/fs/WorkspaceRegistryFile.js +109 -5
- package/dist/infrastructure/fs/WorkspaceRegistryFile.js.map +1 -1
- package/dist/infrastructure/fs/intentScan.d.ts +9 -0
- package/dist/infrastructure/fs/intentScan.d.ts.map +1 -0
- package/dist/infrastructure/fs/intentScan.js +135 -0
- package/dist/infrastructure/fs/intentScan.js.map +1 -0
- package/dist/infrastructure/fs/paths.d.ts +14 -0
- package/dist/infrastructure/fs/paths.d.ts.map +1 -1
- package/dist/infrastructure/fs/paths.js +43 -0
- package/dist/infrastructure/fs/paths.js.map +1 -1
- package/dist/infrastructure/fs/productManifestWriter.d.ts +1 -1
- package/dist/infrastructure/fs/productManifestWriter.d.ts.map +1 -1
- package/dist/infrastructure/fs/productManifestWriter.js +1 -3
- package/dist/infrastructure/fs/productManifestWriter.js.map +1 -1
- package/dist/infrastructure/git/GitWorkspaceHistory.d.ts +18 -0
- package/dist/infrastructure/git/GitWorkspaceHistory.d.ts.map +1 -0
- package/dist/infrastructure/git/GitWorkspaceHistory.js +303 -0
- package/dist/infrastructure/git/GitWorkspaceHistory.js.map +1 -0
- package/dist/infrastructure/git/commitMessage.d.ts +4 -0
- package/dist/infrastructure/git/commitMessage.d.ts.map +1 -0
- package/dist/infrastructure/git/commitMessage.js +71 -0
- package/dist/infrastructure/git/commitMessage.js.map +1 -0
- package/dist/infrastructure/oauth/GoogleOAuth.d.ts +48 -1
- package/dist/infrastructure/oauth/GoogleOAuth.d.ts.map +1 -1
- package/dist/infrastructure/oauth/GoogleOAuth.js +68 -0
- package/dist/infrastructure/oauth/GoogleOAuth.js.map +1 -1
- package/dist/infrastructure/users/UserDirectoryFromRegistry.d.ts +14 -0
- package/dist/infrastructure/users/UserDirectoryFromRegistry.d.ts.map +1 -0
- package/dist/infrastructure/users/UserDirectoryFromRegistry.js +21 -0
- package/dist/infrastructure/users/UserDirectoryFromRegistry.js.map +1 -0
- package/dist/infrastructure/users/UserRegistryFile.d.ts +23 -0
- package/dist/infrastructure/users/UserRegistryFile.d.ts.map +1 -0
- package/dist/infrastructure/users/UserRegistryFile.js +91 -0
- package/dist/infrastructure/users/UserRegistryFile.js.map +1 -0
- package/dist/infrastructure/users/ensureLocalUser.d.ts +15 -0
- package/dist/infrastructure/users/ensureLocalUser.d.ts.map +1 -0
- package/dist/infrastructure/users/ensureLocalUser.js +38 -0
- package/dist/infrastructure/users/ensureLocalUser.js.map +1 -0
- package/dist/infrastructure/users/ensureWorkspaceOwners.d.ts +9 -0
- package/dist/infrastructure/users/ensureWorkspaceOwners.d.ts.map +1 -0
- package/dist/infrastructure/users/ensureWorkspaceOwners.js +22 -0
- package/dist/infrastructure/users/ensureWorkspaceOwners.js.map +1 -0
- package/dist/middleware/auth.d.ts +29 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +81 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.js +1 -1
- package/dist/middleware/cors.js.map +1 -1
- package/dist/middleware/requireAdmin.d.ts +10 -0
- package/dist/middleware/requireAdmin.d.ts.map +1 -0
- package/dist/middleware/requireAdmin.js +24 -0
- package/dist/middleware/requireAdmin.js.map +1 -0
- package/dist/middleware/userId.d.ts +16 -0
- package/dist/middleware/userId.d.ts.map +1 -0
- package/dist/middleware/userId.js +27 -0
- package/dist/middleware/userId.js.map +1 -0
- package/dist/middleware/workspaceAccess.d.ts +47 -0
- package/dist/middleware/workspaceAccess.d.ts.map +1 -0
- package/dist/middleware/workspaceAccess.js +86 -0
- package/dist/middleware/workspaceAccess.js.map +1 -0
- package/dist/policy/Capability.d.ts +12 -0
- package/dist/policy/Capability.d.ts.map +1 -0
- package/dist/policy/Capability.js +31 -0
- package/dist/policy/Capability.js.map +1 -0
- package/dist/policy/CapabilityCheck.d.ts +14 -0
- package/dist/policy/CapabilityCheck.d.ts.map +1 -0
- package/dist/policy/CapabilityCheck.js +2 -0
- package/dist/policy/CapabilityCheck.js.map +1 -0
- package/dist/policy/PermissionRegistry.d.ts +23 -0
- package/dist/policy/PermissionRegistry.d.ts.map +1 -0
- package/dist/policy/PermissionRegistry.js +27 -0
- package/dist/policy/PermissionRegistry.js.map +1 -0
- package/dist/policy/ViewerContext.d.ts +27 -0
- package/dist/policy/ViewerContext.d.ts.map +1 -0
- package/dist/policy/ViewerContext.js +2 -0
- package/dist/policy/ViewerContext.js.map +1 -0
- package/dist/policy/checks.d.ts +15 -0
- package/dist/policy/checks.d.ts.map +1 -0
- package/dist/policy/checks.js +52 -0
- package/dist/policy/checks.js.map +1 -0
- package/dist/policy/defaultRegistry.d.ts +10 -0
- package/dist/policy/defaultRegistry.d.ts.map +1 -0
- package/dist/policy/defaultRegistry.js +16 -0
- package/dist/policy/defaultRegistry.js.map +1 -0
- package/dist/policy/index.d.ts +8 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +5 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/resolveViewer.d.ts +16 -0
- package/dist/policy/resolveViewer.d.ts.map +1 -0
- package/dist/policy/resolveViewer.js +37 -0
- package/dist/policy/resolveViewer.js.map +1 -0
- package/dist/routes/_shared.d.ts +198 -0
- package/dist/routes/_shared.d.ts.map +1 -0
- package/dist/routes/_shared.js +29 -0
- package/dist/routes/_shared.js.map +1 -0
- package/dist/routes/admin.d.ts +13 -0
- package/dist/routes/admin.d.ts.map +1 -0
- package/dist/routes/admin.js +227 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/auth.d.ts +33 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +170 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/batch.d.ts +7 -0
- package/dist/routes/batch.d.ts.map +1 -0
- package/dist/routes/batch.js +47 -0
- package/dist/routes/batch.js.map +1 -0
- package/dist/routes/clarify.d.ts +4 -3
- package/dist/routes/clarify.d.ts.map +1 -1
- package/dist/routes/clarify.js +247 -57
- package/dist/routes/clarify.js.map +1 -1
- package/dist/routes/decisions.d.ts +2 -2
- package/dist/routes/decisions.d.ts.map +1 -1
- package/dist/routes/decisions.js +50 -12
- package/dist/routes/decisions.js.map +1 -1
- package/dist/routes/edges.d.ts +2 -2
- package/dist/routes/edges.d.ts.map +1 -1
- package/dist/routes/edges.js +35 -13
- package/dist/routes/edges.js.map +1 -1
- package/dist/routes/health.d.ts +2 -2
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +21 -3
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/history.d.ts +7 -0
- package/dist/routes/history.d.ts.map +1 -0
- package/dist/routes/history.js +88 -0
- package/dist/routes/history.js.map +1 -0
- package/dist/routes/model.d.ts +2 -2
- package/dist/routes/model.d.ts.map +1 -1
- package/dist/routes/model.js +20 -4
- package/dist/routes/model.js.map +1 -1
- package/dist/routes/nodes.d.ts +2 -2
- package/dist/routes/nodes.d.ts.map +1 -1
- package/dist/routes/nodes.js +75 -19
- package/dist/routes/nodes.js.map +1 -1
- package/dist/routes/ontology.d.ts +2 -2
- package/dist/routes/ontology.d.ts.map +1 -1
- package/dist/routes/ontology.js +22 -5
- package/dist/routes/ontology.js.map +1 -1
- package/dist/routes/proposals.d.ts +4 -3
- package/dist/routes/proposals.d.ts.map +1 -1
- package/dist/routes/proposals.js +170 -41
- package/dist/routes/proposals.js.map +1 -1
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +52 -1
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/skillInputOptions.d.ts +10 -0
- package/dist/routes/skillInputOptions.d.ts.map +1 -0
- package/dist/routes/skillInputOptions.js +161 -0
- package/dist/routes/skillInputOptions.js.map +1 -0
- package/dist/routes/skills.d.ts +2 -2
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/skills.js +89 -19
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/sourceLoaders.d.ts +7 -0
- package/dist/routes/sourceLoaders.d.ts.map +1 -0
- package/dist/routes/sourceLoaders.js +29 -0
- package/dist/routes/sourceLoaders.js.map +1 -0
- package/dist/routes/sourceUnitStates.d.ts +7 -0
- package/dist/routes/sourceUnitStates.d.ts.map +1 -0
- package/dist/routes/sourceUnitStates.js +45 -0
- package/dist/routes/sourceUnitStates.js.map +1 -0
- package/dist/routes/users.d.ts +9 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +127 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/routes/workspaceMembers.d.ts +11 -0
- package/dist/routes/workspaceMembers.d.ts.map +1 -0
- package/dist/routes/workspaceMembers.js +81 -0
- package/dist/routes/workspaceMembers.js.map +1 -0
- package/dist/routes/workspaces.d.ts +19 -1
- package/dist/routes/workspaces.d.ts.map +1 -1
- package/dist/routes/workspaces.js +161 -45
- package/dist/routes/workspaces.js.map +1 -1
- package/dist/server.js +69 -25
- package/dist/server.js.map +1 -1
- package/package.json +48 -31
- package/LICENSE +0 -21
- package/dist/infrastructure/agent/ClaudeCodeAgentBinding.d.ts +0 -8
- package/dist/infrastructure/agent/ClaudeCodeAgentBinding.d.ts.map +0 -1
- package/dist/infrastructure/agent/ClaudeCodeAgentBinding.js +0 -62
- package/dist/infrastructure/agent/ClaudeCodeAgentBinding.js.map +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { UserId as UserIdType, UserPatch as UserPatchType, User as UserType } from '@braidhq/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Persists the user registry to a JSON file at `${BRAID_HOME}/users.json`.
|
|
4
|
+
* Source of truth across server restarts; in-memory data is a derivation.
|
|
5
|
+
*
|
|
6
|
+
* Auth and ACL are server-side concerns and never enter a workspace's
|
|
7
|
+
* git history — keep this file under `~/.braid/`, not inside any
|
|
8
|
+
* workspace directory.
|
|
9
|
+
*/
|
|
10
|
+
export declare class UserRegistryFile {
|
|
11
|
+
private readonly filePath;
|
|
12
|
+
constructor(filePath: string);
|
|
13
|
+
list(): Promise<UserType[]>;
|
|
14
|
+
get(id: UserIdType): Promise<UserType | undefined>;
|
|
15
|
+
getByGoogleSub(sub: string): Promise<UserType | undefined>;
|
|
16
|
+
getByEmail(email: string): Promise<UserType | undefined>;
|
|
17
|
+
create(user: UserType): Promise<UserType>;
|
|
18
|
+
update(id: UserIdType, patch: UserPatchType): Promise<UserType>;
|
|
19
|
+
delete(id: UserIdType): Promise<void>;
|
|
20
|
+
private read;
|
|
21
|
+
private write;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=UserRegistryFile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserRegistryFile.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/users/UserRegistryFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,SAAS,IAAI,aAAa,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAazG;;;;;;;GAOG;AACH,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAEvC,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAK3B,GAAG,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAKlD,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAK1D,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAMxD,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAWzC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC;IAmB/D,MAAM,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;YAQ7B,IAAI;YAeJ,KAAK;CAIpB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { NotFoundError, ValidationError } from '@braidhq/core';
|
|
4
|
+
import { User } from '@braidhq/schema';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
const RegistryContent = z.object({
|
|
7
|
+
users: z.array(User).default([]),
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Persists the user registry to a JSON file at `${BRAID_HOME}/users.json`.
|
|
11
|
+
* Source of truth across server restarts; in-memory data is a derivation.
|
|
12
|
+
*
|
|
13
|
+
* Auth and ACL are server-side concerns and never enter a workspace's
|
|
14
|
+
* git history — keep this file under `~/.braid/`, not inside any
|
|
15
|
+
* workspace directory.
|
|
16
|
+
*/
|
|
17
|
+
export class UserRegistryFile {
|
|
18
|
+
filePath;
|
|
19
|
+
constructor(filePath) {
|
|
20
|
+
this.filePath = filePath;
|
|
21
|
+
}
|
|
22
|
+
async list() {
|
|
23
|
+
const content = await this.read();
|
|
24
|
+
return content.users;
|
|
25
|
+
}
|
|
26
|
+
async get(id) {
|
|
27
|
+
const content = await this.read();
|
|
28
|
+
return content.users.find(u => u.id === id);
|
|
29
|
+
}
|
|
30
|
+
async getByGoogleSub(sub) {
|
|
31
|
+
const content = await this.read();
|
|
32
|
+
return content.users.find(u => u.googleSub === sub);
|
|
33
|
+
}
|
|
34
|
+
async getByEmail(email) {
|
|
35
|
+
const content = await this.read();
|
|
36
|
+
const lowered = email.toLowerCase();
|
|
37
|
+
return content.users.find(u => u.email?.toLowerCase() === lowered);
|
|
38
|
+
}
|
|
39
|
+
async create(user) {
|
|
40
|
+
const content = await this.read();
|
|
41
|
+
if (content.users.some(existing => existing.id === user.id))
|
|
42
|
+
throw new ValidationError(`User "${user.id}" already exists`);
|
|
43
|
+
if (user.googleSub && content.users.some(existing => existing.googleSub === user.googleSub))
|
|
44
|
+
throw new ValidationError(`User with googleSub "${user.googleSub}" already exists`);
|
|
45
|
+
content.users.push(user);
|
|
46
|
+
await this.write(content);
|
|
47
|
+
return user;
|
|
48
|
+
}
|
|
49
|
+
async update(id, patch) {
|
|
50
|
+
const content = await this.read();
|
|
51
|
+
const idx = content.users.findIndex(u => u.id === id);
|
|
52
|
+
if (idx < 0)
|
|
53
|
+
throw new NotFoundError(`User "${id}" not found`);
|
|
54
|
+
const current = content.users[idx];
|
|
55
|
+
// Drop undefined keys so `{ displayName: undefined }` doesn't blow
|
|
56
|
+
// away the existing displayName. Zod's `.partial()` widens every
|
|
57
|
+
// field to optional, but the persisted User schema still requires
|
|
58
|
+
// the non-optional fields to be present.
|
|
59
|
+
const defined = Object.fromEntries(Object.entries(patch).filter(([, v]) => v !== undefined));
|
|
60
|
+
const merged = { ...current, ...defined };
|
|
61
|
+
content.users[idx] = merged;
|
|
62
|
+
await this.write(content);
|
|
63
|
+
return merged;
|
|
64
|
+
}
|
|
65
|
+
async delete(id) {
|
|
66
|
+
const content = await this.read();
|
|
67
|
+
const filtered = content.users.filter(u => u.id !== id);
|
|
68
|
+
if (filtered.length === content.users.length)
|
|
69
|
+
return;
|
|
70
|
+
await this.write({ users: filtered });
|
|
71
|
+
}
|
|
72
|
+
async read() {
|
|
73
|
+
try {
|
|
74
|
+
const raw = await readFile(this.filePath, 'utf-8');
|
|
75
|
+
const parsed = RegistryContent.safeParse(JSON.parse(raw));
|
|
76
|
+
if (!parsed.success)
|
|
77
|
+
throw new ValidationError(`Invalid user registry at "${this.filePath}": ${parsed.error.message}`);
|
|
78
|
+
return parsed.data;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error.code === 'ENOENT')
|
|
82
|
+
return { users: [] };
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async write(content) {
|
|
87
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
88
|
+
await writeFile(this.filePath, `${JSON.stringify(content, null, 2)}\n`, 'utf-8');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=UserRegistryFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserRegistryFile.js","sourceRoot":"","sources":["../../../src/infrastructure/users/UserRegistryFile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { UserRegistryFile } from './UserRegistryFile.js';
|
|
2
|
+
/**
|
|
3
|
+
* Branded id reserved for the single implicit account on a local install.
|
|
4
|
+
* `userIdMiddleware` resolves to this value when no `X-Braid-User` header
|
|
5
|
+
* is sent (the embedded sidecar path), so the registry MUST have a row
|
|
6
|
+
* for it or `getMe` and audit-trail reads break.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LOCAL_USER_ID: string & import("zod").BRAND<"UserId">;
|
|
9
|
+
/**
|
|
10
|
+
* Idempotently ensure the `local-user` row exists. Pre-existing rows
|
|
11
|
+
* are left untouched (the user may have renamed themselves, set an
|
|
12
|
+
* email, etc.). Default `displayName` comes from the OS account.
|
|
13
|
+
*/
|
|
14
|
+
export declare function ensureLocalUser(registry: UserRegistryFile): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=ensureLocalUser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensureLocalUser.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/users/ensureLocalUser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAK7D;;;;;GAKG;AACH,eAAO,MAAM,aAAa,wCAA6B,CAAA;AAEvD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/E"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { userInfo } from 'node:os';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { UserId } from '@braidhq/schema';
|
|
4
|
+
/**
|
|
5
|
+
* Branded id reserved for the single implicit account on a local install.
|
|
6
|
+
* `userIdMiddleware` resolves to this value when no `X-Braid-User` header
|
|
7
|
+
* is sent (the embedded sidecar path), so the registry MUST have a row
|
|
8
|
+
* for it or `getMe` and audit-trail reads break.
|
|
9
|
+
*/
|
|
10
|
+
export const LOCAL_USER_ID = UserId.parse('local-user');
|
|
11
|
+
/**
|
|
12
|
+
* Idempotently ensure the `local-user` row exists. Pre-existing rows
|
|
13
|
+
* are left untouched (the user may have renamed themselves, set an
|
|
14
|
+
* email, etc.). Default `displayName` comes from the OS account.
|
|
15
|
+
*/
|
|
16
|
+
export async function ensureLocalUser(registry) {
|
|
17
|
+
const existing = await registry.get(LOCAL_USER_ID);
|
|
18
|
+
if (existing)
|
|
19
|
+
return;
|
|
20
|
+
await registry.create({
|
|
21
|
+
id: LOCAL_USER_ID,
|
|
22
|
+
displayName: safeOsUsername(),
|
|
23
|
+
serverRole: 'admin',
|
|
24
|
+
createdAt: new Date().toISOString(),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function safeOsUsername() {
|
|
28
|
+
try {
|
|
29
|
+
const info = userInfo();
|
|
30
|
+
if (info.username && info.username.length > 0)
|
|
31
|
+
return info.username;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Some sandboxed environments throw on userInfo(); fall through.
|
|
35
|
+
}
|
|
36
|
+
return process.env.USER ?? process.env.USERNAME ?? 'local';
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=ensureLocalUser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensureLocalUser.js","sourceRoot":"","sources":["../../../src/infrastructure/users/ensureLocalUser.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAClC,OAAO,OAAO,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAExC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;AAEvD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAA0B;IAC9D,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IAClD,IAAI,QAAQ;QACV,OAAM;IAER,MAAM,QAAQ,CAAC,MAAM,CAAC;QACpB,EAAE,EAAE,aAAa;QACjB,WAAW,EAAE,cAAc,EAAE;QAC7B,UAAU,EAAE,OAAO;QACnB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAe;KACjD,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;QACvB,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;YAC3C,OAAO,IAAI,CAAC,QAAQ,CAAA;IACxB,CAAC;IACD,MAAM,CAAC;QACL,iEAAiE;IACnE,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,OAAO,CAAA;AAC5D,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WorkspaceRegistryFile } from '../fs/WorkspaceRegistryFile.js';
|
|
2
|
+
/**
|
|
3
|
+
* Phase C migration: workspaces registered before members existed have
|
|
4
|
+
* empty `members[]`. Seed each one with `local-user` as owner so the
|
|
5
|
+
* single-tenant install keeps working with no manual steps. Idempotent
|
|
6
|
+
* — workspaces that already have members are left alone.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ensureWorkspaceOwners(registry: WorkspaceRegistryFile): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=ensureWorkspaceOwners.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensureWorkspaceOwners.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/users/ensureWorkspaceOwners.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAA;AAG3E;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAa1F"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { LOCAL_USER_ID } from './ensureLocalUser.js';
|
|
2
|
+
/**
|
|
3
|
+
* Phase C migration: workspaces registered before members existed have
|
|
4
|
+
* empty `members[]`. Seed each one with `local-user` as owner so the
|
|
5
|
+
* single-tenant install keeps working with no manual steps. Idempotent
|
|
6
|
+
* — workspaces that already have members are left alone.
|
|
7
|
+
*/
|
|
8
|
+
export async function ensureWorkspaceOwners(registry) {
|
|
9
|
+
const rootPaths = await registry.list();
|
|
10
|
+
const now = new Date().toISOString();
|
|
11
|
+
for (const rootPath of rootPaths) {
|
|
12
|
+
const members = await registry.listMembers(rootPath);
|
|
13
|
+
if (members.length > 0)
|
|
14
|
+
continue;
|
|
15
|
+
await registry.addMember(rootPath, {
|
|
16
|
+
userId: LOCAL_USER_ID,
|
|
17
|
+
role: 'owner',
|
|
18
|
+
joinedAt: now,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=ensureWorkspaceOwners.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensureWorkspaceOwners.js","sourceRoot":"","sources":["../../../src/infrastructure/users/ensureWorkspaceOwners.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,QAA+B;IACzE,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IACvC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAe,CAAA;IACjD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;QACpD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YACpB,SAAQ;QACV,MAAM,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE;YACjC,MAAM,EAAE,aAAa;YACrB,IAAI,EAAE,OAAO;YACb,QAAQ,EAAE,GAAG;SACd,CAAC,CAAA;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { SessionStore } from '../infrastructure/auth/SessionStore.js';
|
|
3
|
+
export interface AuthMiddlewareOptions {
|
|
4
|
+
readonly sessionStore: SessionStore;
|
|
5
|
+
/**
|
|
6
|
+
* When true, skip the Bearer check and let `userIdMiddleware`'s
|
|
7
|
+
* fallback (local-user via env / header) take over. The Tauri
|
|
8
|
+
* embedded sidecar sets this so the local install keeps working
|
|
9
|
+
* without an OAuth round trip; a remote deployment leaves it false
|
|
10
|
+
* so anonymous traffic is rejected.
|
|
11
|
+
*
|
|
12
|
+
* Resolution: explicit option > `BRAID_LOCAL_TRUST=true` env var.
|
|
13
|
+
* Defaults to `false` when neither is set — composeFs flips this
|
|
14
|
+
* to `true` for local development.
|
|
15
|
+
*/
|
|
16
|
+
readonly localTrust?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Pull a Bearer token off the request, or return `undefined` if the
|
|
20
|
+
* header is missing / malformed. Route handlers use this to forward the
|
|
21
|
+
* caller's identity to spawned skill subprocesses without re-parsing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractBearerToken(context: {
|
|
24
|
+
req: {
|
|
25
|
+
header: (name: string) => string | undefined;
|
|
26
|
+
};
|
|
27
|
+
}): string | undefined;
|
|
28
|
+
export declare function authMiddleware(options: AuthMiddlewareOptions): MiddlewareHandler;
|
|
29
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AAmB1E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAA;IACnC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAC9B;AAUD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE;QAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;KAAE,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAMzH;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,iBAAiB,CAoDhF"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { parseBoolEnv } from '../infrastructure/env.js';
|
|
3
|
+
/**
|
|
4
|
+
* Routes the auth middleware never gates. Anonymous access is required
|
|
5
|
+
* here, otherwise the login flow itself becomes impossible to bootstrap.
|
|
6
|
+
*
|
|
7
|
+
* Path prefixes (not exact match) so `/auth/google/start`,
|
|
8
|
+
* `/auth/google/callback`, `/auth/whoami` all flow through without a
|
|
9
|
+
* token. `/health` stays open so platform probes don't need a token.
|
|
10
|
+
* `/openapi.json` matches the broad industry convention that OpenAPI
|
|
11
|
+
* descriptions are publicly readable (Swagger UI, codegen, MCP gateways
|
|
12
|
+
* default to fetching them without auth). It only documents shape, not
|
|
13
|
+
* data, so leaking it to anonymous callers is safe.
|
|
14
|
+
*/
|
|
15
|
+
const PUBLIC_EXACT = new Set(['/openapi.json']);
|
|
16
|
+
const PUBLIC_PREFIXES = ['/auth/', '/health'];
|
|
17
|
+
// EventSource (browser SSE) cannot send custom Authorization headers, so
|
|
18
|
+
// for live-update endpoints we additionally accept a `?token=...` query
|
|
19
|
+
// parameter and treat it identically to a Bearer token. Limited to paths
|
|
20
|
+
// ending in `/events` so this lenient path doesn't apply to mutating routes.
|
|
21
|
+
function isSseEventsPath(path) {
|
|
22
|
+
return path.endsWith('/events');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Pull a Bearer token off the request, or return `undefined` if the
|
|
26
|
+
* header is missing / malformed. Route handlers use this to forward the
|
|
27
|
+
* caller's identity to spawned skill subprocesses without re-parsing.
|
|
28
|
+
*/
|
|
29
|
+
export function extractBearerToken(context) {
|
|
30
|
+
const header = context.req.header('Authorization');
|
|
31
|
+
if (!header || !header.startsWith('Bearer '))
|
|
32
|
+
return undefined;
|
|
33
|
+
const token = header.slice('Bearer '.length).trim();
|
|
34
|
+
return token.length > 0 ? token : undefined;
|
|
35
|
+
}
|
|
36
|
+
export function authMiddleware(options) {
|
|
37
|
+
const localTrust = options.localTrust ?? parseBoolEnv(process.env.BRAID_LOCAL_TRUST, false);
|
|
38
|
+
return async (context, next) => {
|
|
39
|
+
const path = context.req.path;
|
|
40
|
+
if (PUBLIC_EXACT.has(path) || PUBLIC_PREFIXES.some(prefix => path.startsWith(prefix))) {
|
|
41
|
+
await next();
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (localTrust) {
|
|
45
|
+
// The downstream `userIdMiddleware` will stamp `local-user` (or
|
|
46
|
+
// honour an explicit `X-Braid-User` header for multi-persona
|
|
47
|
+
// dev). The auth layer has no opinion here.
|
|
48
|
+
await next();
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const header = context.req.header('Authorization');
|
|
52
|
+
let token = header?.startsWith('Bearer ') ? header.slice('Bearer '.length).trim() : null;
|
|
53
|
+
// Fall back to a query-param token for SSE endpoints only.
|
|
54
|
+
if (!token && isSseEventsPath(path)) {
|
|
55
|
+
const queryToken = context.req.query('token');
|
|
56
|
+
if (queryToken)
|
|
57
|
+
token = queryToken;
|
|
58
|
+
}
|
|
59
|
+
if (!token) {
|
|
60
|
+
return context.json({
|
|
61
|
+
type: 'about:blank',
|
|
62
|
+
title: 'Unauthorized',
|
|
63
|
+
status: 401,
|
|
64
|
+
detail: 'Missing or invalid Authorization header. Sign in to continue.',
|
|
65
|
+
}, 401, { 'Content-Type': 'application/problem+json' });
|
|
66
|
+
}
|
|
67
|
+
const session = await options.sessionStore.resolve(token);
|
|
68
|
+
if (!session) {
|
|
69
|
+
return context.json({
|
|
70
|
+
type: 'about:blank',
|
|
71
|
+
title: 'Unauthorized',
|
|
72
|
+
status: 401,
|
|
73
|
+
detail: 'Session expired or revoked. Sign in again.',
|
|
74
|
+
}, 401, { 'Content-Type': 'application/problem+json' });
|
|
75
|
+
}
|
|
76
|
+
context.set('userId', session.userId);
|
|
77
|
+
await next();
|
|
78
|
+
return undefined;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/middleware/cors.js
CHANGED
|
@@ -19,7 +19,7 @@ export function corsMiddleware(options = {}) {
|
|
|
19
19
|
}
|
|
20
20
|
if (context.req.method === 'OPTIONS') {
|
|
21
21
|
context.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
|
22
|
-
context.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
22
|
+
context.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Braid-User');
|
|
23
23
|
context.header('Access-Control-Max-Age', '86400');
|
|
24
24
|
return context.body(null, 204);
|
|
25
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.js","sourceRoot":"","sources":["../../src/middleware/cors.ts"],"names":[],"mappings":"AAEA,MAAM,mBAAmB,GAAG;IAC1B,uBAAuB;IACvB,uBAAuB;IACvB,uBAAuB;CACxB,CAAA;AAMD;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,UAAuB,EAAE;IACtD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,cAAc,IAAI,mBAAmB,CAAC,CAAA;IACtE,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC3C,IAAI,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,MAAM,CAAC,6BAA6B,EAAE,MAAM,CAAC,CAAA;YACrD,OAAO,CAAC,MAAM,CAAC,kCAAkC,EAAE,MAAM,CAAC,CAAA;YAC1D,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAClC,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACrC,OAAO,CAAC,MAAM,CAAC,8BAA8B,EAAE,wCAAwC,CAAC,CAAA;YACxF,OAAO,CAAC,MAAM,CAAC,8BAA8B,EAAE,
|
|
1
|
+
{"version":3,"file":"cors.js","sourceRoot":"","sources":["../../src/middleware/cors.ts"],"names":[],"mappings":"AAEA,MAAM,mBAAmB,GAAG;IAC1B,uBAAuB;IACvB,uBAAuB;IACvB,uBAAuB;CACxB,CAAA;AAMD;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,UAAuB,EAAE;IACtD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,cAAc,IAAI,mBAAmB,CAAC,CAAA;IACtE,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC3C,IAAI,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,MAAM,CAAC,6BAA6B,EAAE,MAAM,CAAC,CAAA;YACrD,OAAO,CAAC,MAAM,CAAC,kCAAkC,EAAE,MAAM,CAAC,CAAA;YAC1D,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAClC,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACrC,OAAO,CAAC,MAAM,CAAC,8BAA8B,EAAE,wCAAwC,CAAC,CAAA;YACxF,OAAO,CAAC,MAAM,CAAC,8BAA8B,EAAE,2CAA2C,CAAC,CAAA;YAC3F,OAAO,CAAC,MAAM,CAAC,wBAAwB,EAAE,OAAO,CAAC,CAAA;YACjD,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QAChC,CAAC;QACD,MAAM,IAAI,EAAE,CAAA;QACZ,OAAO,SAAS,CAAA;IAClB,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { UserRegistryFile } from '../infrastructure/users/UserRegistryFile.js';
|
|
3
|
+
/**
|
|
4
|
+
* Server-admin gate. Composes after `authMiddleware` + `userIdMiddleware`
|
|
5
|
+
* so the resolved userId is on the context. Local-trust callers stamped
|
|
6
|
+
* as `local-user` pass through because `ensureLocalUser` seeds them
|
|
7
|
+
* with `serverRole: 'admin'`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function requireAdmin(userRegistry: UserRegistryFile): MiddlewareHandler;
|
|
10
|
+
//# sourceMappingURL=requireAdmin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requireAdmin.d.ts","sourceRoot":"","sources":["../../src/middleware/requireAdmin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAA;AAGnF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,gBAAgB,GAAG,iBAAiB,CAmB9E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getUserId } from './userId.js';
|
|
2
|
+
/**
|
|
3
|
+
* Server-admin gate. Composes after `authMiddleware` + `userIdMiddleware`
|
|
4
|
+
* so the resolved userId is on the context. Local-trust callers stamped
|
|
5
|
+
* as `local-user` pass through because `ensureLocalUser` seeds them
|
|
6
|
+
* with `serverRole: 'admin'`.
|
|
7
|
+
*/
|
|
8
|
+
export function requireAdmin(userRegistry) {
|
|
9
|
+
return async (context, next) => {
|
|
10
|
+
const userId = getUserId(context);
|
|
11
|
+
const user = await userRegistry.get(userId);
|
|
12
|
+
if (!user || user.serverRole !== 'admin') {
|
|
13
|
+
return context.json({
|
|
14
|
+
type: 'about:blank',
|
|
15
|
+
title: 'Forbidden',
|
|
16
|
+
status: 403,
|
|
17
|
+
detail: 'Server admin required.',
|
|
18
|
+
}, 403, { 'Content-Type': 'application/problem+json' });
|
|
19
|
+
}
|
|
20
|
+
await next();
|
|
21
|
+
return undefined;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=requireAdmin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requireAdmin.js","sourceRoot":"","sources":["../../src/middleware/requireAdmin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,YAA8B;IACzD,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAA;QACjC,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAC3C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;YACzC,OAAO,OAAO,CAAC,IAAI,CACjB;gBACE,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,WAAW;gBAClB,MAAM,EAAE,GAAG;gBACX,MAAM,EAAE,wBAAwB;aACjC,EACD,GAAG,EACH,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAC/C,CAAA;QACH,CAAC;QACD,MAAM,IAAI,EAAE,CAAA;QACZ,OAAO,SAAS,CAAA;IAClB,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { UserId as UserIdType } from '@braidhq/schema';
|
|
2
|
+
import type { Context, MiddlewareHandler } from 'hono';
|
|
3
|
+
declare module 'hono' {
|
|
4
|
+
interface ContextVariableMap {
|
|
5
|
+
userId: UserIdType;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the caller's userId from `X-Braid-User` (Phase A) and stash
|
|
10
|
+
* it on the request context. Falls back to `local-user` when the
|
|
11
|
+
* header is absent, preserving local-only behaviour for the embedded
|
|
12
|
+
* sidecar that doesn't bother sending one.
|
|
13
|
+
*/
|
|
14
|
+
export declare const userIdMiddleware: MiddlewareHandler;
|
|
15
|
+
export declare function getUserId(context: Context): UserIdType;
|
|
16
|
+
//# sourceMappingURL=userId.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userId.d.ts","sourceRoot":"","sources":["../../src/middleware/userId.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAGtD,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,kBAAkB;QAC1B,MAAM,EAAE,UAAU,CAAA;KACnB;CACF;AAaD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,iBAM9B,CAAA;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAEtD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { UserId } from '@braidhq/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Header name carrying the caller's userId in Phase A (pre-auth).
|
|
4
|
+
* Phase B (Google OAuth) will replace this with `Authorization: Bearer`
|
|
5
|
+
* token resolution and `c.set('userId', ...)` upstream of this middleware;
|
|
6
|
+
* the rest of the codebase only ever calls `getUserId(c)`, so the
|
|
7
|
+
* substitution stays local to the auth layer.
|
|
8
|
+
*/
|
|
9
|
+
const HEADER = 'X-Braid-User';
|
|
10
|
+
const DEFAULT_USER_ID = UserId.parse('local-user');
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the caller's userId from `X-Braid-User` (Phase A) and stash
|
|
13
|
+
* it on the request context. Falls back to `local-user` when the
|
|
14
|
+
* header is absent, preserving local-only behaviour for the embedded
|
|
15
|
+
* sidecar that doesn't bother sending one.
|
|
16
|
+
*/
|
|
17
|
+
export const userIdMiddleware = async (context, next) => {
|
|
18
|
+
if (context.get('userId') === undefined) {
|
|
19
|
+
const header = context.req.header(HEADER);
|
|
20
|
+
context.set('userId', header && header.length > 0 ? UserId.parse(header) : DEFAULT_USER_ID);
|
|
21
|
+
}
|
|
22
|
+
await next();
|
|
23
|
+
};
|
|
24
|
+
export function getUserId(context) {
|
|
25
|
+
return context.get('userId');
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=userId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userId.js","sourceRoot":"","sources":["../../src/middleware/userId.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAQxC;;;;;;GAMG;AACH,MAAM,MAAM,GAAG,cAAc,CAAA;AAE7B,MAAM,eAAe,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;AAElD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAsB,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;IACzE,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,SAAS,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACzC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAA;IAC7F,CAAC;IACD,MAAM,IAAI,EAAE,CAAA;AACd,CAAC,CAAA;AAED,MAAM,UAAU,SAAS,CAAC,OAAgB;IACxC,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;AAC9B,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { WorkspaceService } from '@braidhq/core';
|
|
2
|
+
import type { Context, MiddlewareHandler } from 'hono';
|
|
3
|
+
import type { WorkspaceRegistryFile } from '../infrastructure/fs/WorkspaceRegistryFile.js';
|
|
4
|
+
import type { UserRegistryFile } from '../infrastructure/users/UserRegistryFile.js';
|
|
5
|
+
import type { Capability, ViewerContext } from '../policy/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Server-scope capability guard. Used for actions that happen without
|
|
8
|
+
* a workspace context (e.g. workspace creation). Builds a viewer with
|
|
9
|
+
* member=undefined; non-admin users resolve to effectiveRole=null and
|
|
10
|
+
* fail every check by construction.
|
|
11
|
+
*
|
|
12
|
+
* Skips the gate when userRegistry isn't provided so in-memory test
|
|
13
|
+
* compositions stay open. Production deployments always pass it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function requireServerCapability(capability: Capability, userRegistry: UserRegistryFile | undefined): MiddlewareHandler;
|
|
16
|
+
declare module 'hono' {
|
|
17
|
+
interface ContextVariableMap {
|
|
18
|
+
viewerContext: ViewerContext;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export interface WorkspaceAccessOptions {
|
|
22
|
+
readonly registry: WorkspaceRegistryFile;
|
|
23
|
+
readonly workspaceService: WorkspaceService;
|
|
24
|
+
readonly userRegistry: UserRegistryFile;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the caller's ViewerContext for this workspace and stashes
|
|
28
|
+
* it on the Hono context for every downstream layer to read. Composes
|
|
29
|
+
* after `workspaceIdMiddleware` + `userIdMiddleware`. Outsiders (no
|
|
30
|
+
* member row + not a server admin) get 403 here.
|
|
31
|
+
*
|
|
32
|
+
* The actual policy decisions live in `policy/`; this middleware just
|
|
33
|
+
* builds the viewer and rejects unauthenticated outsiders.
|
|
34
|
+
*/
|
|
35
|
+
export declare function workspaceAccessMiddleware(options: WorkspaceAccessOptions): MiddlewareHandler;
|
|
36
|
+
export declare function getViewerContext(context: Context): ViewerContext | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Mutation guard. Returns 403 if the viewer can't perform the given
|
|
39
|
+
* capability. Optional `buildResource` lets specific capabilities
|
|
40
|
+
* (e.g. `skill.run`) attach per-request data the check needs.
|
|
41
|
+
*
|
|
42
|
+
* Test compositions that don't mount `workspaceAccessMiddleware`
|
|
43
|
+
* (in-memory tests, headless server) skip the gate. Production
|
|
44
|
+
* deployments always mount the access middleware so the gate is real.
|
|
45
|
+
*/
|
|
46
|
+
export declare function requirePermission(capability: Capability, buildResource?: (context: Context) => Promise<ViewerContext['resource']>): MiddlewareHandler;
|
|
47
|
+
//# sourceMappingURL=workspaceAccess.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceAccess.d.ts","sourceRoot":"","sources":["../../src/middleware/workspaceAccess.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AACtD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+CAA+C,CAAA;AAC1F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAA;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAKnE;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,gBAAgB,GAAG,SAAS,GACzC,iBAAiB,CAgBnB;AAED,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,kBAAkB;QAC1B,aAAa,EAAE,aAAa,CAAA;KAC7B;CACF;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAA;IACxC,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAA;IAC3C,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAA;CACxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,GAAG,iBAAiB,CAgB5F;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAE5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,UAAU,EACtB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,GACvE,iBAAiB,CAcnB"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { defaultPermissionRegistry, resolveViewer } from '../policy/index.js';
|
|
2
|
+
import { getUserId } from './userId.js';
|
|
3
|
+
import { getWorkspaceId } from './workspaceId.js';
|
|
4
|
+
/**
|
|
5
|
+
* Server-scope capability guard. Used for actions that happen without
|
|
6
|
+
* a workspace context (e.g. workspace creation). Builds a viewer with
|
|
7
|
+
* member=undefined; non-admin users resolve to effectiveRole=null and
|
|
8
|
+
* fail every check by construction.
|
|
9
|
+
*
|
|
10
|
+
* Skips the gate when userRegistry isn't provided so in-memory test
|
|
11
|
+
* compositions stay open. Production deployments always pass it.
|
|
12
|
+
*/
|
|
13
|
+
export function requireServerCapability(capability, userRegistry) {
|
|
14
|
+
return async (context, next) => {
|
|
15
|
+
if (!userRegistry) {
|
|
16
|
+
await next();
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const userId = getUserId(context);
|
|
20
|
+
const user = await userRegistry.get(userId);
|
|
21
|
+
if (!user)
|
|
22
|
+
return forbid(context, `Unknown user "${userId}".`);
|
|
23
|
+
const viewer = resolveViewer(user, undefined);
|
|
24
|
+
if (!defaultPermissionRegistry.can(capability, viewer))
|
|
25
|
+
return forbid(context, `Your role cannot perform "${capability}".`);
|
|
26
|
+
await next();
|
|
27
|
+
return undefined;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolves the caller's ViewerContext for this workspace and stashes
|
|
32
|
+
* it on the Hono context for every downstream layer to read. Composes
|
|
33
|
+
* after `workspaceIdMiddleware` + `userIdMiddleware`. Outsiders (no
|
|
34
|
+
* member row + not a server admin) get 403 here.
|
|
35
|
+
*
|
|
36
|
+
* The actual policy decisions live in `policy/`; this middleware just
|
|
37
|
+
* builds the viewer and rejects unauthenticated outsiders.
|
|
38
|
+
*/
|
|
39
|
+
export function workspaceAccessMiddleware(options) {
|
|
40
|
+
return async (context, next) => {
|
|
41
|
+
const workspaceId = getWorkspaceId(context);
|
|
42
|
+
const userId = getUserId(context);
|
|
43
|
+
const user = await options.userRegistry.get(userId);
|
|
44
|
+
if (!user)
|
|
45
|
+
return forbid(context, `Unknown user "${userId}".`);
|
|
46
|
+
const workspace = await options.workspaceService.findById(workspaceId);
|
|
47
|
+
const member = await options.registry.getMember(workspace.rootPath, userId);
|
|
48
|
+
const viewer = resolveViewer(user, member);
|
|
49
|
+
if (viewer.effectiveRole === null)
|
|
50
|
+
return forbid(context, `You are not a member of workspace "${workspaceId}".`);
|
|
51
|
+
context.set('viewerContext', viewer);
|
|
52
|
+
await next();
|
|
53
|
+
return undefined;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function getViewerContext(context) {
|
|
57
|
+
return context.get('viewerContext');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Mutation guard. Returns 403 if the viewer can't perform the given
|
|
61
|
+
* capability. Optional `buildResource` lets specific capabilities
|
|
62
|
+
* (e.g. `skill.run`) attach per-request data the check needs.
|
|
63
|
+
*
|
|
64
|
+
* Test compositions that don't mount `workspaceAccessMiddleware`
|
|
65
|
+
* (in-memory tests, headless server) skip the gate. Production
|
|
66
|
+
* deployments always mount the access middleware so the gate is real.
|
|
67
|
+
*/
|
|
68
|
+
export function requirePermission(capability, buildResource) {
|
|
69
|
+
return async (context, next) => {
|
|
70
|
+
const base = getViewerContext(context);
|
|
71
|
+
if (!base) {
|
|
72
|
+
await next();
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const resource = buildResource ? await buildResource(context) : undefined;
|
|
76
|
+
const viewer = resource ? { ...base, resource } : base;
|
|
77
|
+
if (!defaultPermissionRegistry.can(capability, viewer))
|
|
78
|
+
return forbid(context, `Your role cannot perform "${capability}".`);
|
|
79
|
+
await next();
|
|
80
|
+
return undefined;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function forbid(context, detail) {
|
|
84
|
+
return context.json({ type: 'about:blank', title: 'Forbidden', status: 403, detail }, 403, { 'Content-Type': 'application/problem+json' });
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=workspaceAccess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceAccess.js","sourceRoot":"","sources":["../../src/middleware/workspaceAccess.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closed enumeration of capability identifiers known to the core
|
|
3
|
+
* platform. Plugins can register their own checks under custom string
|
|
4
|
+
* ids; this list is the central catalog so first-party code has a
|
|
5
|
+
* type-safe handle.
|
|
6
|
+
*
|
|
7
|
+
* New capability = add to CAPABILITY_IDS + write a CapabilityCheck
|
|
8
|
+
* class implementing it. Existing checks don't care about new ids.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CAPABILITY_IDS: readonly ["workspace.create", "workspace.read", "workspace.write", "proposal.read", "proposal.write", "clarify.read", "clarify.write", "history.write", "skill.run"];
|
|
11
|
+
export type Capability = (typeof CAPABILITY_IDS)[number];
|
|
12
|
+
//# sourceMappingURL=Capability.d.ts.map
|