@vantageos/cloud-identity 0.1.0 → 0.3.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/CHANGELOG.md +60 -1
- package/LICENSE +105 -16
- package/README.md +183 -53
- package/dist/bearer-validation.d.ts +2 -3
- package/dist/bearer-validation.d.ts.map +1 -1
- package/dist/bearer-validation.js +2 -3
- package/dist/bearer-validation.js.map +1 -1
- package/dist/crypto.d.ts +3 -5
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +3 -5
- package/dist/crypto.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/org-guard.d.ts +79 -0
- package/dist/org-guard.d.ts.map +1 -0
- package/dist/org-guard.js +70 -0
- package/dist/org-guard.js.map +1 -0
- package/dist/scope-filter.d.ts +33 -18
- package/dist/scope-filter.d.ts.map +1 -1
- package/dist/scope-filter.js +44 -19
- package/dist/scope-filter.js.map +1 -1
- package/dist/tenancy-domain.d.ts +127 -0
- package/dist/tenancy-domain.d.ts.map +1 -0
- package/dist/tenancy-domain.js +148 -0
- package/dist/tenancy-domain.js.map +1 -0
- package/package.json +9 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vantageos/cloud-identity 0.2.0 — Domain-tenancy layer.
|
|
3
|
+
*
|
|
4
|
+
* Additive-only: does not modify any 0.1.0 exports.
|
|
5
|
+
*
|
|
6
|
+
* Provides:
|
|
7
|
+
* - WorkspaceRole literal union + Zod schema
|
|
8
|
+
* - Workspace type + Zod schema
|
|
9
|
+
* - WorkspaceMember type + Zod schema
|
|
10
|
+
* - TenantContext type + Zod schema
|
|
11
|
+
* - ScopeViolationError class (code: "SCOPE_VIOLATION")
|
|
12
|
+
* - getEffectiveTenantId(ctx, args) — multi-tenant isolation guard
|
|
13
|
+
* - decodeUnverifiedBearer(token) — DECODE-ONLY helper, NOT authentication.
|
|
14
|
+
* The decoded payload is attacker-controlled. Production code MUST use a
|
|
15
|
+
* signed JWT or opaque-token lookup before treating the payload as a
|
|
16
|
+
* trust boundary.
|
|
17
|
+
*/
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// WorkspaceRole
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
export const workspaceRoleSchema = z.enum(["Admin", "Editor", "Viewer"]);
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Workspace
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
export const workspaceSchema = z.object({
|
|
27
|
+
id: z.string().min(1),
|
|
28
|
+
name: z.string().min(1),
|
|
29
|
+
createdAt: z.number(),
|
|
30
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
31
|
+
});
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// WorkspaceMember
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
export const workspaceMemberSchema = z.object({
|
|
36
|
+
userId: z.string().min(1),
|
|
37
|
+
workspaceId: z.string().min(1),
|
|
38
|
+
role: workspaceRoleSchema,
|
|
39
|
+
joinedAt: z.number(),
|
|
40
|
+
});
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// TenantContext
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
export const tenantContextSchema = z.object({
|
|
45
|
+
workspaceId: z.string().min(1),
|
|
46
|
+
userId: z.string().min(1),
|
|
47
|
+
roles: z.array(workspaceRoleSchema),
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Thrown by `getEffectiveTenantId` when `args.workspaceId` does not match the
|
|
51
|
+
* workspaceId encoded in the bearer-resolved `TenantContext`. This error is the
|
|
52
|
+
* canonical cross-tenant isolation signal across the VantagePeers Cloud fleet.
|
|
53
|
+
*
|
|
54
|
+
* Callers MUST translate this to a 403 or MCP permission-denied — never 404.
|
|
55
|
+
*/
|
|
56
|
+
export class ScopeViolationError extends Error {
|
|
57
|
+
code = "SCOPE_VIOLATION";
|
|
58
|
+
payload;
|
|
59
|
+
constructor(payload) {
|
|
60
|
+
super(`SCOPE_VIOLATION: requested tenant "${payload.requestedTenantId}" does not match context tenant "${payload.contextTenantId}" — ${payload.reason}`);
|
|
61
|
+
this.name = "ScopeViolationError";
|
|
62
|
+
this.payload = payload;
|
|
63
|
+
// Restore prototype chain (required for instanceof checks in transpiled targets).
|
|
64
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// getEffectiveTenantId
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the effective tenant workspace ID from a `TenantContext` + request args.
|
|
72
|
+
*
|
|
73
|
+
* If `args.workspaceId` differs from the context's `workspaceId` (derived from
|
|
74
|
+
* the bearer token), a `ScopeViolationError` is thrown immediately.
|
|
75
|
+
*
|
|
76
|
+
* This is the primary multi-tenant isolation gate — call it at the top of every
|
|
77
|
+
* MCP tool handler that accepts a `workspaceId` argument.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const tenantId = getEffectiveTenantId(ctx, args);
|
|
82
|
+
* // safe to use tenantId for DB queries — caller's scope is verified
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function getEffectiveTenantId(ctx, args) {
|
|
86
|
+
if (args.workspaceId !== ctx.workspaceId) {
|
|
87
|
+
throw new ScopeViolationError({
|
|
88
|
+
requestedTenantId: args.workspaceId,
|
|
89
|
+
contextTenantId: ctx.workspaceId,
|
|
90
|
+
reason: "args.workspaceId does not match bearer-resolved context workspaceId",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return ctx.workspaceId;
|
|
94
|
+
}
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// decodeUnverifiedBearer
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
/**
|
|
99
|
+
* @security ⚠️ DECODE-ONLY. NOT AUTHENTICATION. NOT A TRUST BOUNDARY.
|
|
100
|
+
*
|
|
101
|
+
* Decodes a `base64(JSON({ userId, workspaceId, roles[] }))` payload and
|
|
102
|
+
* validates its SHAPE with Zod. It performs NO signature verification, NO
|
|
103
|
+
* issuer check, NO expiry check, NO replay-attack protection. The decoded
|
|
104
|
+
* payload is attacker-controlled and MUST NOT be used as the source of
|
|
105
|
+
* truth for multi-tenant isolation.
|
|
106
|
+
*
|
|
107
|
+
* Forgery example: any attacker producing
|
|
108
|
+
* base64(JSON({ userId:"x", workspaceId:"victim-org", roles:["Admin"] }))
|
|
109
|
+
* passes this function. Treating the returned `workspaceId` as authoritative
|
|
110
|
+
* — for example feeding it into `getEffectiveTenantId` as the trusted
|
|
111
|
+
* `ctx.workspaceId` — silently grants cross-tenant access.
|
|
112
|
+
*
|
|
113
|
+
* Use only as:
|
|
114
|
+
* - test fixture / harness helper
|
|
115
|
+
* - decoder for a bearer that was ALREADY verified by an upstream signed-JWT
|
|
116
|
+
* middleware (the production trust boundary)
|
|
117
|
+
*
|
|
118
|
+
* For the production trust boundary, callers MUST substitute a signed JWT
|
|
119
|
+
* verifier (not yet exported by this package) or an opaque-token lookup
|
|
120
|
+
* against a Convex tenancy table.
|
|
121
|
+
*
|
|
122
|
+
* Renamed from `resolveBearer` (0.2.0 pre-release) to make the lack of
|
|
123
|
+
* verification impossible to miss at the call-site.
|
|
124
|
+
*/
|
|
125
|
+
const bearerPayloadSchema = z.object({
|
|
126
|
+
userId: z.string().min(1),
|
|
127
|
+
workspaceId: z.string().min(1),
|
|
128
|
+
roles: z.array(workspaceRoleSchema),
|
|
129
|
+
});
|
|
130
|
+
export async function decodeUnverifiedBearer(token) {
|
|
131
|
+
let raw;
|
|
132
|
+
try {
|
|
133
|
+
raw = Buffer.from(token, "base64").toString("utf-8");
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
throw new Error(`decodeUnverifiedBearer: failed to base64-decode token`);
|
|
137
|
+
}
|
|
138
|
+
let parsed;
|
|
139
|
+
try {
|
|
140
|
+
parsed = JSON.parse(raw);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
throw new Error(`decodeUnverifiedBearer: token payload is not valid JSON`);
|
|
144
|
+
}
|
|
145
|
+
// Zod parse — throws ZodError on schema violations (missing fields, bad roles).
|
|
146
|
+
return bearerPayloadSchema.parse(parsed);
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=tenancy-domain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenancy-domain.js","sourceRoot":"","sources":["../src/tenancy-domain.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vantageos/cloud-identity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Framework-agnostic identity + scope-filter primitives for VantagePeers Cloud (multi-tenant MCP).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"./types": {
|
|
26
26
|
"types": "./dist/types.d.ts",
|
|
27
27
|
"import": "./dist/types.js"
|
|
28
|
+
},
|
|
29
|
+
"./tenancy-domain": {
|
|
30
|
+
"types": "./dist/tenancy-domain.d.ts",
|
|
31
|
+
"import": "./dist/tenancy-domain.js"
|
|
28
32
|
}
|
|
29
33
|
},
|
|
30
34
|
"files": [
|
|
@@ -49,7 +53,7 @@
|
|
|
49
53
|
"identity"
|
|
50
54
|
],
|
|
51
55
|
"author": "VantagePeers Cloud",
|
|
52
|
-
"license": "
|
|
56
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
53
57
|
"repository": {
|
|
54
58
|
"type": "git",
|
|
55
59
|
"url": "git+https://github.com/vantageos-agency/cloud-identity.git"
|
|
@@ -68,5 +72,8 @@
|
|
|
68
72
|
"@types/node": "^20.11.0",
|
|
69
73
|
"typescript": "^5.4.0",
|
|
70
74
|
"vitest": "^1.6.0"
|
|
75
|
+
},
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"zod": "^4.4.3"
|
|
71
78
|
}
|
|
72
79
|
}
|