@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
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,65 @@
|
|
|
3
3
|
All notable changes to `@vantageos/cloud-identity` are documented here.
|
|
4
4
|
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## [Unreleased] - 0.3.0
|
|
7
|
+
|
|
8
|
+
**BREAKING.** Closes two measured 0.2.0 defects — a right must never be
|
|
9
|
+
granted by absence.
|
|
10
|
+
|
|
11
|
+
### Changed (breaking)
|
|
12
|
+
|
|
13
|
+
- `passesScopeFilter`, `scopeFilterList`, `scopeFilterGet`, `isMasterScope`,
|
|
14
|
+
`isWildcardScope`: `oauthCtx` is now **mandatory** (`OAuthCtx`, no longer
|
|
15
|
+
`OAuthCtx | undefined`). The 0.2.0 behaviour of an omitted `oauthCtx`
|
|
16
|
+
silently passing every row (wildcard, as if master-scoped) is removed.
|
|
17
|
+
Omitting the argument now breaks the caller's build; at runtime (for
|
|
18
|
+
callers that bypass TypeScript) it throws instead of granting access.
|
|
19
|
+
Callers that deliberately want the old legacy-bearer wildcard behaviour
|
|
20
|
+
must import and pass the new `LEGACY_WILDCARD_CTX` sentinel explicitly.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `LEGACY_WILDCARD_CTX` — explicit, by-name opt-in for the pre-0.3.0
|
|
25
|
+
legacy-bearer wildcard behaviour (`src/scope-filter.ts`).
|
|
26
|
+
- `requireTenantId(source: TenantSource): string` — organization/workspace
|
|
27
|
+
membership guard, unifying the human (session) and machine (bearer) entry
|
|
28
|
+
paths behind one contract. Refuses on missing session, missing/empty `orgId`, and
|
|
29
|
+
missing/empty bearer-resolved `workspaceId` (`src/org-guard.ts`).
|
|
30
|
+
- `SessionIdentity`, `TenantSource` types.
|
|
31
|
+
|
|
32
|
+
### Scope note
|
|
33
|
+
|
|
34
|
+
The package now exposes, behind one contract, all four primitives an
|
|
35
|
+
application needs and should never reimplement: tenant resolution
|
|
36
|
+
(`getEffectiveTenantId`), membership requirement (`requireTenantId`), row
|
|
37
|
+
filtering (`passesScopeFilter` / `scopeFilterList` / `scopeFilterGet`), and
|
|
38
|
+
identifier validation (`validateMasterBearer`). Framework adapters (Convex,
|
|
39
|
+
Hono, etc.) wrap these; they must not reimplement the checks.
|
|
40
|
+
|
|
41
|
+
## [0.2.0] - 2026-06-13
|
|
42
|
+
|
|
43
|
+
Additive release — domain-tenancy layer. No breaking changes. All 0.1.0 exports unchanged.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- `WorkspaceRole` literal union `"Admin" | "Editor" | "Viewer"` + `workspaceRoleSchema` (Zod).
|
|
48
|
+
- `Workspace` type + `workspaceSchema` (Zod) — `{ id, name, createdAt, metadata? }`.
|
|
49
|
+
- `WorkspaceMember` type + `workspaceMemberSchema` (Zod) — `{ userId, workspaceId, role, joinedAt }`.
|
|
50
|
+
- `TenantContext` type + `tenantContextSchema` (Zod) — `{ workspaceId, userId, roles[] }`.
|
|
51
|
+
- `ScopeViolationError` class — `code: "SCOPE_VIOLATION"`, payload `{ requestedTenantId, contextTenantId, reason }`. Canonical cross-tenant isolation signal — translate to 403 or MCP permission-denied.
|
|
52
|
+
- `getEffectiveTenantId(ctx, args)` — multi-tenant isolation guard. Throws `ScopeViolationError` when `args.workspaceId !== ctx.workspaceId`; returns `ctx.workspaceId` on match.
|
|
53
|
+
- `decodeUnverifiedBearer(token)` — async DECODER for `base64(JSON({ userId, workspaceId, roles[] }))` into `BearerPayload`. Validates SHAPE via Zod and throws on shape or decode failure. **⚠️ NOT authentication. NOT a trust boundary.** Performs NO signature verification, NO issuer check, NO expiry check. The decoded payload is attacker-controlled — see the "Security: bearer decoding" section of README. Use as a test fixture or as a decoder for a bearer already verified by an upstream signed-JWT / opaque-token middleware. The production trust boundary (signed JWT + verification) is planned for 0.3.0. Renamed from the original `resolveBearer` (0.2.0 pre-release) per Eta security review — the new name makes the lack of verification impossible to miss at the call-site.
|
|
54
|
+
- `BearerPayload` type (inferred from Zod schema).
|
|
55
|
+
- `ScopeViolationPayload` interface.
|
|
56
|
+
- New subpath export: `@vantageos/cloud-identity/tenancy-domain`.
|
|
57
|
+
- `zod` added as a production dependency (schema validation for domain types).
|
|
58
|
+
|
|
59
|
+
### Notes
|
|
60
|
+
|
|
61
|
+
- All new symbols are also re-exported from the main `@vantageos/cloud-identity` entry point.
|
|
62
|
+
- `decodeUnverifiedBearer` uses base64-JSON as a lightweight format and is intentionally NOT an authentication primitive. Production callers MUST run a SIGNATURE-VERIFYING middleware (signed JWT / Clerk session / opaque-token lookup) before constructing the `TenantContext` passed to `getEffectiveTenantId`.
|
|
63
|
+
- `getEffectiveTenantId` expects a `TenantContext` whose `workspaceId` came from a signature-verified source. Feeding it the output of `decodeUnverifiedBearer` directly is a security bug — see README "Security: bearer decoding".
|
|
64
|
+
|
|
6
65
|
## [0.1.0] - 2026-06-04
|
|
7
66
|
|
|
8
67
|
Initial release.
|
|
@@ -14,5 +73,5 @@ Initial release.
|
|
|
14
73
|
- Public type surface: `OAuthCtx`, `ScopeProfile`, `NamespacePrefix`, `FromAllowListEntry`, `ValidateMasterBearerResult`.
|
|
15
74
|
|
|
16
75
|
### Notes
|
|
17
|
-
- `getEffectiveWorkspaceId` deferred to 0.2.0
|
|
76
|
+
- `getEffectiveWorkspaceId` deferred to 0.2.0.
|
|
18
77
|
- No `provenance: true` in `publishConfig` — provenance only from CI per fleet friction lesson `npm-publish-provenance-only-from-ci-not-local-host`.
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,110 @@
|
|
|
1
|
-
|
|
1
|
+
Functional Source License, Version 1.1, Apache 2.0 Future License
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Abbreviation
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
FSL-1.1-Apache-2.0
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 VantageOS (ElPi Corp)
|
|
10
|
+
|
|
11
|
+
Terms and Conditions
|
|
12
|
+
|
|
13
|
+
Licensor: VantageOS (ElPi Corp)
|
|
14
|
+
|
|
15
|
+
Licensed Work: VantagePeers
|
|
16
|
+
The Licensed Work is (c) 2026 VantageOS (ElPi Corp)
|
|
17
|
+
|
|
18
|
+
Additional Use Grant: You may make production use of the Licensed Work,
|
|
19
|
+
provided Your use does not include offering the Licensed
|
|
20
|
+
Work to third parties as a hosted or managed service
|
|
21
|
+
where the service provides users with access to any
|
|
22
|
+
substantial set of the features or functionality of the
|
|
23
|
+
Licensed Work.
|
|
24
|
+
|
|
25
|
+
Change Date: 2028-04-03 (two years from publication)
|
|
26
|
+
|
|
27
|
+
Change License: Apache License, Version 2.0
|
|
28
|
+
|
|
29
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
30
|
+
please contact: contact@vantageos.com
|
|
31
|
+
|
|
32
|
+
License text below is provided for informational purposes only.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
Functional Source License, Version 1.1, Apache 2.0 Future License
|
|
37
|
+
|
|
38
|
+
1. Purpose
|
|
39
|
+
|
|
40
|
+
This license gives you broad permission to use, modify, and share this
|
|
41
|
+
software, with one important condition: you may not use it to compete with us.
|
|
42
|
+
|
|
43
|
+
2. Acceptance
|
|
44
|
+
|
|
45
|
+
To use the software, you must agree to these terms. If you do not agree, you
|
|
46
|
+
may not use the software.
|
|
47
|
+
|
|
48
|
+
3. Copyright License
|
|
49
|
+
|
|
50
|
+
The licensor grants you a non-exclusive, royalty-free, worldwide, non-
|
|
51
|
+
sublicensable, non-transferable license to use, copy, distribute, make
|
|
52
|
+
available, and prepare derivative works of the software, in each case subject
|
|
53
|
+
to the limitations below.
|
|
54
|
+
|
|
55
|
+
4. Limitations
|
|
56
|
+
|
|
57
|
+
You may not make the functionality of the software available to third parties
|
|
58
|
+
as a service, or otherwise use the software to provide a service to third
|
|
59
|
+
parties that competes with the licensor.
|
|
60
|
+
|
|
61
|
+
5. Patents
|
|
62
|
+
|
|
63
|
+
The licensor grants you a license, under any patent claims the licensor can
|
|
64
|
+
license or becomes able to license, to make, have made, use, sell, offer for
|
|
65
|
+
sale, import, and have imported the software, in each case subject to the
|
|
66
|
+
limitations and conditions in this license.
|
|
67
|
+
|
|
68
|
+
6. Fair Use
|
|
69
|
+
|
|
70
|
+
This license is not intended to limit any rights you may have under applicable
|
|
71
|
+
fair use or other laws.
|
|
72
|
+
|
|
73
|
+
7. No Other Rights
|
|
74
|
+
|
|
75
|
+
These terms do not grant any other rights. The licensor reserves all rights not
|
|
76
|
+
expressly granted.
|
|
77
|
+
|
|
78
|
+
8. Termination
|
|
79
|
+
|
|
80
|
+
Your license is automatically terminated if you violate the terms. However, if
|
|
81
|
+
you cure the violation within 30 days of discovering it, your license is
|
|
82
|
+
reinstated retroactively.
|
|
83
|
+
|
|
84
|
+
9. Disclaimer
|
|
14
85
|
|
|
15
86
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
SOFTWARE.
|
|
87
|
+
IMPLIED.
|
|
88
|
+
|
|
89
|
+
10. Limitation of Liability
|
|
90
|
+
|
|
91
|
+
TO THE EXTENT PERMITTED BY LAW, THE LICENSOR IS NOT LIABLE FOR ANY DAMAGES
|
|
92
|
+
ARISING FROM THE USE OF THE SOFTWARE.
|
|
93
|
+
|
|
94
|
+
11. Change Date and License
|
|
95
|
+
|
|
96
|
+
After the Change Date, the licensor grants you the rights under the Change
|
|
97
|
+
License.
|
|
98
|
+
|
|
99
|
+
12. Definitions
|
|
100
|
+
|
|
101
|
+
"Compete" means providing a product or service that is substantially similar
|
|
102
|
+
to, or a replacement for, all or a significant portion of the Licensed Work.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
Apache License, Version 2.0
|
|
107
|
+
|
|
108
|
+
After the Change Date (2028-04-03), this software will be available under the
|
|
109
|
+
Apache License, Version 2.0. See https://www.apache.org/licenses/LICENSE-2.0
|
|
110
|
+
for full text.
|
package/README.md
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# @vantageos/cloud-identity
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Building blocks for multi-tenant authorization: decide which tenant a caller
|
|
4
|
+
belongs to, and keep every other tenant's rows out of the response.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
If your service holds data for several customers behind one deployment, you
|
|
7
|
+
answer the same four questions on every request — who is calling, which tenant
|
|
8
|
+
they belong to, which rows they may see, and whether their token is genuine.
|
|
9
|
+
This package answers them once, in plain functions with no framework attached,
|
|
10
|
+
so each service does not re-implement (and re-misimplement) its own version.
|
|
11
|
+
|
|
12
|
+
It depends on no web framework, router or database, and runs anywhere
|
|
13
|
+
JavaScript runs.
|
|
6
14
|
|
|
7
15
|
## Install
|
|
8
16
|
|
|
@@ -10,76 +18,198 @@ Consumed by `vantage-peers-mcp` and `@vantageos/vantage-crm-mcp` — provides th
|
|
|
10
18
|
npm install @vantageos/cloud-identity
|
|
11
19
|
```
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
Node 18 or later. ESM only.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
| `@vantageos/cloud-identity/crypto` | `timingSafeEqual` | Constant-time byte comparison (no branch-timing leak). |
|
|
18
|
-
| `@vantageos/cloud-identity/scope-filter` | `passesScopeFilter`, `scopeFilterList`, `scopeFilterGet` | Row-level visibility filter on `(createdBy, namespace)` using the caller's OAuth scope. |
|
|
19
|
-
| `@vantageos/cloud-identity/bearer-validation` | `validateMasterBearer` | Bearer-token parsing + sha256 constant-time match against a configured master secret. |
|
|
20
|
-
| `@vantageos/cloud-identity/types` | `OAuthCtx`, `ScopeProfile`, `NamespacePrefix`, `FromAllowListEntry`, `ValidateMasterBearerResult` | Public type surface. |
|
|
25
|
+
```js
|
|
26
|
+
import { requireTenantId, scopeFilterList } from "@vantageos/cloud-identity";
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
// 1. Which tenant is this caller in? Throws if the answer is "none".
|
|
29
|
+
const tenantId = requireTenantId({
|
|
30
|
+
kind: "session",
|
|
31
|
+
identity: { userId: "user_123", orgId: "org_abc" },
|
|
32
|
+
});
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
// 2. Which of these rows may they see?
|
|
35
|
+
const ctx = {
|
|
36
|
+
fromAllowList: ["reports"],
|
|
37
|
+
namespaceReadPrefixes: ["team/finance"],
|
|
38
|
+
namespaceWritePrefixes: [],
|
|
39
|
+
};
|
|
25
40
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
41
|
+
const rows = [
|
|
42
|
+
{ createdBy: "reports", namespace: "team/finance" }, // kept
|
|
43
|
+
{ createdBy: "someone-else", namespace: "team/legal" }, // dropped
|
|
44
|
+
];
|
|
30
45
|
|
|
31
|
-
|
|
46
|
+
console.log(tenantId); // "org_abc"
|
|
47
|
+
console.log(scopeFilterList(ctx, rows)); // only the first row
|
|
48
|
+
```
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
50
|
+
Every symbol is available from the package root. Subpath imports
|
|
51
|
+
(`@vantageos/cloud-identity/scope-filter` and friends) also work if you prefer
|
|
52
|
+
to be explicit about where something comes from.
|
|
36
53
|
|
|
37
|
-
##
|
|
54
|
+
## Both caller paths, one contract
|
|
38
55
|
|
|
39
|
-
|
|
56
|
+
A right is never granted by an absent argument. `requireTenantId` refuses by
|
|
57
|
+
default — a missing session, a missing organization, or a missing
|
|
58
|
+
bearer-resolved workspace all throw; there is no branch that falls through to
|
|
59
|
+
"full access" when tenant information is absent. The same function resolves
|
|
60
|
+
both entry paths a VantagePeers Cloud request can arrive on:
|
|
40
61
|
|
|
41
62
|
```ts
|
|
42
|
-
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
import {
|
|
64
|
+
requireTenantId,
|
|
65
|
+
decodeUnverifiedBearer,
|
|
66
|
+
type TenantSource,
|
|
67
|
+
} from "@vantageos/cloud-identity";
|
|
68
|
+
|
|
69
|
+
async function resolveTenant(req: {
|
|
70
|
+
session?: { orgId?: string | null } | null;
|
|
71
|
+
bearerToken?: string;
|
|
72
|
+
}): Promise<string> {
|
|
73
|
+
const source: TenantSource = req.session
|
|
74
|
+
? { kind: "session", identity: req.session }
|
|
75
|
+
: {
|
|
76
|
+
// decodeUnverifiedBearer only decodes the shape — verify the token's
|
|
77
|
+
// signature (or look it up against your own store) before trusting
|
|
78
|
+
// any field on the payload. See "What this package does not do".
|
|
79
|
+
kind: "bearer",
|
|
80
|
+
context: await decodeUnverifiedBearer(req.bearerToken!),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Throws for BOTH paths if no tenant is attached — never returns a
|
|
84
|
+
// "default" tenant id when one is missing.
|
|
85
|
+
return requireTenantId(source);
|
|
48
86
|
}
|
|
49
|
-
```
|
|
50
87
|
|
|
51
|
-
|
|
88
|
+
// Human path: a signed-in user with an active organization.
|
|
89
|
+
await resolveTenant({ session: { orgId: "org_abc" } }); // -> "org_abc"
|
|
52
90
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
import type { OAuthCtx } from "@vantageos/cloud-identity/types";
|
|
91
|
+
// Machine path: a caller presenting an already-resolved bearer token.
|
|
92
|
+
await resolveTenant({ bearerToken: someBase64Token }); // -> the token's workspaceId
|
|
56
93
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
namespaceReadPrefixes: ["orchestrator/alpha"],
|
|
60
|
-
namespaceWritePrefixes: [],
|
|
61
|
-
};
|
|
62
|
-
const visible = scopeFilterList(ctx, rowsFromConvex);
|
|
94
|
+
// Either path with nothing attached throws instead of granting access.
|
|
95
|
+
await resolveTenant({ session: null }); // throws Error("Unauthenticated: no session.")
|
|
63
96
|
```
|
|
64
97
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
98
|
+
## What each function does
|
|
99
|
+
|
|
100
|
+
**Tenant resolution and membership**
|
|
101
|
+
|
|
102
|
+
- `requireTenantId(source)` — returns the caller's tenant id, or throws if
|
|
103
|
+
there is none. It accepts both entry paths behind one contract:
|
|
104
|
+
`{ kind: "session", identity }` for a signed-in human, and
|
|
105
|
+
`{ kind: "bearer", context }` for a machine caller. A missing session, a
|
|
106
|
+
missing organization and an empty organization id are all refusals, never a
|
|
107
|
+
default.
|
|
108
|
+
- `getEffectiveTenantId(ctx, args)` — resolves which tenant a request should
|
|
109
|
+
act on when a caller may legitimately act on more than one.
|
|
110
|
+
|
|
111
|
+
**Row filtering**
|
|
112
|
+
|
|
113
|
+
- `passesScopeFilter(ctx, row)` — true when this row is visible to this
|
|
114
|
+
caller.
|
|
115
|
+
- `scopeFilterList(ctx, rows)` — the subset of rows the caller may see.
|
|
116
|
+
- `scopeFilterGet(ctx, row)` — the row, or `null` if the caller may not see it.
|
|
117
|
+
- `isMasterScope(ctx)` / `isWildcardScope(ctx)` — true when the caller holds
|
|
118
|
+
unrestricted access. Useful for skipping filtering you know is pointless;
|
|
119
|
+
never as a substitute for it.
|
|
120
|
+
|
|
121
|
+
Visibility is granted two ways: the row's `createdBy` appears in the caller's
|
|
122
|
+
`fromAllowList`, or the row's `namespace` sits under one of the caller's
|
|
123
|
+
`namespaceReadPrefixes`. Prefixes match on a path boundary, so a caller allowed
|
|
124
|
+
`team/finance` does not thereby see `team/finance-archive`.
|
|
125
|
+
|
|
126
|
+
**Token validation**
|
|
127
|
+
|
|
128
|
+
- `validateMasterBearer(header, secret)` — compares an `Authorization: Bearer`
|
|
129
|
+
header against a shared secret in constant time, and reports *why* it failed:
|
|
130
|
+
header absent, header malformed, or value mismatched.
|
|
131
|
+
- `timingSafeEqual(a, b)` — constant-time byte comparison, if you need to build
|
|
132
|
+
your own check.
|
|
133
|
+
|
|
134
|
+
**Shapes**
|
|
135
|
+
|
|
136
|
+
Zod schemas and their inferred TypeScript types for workspaces, members,
|
|
137
|
+
roles and tenant context: `workspaceSchema` (type `Workspace`),
|
|
138
|
+
`workspaceMemberSchema` (type `WorkspaceMember`), `workspaceRoleSchema`
|
|
139
|
+
(type `WorkspaceRole`), `tenantContextSchema` (type `TenantContext`) — plus
|
|
140
|
+
`ScopeViolationError` (payload type `ScopeViolationPayload`), for refusals you
|
|
141
|
+
want to catch by type, and `BearerPayload`, the type returned by
|
|
142
|
+
`decodeUnverifiedBearer`.
|
|
143
|
+
|
|
144
|
+
**Supporting types** (no runtime code — import with `import type`)
|
|
145
|
+
|
|
146
|
+
- `OAuthCtx` — the context object every scope-filter function above requires:
|
|
147
|
+
`{ fromAllowList, namespaceReadPrefixes, namespaceWritePrefixes, scope? }`.
|
|
148
|
+
- `ScopeProfile`, `NamespacePrefix`, `FromAllowListEntry` — the field types
|
|
149
|
+
that make up `OAuthCtx`.
|
|
150
|
+
- `ValidateMasterBearerResult` — the return type of `validateMasterBearer`.
|
|
151
|
+
- `ScopeFilterable` — the minimal row shape (`{ createdBy?, namespace? }`)
|
|
152
|
+
accepted by `passesScopeFilter` / `scopeFilterList` / `scopeFilterGet`.
|
|
153
|
+
- `SessionIdentity` — the human-path shape `requireTenantId` accepts under
|
|
154
|
+
`{ kind: "session", identity }` (see "Both caller paths, one contract").
|
|
155
|
+
- `TenantSource` — the discriminated union `requireTenantId` accepts:
|
|
156
|
+
`{ kind: "session", identity }` or `{ kind: "bearer", context }`.
|
|
157
|
+
|
|
158
|
+
## What this package does not do
|
|
159
|
+
|
|
160
|
+
Worth reading before you rely on it.
|
|
161
|
+
|
|
162
|
+
- **`decodeUnverifiedBearer` decodes a token. It does not verify one.** The
|
|
163
|
+
name is literal. It reads the payload out of a bearer token and checks its
|
|
164
|
+
shape; it does not check a signature, so the contents are whatever the caller
|
|
165
|
+
chose to put there. Treating its output as trusted is a vulnerability, not a
|
|
166
|
+
shortcut. Verify the token — a signed JWT, or a lookup against your own store
|
|
167
|
+
— before you believe any field in it.
|
|
168
|
+
- **This is not an authentication system.** There is no user store, no login,
|
|
169
|
+
no session issuance. It takes an identity you have already established and
|
|
170
|
+
tells you what that identity may reach.
|
|
171
|
+
- **It does not talk to your database.** `scopeFilterList` filters rows you
|
|
172
|
+
have already fetched. If fetching them was itself expensive or unsafe, filter
|
|
173
|
+
earlier, in your query.
|
|
174
|
+
|
|
175
|
+
## Upgrading to 0.3.0
|
|
176
|
+
|
|
177
|
+
**This release changes a default, and it breaks compilation on purpose.**
|
|
178
|
+
|
|
179
|
+
Before 0.3.0, calling a scope-filter function without an authorization context
|
|
180
|
+
returned `true` — no context meant full access. A caller who simply forgot to
|
|
181
|
+
pass the context received every row, and no warning. From 0.3.0 the context is
|
|
182
|
+
required: omitting it is a type error, and passing `null` or `undefined`
|
|
183
|
+
throws.
|
|
184
|
+
|
|
185
|
+
The breakage is deliberate, and it is the point. A caller that stops compiling
|
|
186
|
+
is a caller that has been told. A caller that still compiles and quietly
|
|
187
|
+
returns different rows is an incident nobody attributes to the upgrade.
|
|
188
|
+
|
|
189
|
+
What to do:
|
|
190
|
+
|
|
191
|
+
- If the call site has a real authorization context, pass it. This is almost
|
|
192
|
+
always the right fix, and the context is usually already in scope.
|
|
193
|
+
- If the call site genuinely means "unrestricted", pass the exported constant
|
|
194
|
+
`LEGACY_WILDCARD_CTX` by name. It reproduces the old behaviour exactly. It
|
|
195
|
+
has to be written out, because an intentional bypass belongs in a code review
|
|
196
|
+
and an accidental one should not be possible.
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
import { scopeFilterList, LEGACY_WILDCARD_CTX } from "@vantageos/cloud-identity";
|
|
200
|
+
|
|
201
|
+
scopeFilterList(LEGACY_WILDCARD_CTX, rows); // explicit, unrestricted
|
|
77
202
|
```
|
|
78
203
|
|
|
79
|
-
|
|
204
|
+
`requireTenantId` is also new in 0.3.0. Nothing existing calls it, so it breaks
|
|
205
|
+
nothing.
|
|
80
206
|
|
|
81
|
-
|
|
207
|
+
Version history is in [CHANGELOG.md](./CHANGELOG.md).
|
|
82
208
|
|
|
83
209
|
## License
|
|
84
210
|
|
|
85
|
-
|
|
211
|
+
FSL-1.1-Apache-2.0. See [LICENSE](./LICENSE).
|
|
212
|
+
|
|
213
|
+
## Issues
|
|
214
|
+
|
|
215
|
+
https://github.com/vantageos-agency/cloud-identity/issues
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Master-token Bearer validation.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from
|
|
5
|
-
*
|
|
6
|
-
* HARDENED per @vantageos/cloud-identity 0.1.0 contract:
|
|
4
|
+
* Extracted from an earlier in-tree master-gate middleware and HARDENED per
|
|
5
|
+
* this package's contract:
|
|
7
6
|
* - SHA-256 hash both the presented token AND the configured master secret,
|
|
8
7
|
* then compare the two digests with `timingSafeEqual` (constant time).
|
|
9
8
|
* - The original middleware did a direct `token !== masterToken` string
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bearer-validation.d.ts","sourceRoot":"","sources":["../src/bearer-validation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bearer-validation.d.ts","sourceRoot":"","sources":["../src/bearer-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAU7D;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,0BAA0B,CAAC,CAsBrC"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Master-token Bearer validation.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from
|
|
5
|
-
*
|
|
6
|
-
* HARDENED per @vantageos/cloud-identity 0.1.0 contract:
|
|
4
|
+
* Extracted from an earlier in-tree master-gate middleware and HARDENED per
|
|
5
|
+
* this package's contract:
|
|
7
6
|
* - SHA-256 hash both the presented token AND the configured master secret,
|
|
8
7
|
* then compare the two digests with `timingSafeEqual` (constant time).
|
|
9
8
|
* - The original middleware did a direct `token !== masterToken` string
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bearer-validation.js","sourceRoot":"","sources":["../src/bearer-validation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bearer-validation.js","sourceRoot":"","sources":["../src/bearer-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,MAAM,mBAAmB,GAAG,SAAS,CAAC;AAEtC,KAAK,UAAU,WAAW,CAAC,KAAa;IACtC,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC3D,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,UAA8B,EAC9B,YAAoB;IAEpB,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,EAAE,EAAE,CAAC;QACzE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACzC,CAAC;IACD,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAC3C,CAAC;IACD,sEAAsE;IACtE,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;IACvC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC3C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAC3C,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAClE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAC3C,CAAC;IACD,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACpD,WAAW,CAAC,KAAK,CAAC;QAClB,WAAW,CAAC,YAAY,CAAC;KAC1B,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,MAAM,eAAe,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IAC5D,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAC9D,CAAC"}
|
package/dist/crypto.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Constant-time byte-array comparison.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the helper is framework-agnostic and reusable by any caller (hash bytes,
|
|
8
|
-
* raw token bytes, HMAC tags, etc.).
|
|
4
|
+
* Takes `(Uint8Array, Uint8Array)` rather than strings so the helper stays
|
|
5
|
+
* framework-agnostic and reusable by any caller: hash bytes, raw token
|
|
6
|
+
* bytes, HMAC tags.
|
|
9
7
|
*
|
|
10
8
|
* Algorithm (identical to source):
|
|
11
9
|
* 1. Length mismatch → still run a dummy Web Crypto HMAC over equal-length
|
package/dist/crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CACnC,CAAC,EAAE,UAAU,EACb,CAAC,EAAE,UAAU,GACZ,OAAO,CAAC,OAAO,CAAC,CAoBlB"}
|
package/dist/crypto.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Constant-time byte-array comparison.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the helper is framework-agnostic and reusable by any caller (hash bytes,
|
|
8
|
-
* raw token bytes, HMAC tags, etc.).
|
|
4
|
+
* Takes `(Uint8Array, Uint8Array)` rather than strings so the helper stays
|
|
5
|
+
* framework-agnostic and reusable by any caller: hash bytes, raw token
|
|
6
|
+
* bytes, HMAC tags.
|
|
9
7
|
*
|
|
10
8
|
* Algorithm (identical to source):
|
|
11
9
|
* 1. Length mismatch → still run a dummy Web Crypto HMAC over equal-length
|
package/dist/crypto.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,CAAa,EACb,CAAa;IAEb,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,6EAA6E;QAC7E,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACxC,KAAK,EACL,WAAsC,EACtC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;QACF,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAgC,CAAC,CAAC;QACzE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vantageos/cloud-identity — public surface.
|
|
3
3
|
*
|
|
4
|
-
* Framework-agnostic
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Framework-agnostic building blocks for multi-tenant authorization:
|
|
5
|
+
* tenant resolution, membership requirement, row filtering and bearer
|
|
6
|
+
* validation.
|
|
7
7
|
*/
|
|
8
8
|
export { timingSafeEqual } from "./crypto.js";
|
|
9
|
-
export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, type ScopeFilterable, } from "./scope-filter.js";
|
|
9
|
+
export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, LEGACY_WILDCARD_CTX, type ScopeFilterable, } from "./scope-filter.js";
|
|
10
10
|
export { validateMasterBearer } from "./bearer-validation.js";
|
|
11
11
|
export type { OAuthCtx, ScopeProfile, NamespacePrefix, FromAllowListEntry, ValidateMasterBearerResult, } from "./types.js";
|
|
12
|
+
export { workspaceSchema, workspaceMemberSchema, workspaceRoleSchema, tenantContextSchema, ScopeViolationError, getEffectiveTenantId, decodeUnverifiedBearer, } from "./tenancy-domain.js";
|
|
13
|
+
export type { Workspace, WorkspaceMember, WorkspaceRole, TenantContext, ScopeViolationPayload, BearerPayload, } from "./tenancy-domain.js";
|
|
14
|
+
export { requireTenantId } from "./org-guard.js";
|
|
15
|
+
export type { SessionIdentity, TenantSource } from "./org-guard.js";
|
|
12
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vantageos/cloud-identity — public surface.
|
|
3
3
|
*
|
|
4
|
-
* Framework-agnostic
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Framework-agnostic building blocks for multi-tenant authorization:
|
|
5
|
+
* tenant resolution, membership requirement, row filtering and bearer
|
|
6
|
+
* validation.
|
|
7
7
|
*/
|
|
8
8
|
export { timingSafeEqual } from "./crypto.js";
|
|
9
|
-
export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, } from "./scope-filter.js";
|
|
9
|
+
export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, LEGACY_WILDCARD_CTX, } from "./scope-filter.js";
|
|
10
10
|
export { validateMasterBearer } from "./bearer-validation.js";
|
|
11
|
+
// 0.2.0 — Domain-tenancy layer (additive)
|
|
12
|
+
export { workspaceSchema, workspaceMemberSchema, workspaceRoleSchema, tenantContextSchema, ScopeViolationError, getEffectiveTenantId, decodeUnverifiedBearer, } from "./tenancy-domain.js";
|
|
13
|
+
// Organization-membership guard (unified session + bearer contract)
|
|
14
|
+
export { requireTenantId } from "./org-guard.js";
|
|
11
15
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAS9D,0CAA0C;AAC1C,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAU7B,oEAAoE;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|