@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.
@@ -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":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;AAGzE,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;CACvD,CAAC,CAAC;AAGH,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,IAAI,EAAE,mBAAmB;IACzB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;CACrB,CAAC,CAAC;AAGH,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC;CACpC,CAAC,CAAC;AAaH;;;;;;GAMG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACnC,IAAI,GAAG,iBAA0B,CAAC;IAClC,OAAO,CAAwB;IAExC,YAAY,OAA8B;QACxC,KAAK,CACH,sCAAsC,OAAO,CAAC,iBAAiB,oCAAoC,OAAO,CAAC,eAAe,OAAO,OAAO,CAAC,MAAM,EAAE,CAClJ,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,kFAAkF;QAClF,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB,CAClC,GAAkB,EAClB,IAA6B;IAE7B,IAAI,IAAI,CAAC,WAAW,KAAK,GAAG,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,IAAI,mBAAmB,CAAC;YAC5B,iBAAiB,EAAE,IAAI,CAAC,WAAW;YACnC,eAAe,EAAE,GAAG,CAAC,WAAW;YAChC,MAAM,EAAE,qEAAqE;SAC9E,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC,WAAW,CAAC;AACzB,CAAC;AAED,8EAA8E;AAC9E,yBAAyB;AACzB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC;CACpC,CAAC,CAAC;AAIH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAa;IAEb,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,yDAAyD,CAC1D,CAAC;IACJ,CAAC;IAED,gFAAgF;IAChF,OAAO,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC3C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vantageos/cloud-identity",
3
- "version": "0.1.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": "MIT",
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
  }