@jini-ai/cms 0.3.10 → 0.4.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 +59 -0
- package/README.md +47 -19
- package/dist/content-types/agent-tools.d.ts +3 -56
- package/dist/content-types/agent-tools.js +22 -6
- package/dist/content-types/cleanup.d.ts +18 -13
- package/dist/content-types/cleanup.js +16 -11
- package/dist/content-types/errors.d.ts +57 -24
- package/dist/content-types/errors.js +56 -39
- package/dist/content-types/field-defs.d.ts +8 -4
- package/dist/content-types/field-defs.js +19 -14
- package/dist/content-types/index-provisioning.d.ts +61 -47
- package/dist/content-types/index-provisioning.js +90 -66
- package/dist/content-types/index.d.ts +4 -3
- package/dist/content-types/lifecycle-dispatch.d.ts +11 -5
- package/dist/content-types/lifecycle-dispatch.js +5 -4
- package/dist/content-types/lifecycle.d.ts +23 -18
- package/dist/content-types/lifecycle.js +71 -64
- package/dist/content-types/repo.memory.d.ts +5 -4
- package/dist/content-types/repo.memory.js +3 -2
- package/dist/content-types/tool-registrations.d.ts +6 -8
- package/dist/content-types/tool-registrations.js +24 -28
- package/dist/content-types/types.d.ts +17 -13
- package/dist/content-types/types.js +11 -5
- package/dist/content-types/write-service.d.ts +20 -22
- package/dist/content-types/write-service.js +78 -75
- package/dist/core/authorization.d.ts +15 -0
- package/dist/core/authorization.js +2 -0
- package/dist/core/commands/change-set.d.ts +15 -7
- package/dist/core/commands/command.d.ts +45 -26
- package/dist/core/commands/command.js +73 -63
- package/dist/core/entity-liveness.d.ts +11 -7
- package/dist/core/entity-liveness.js +5 -4
- package/dist/core/index.d.ts +12 -5
- package/dist/core/index.js +10 -4
- package/dist/core/ports.d.ts +25 -24
- package/dist/core/ports.js +1 -0
- package/dist/core/tools/args.d.ts +49 -0
- package/dist/core/tools/args.js +46 -0
- package/dist/core/tools/index.d.ts +3 -0
- package/dist/core/tools/index.js +3 -0
- package/dist/entries/agent-tools.d.ts +1 -73
- package/dist/entries/agent-tools.js +19 -13
- package/dist/entries/errors.d.ts +28 -14
- package/dist/entries/errors.js +17 -11
- package/dist/entries/field-validation.d.ts +5 -3
- package/dist/entries/field-validation.js +7 -5
- package/dist/entries/index.d.ts +2 -2
- package/dist/entries/lifecycle-dispatch.d.ts +5 -2
- package/dist/entries/lifecycle-dispatch.js +2 -1
- package/dist/entries/list.d.ts +4 -2
- package/dist/entries/list.js +4 -3
- package/dist/entries/repo.memory.d.ts +12 -10
- package/dist/entries/repo.memory.js +11 -10
- package/dist/entries/tool-registrations.d.ts +6 -8
- package/dist/entries/tool-registrations.js +42 -41
- package/dist/entries/types.d.ts +0 -7
- package/dist/entries/write-service.d.ts +28 -29
- package/dist/entries/write-service.js +114 -105
- package/dist/http/settings/cms-adapter.d.ts +20 -0
- package/dist/http/settings/cms-adapter.js +124 -0
- package/dist/http/settings/contracts.d.ts +135 -0
- package/dist/http/settings/contracts.js +2 -0
- package/dist/http/settings/http.d.ts +10 -0
- package/dist/http/settings/http.js +211 -0
- package/dist/http/settings/index.d.ts +5 -0
- package/dist/http/settings/index.js +4 -0
- package/dist/http/settings/shared.d.ts +68 -0
- package/dist/http/settings/shared.js +54 -0
- package/dist/http/settings/stream.d.ts +24 -0
- package/dist/http/settings/stream.js +147 -0
- package/dist/media/agent-tools.d.ts +1 -66
- package/dist/media/agent-tools.js +5 -1
- package/dist/media/blob-gc-lock.d.ts +6 -2
- package/dist/media/blob-gc-lock.js +4 -2
- package/dist/media/blob-gc.d.ts +64 -51
- package/dist/media/blob-gc.js +62 -52
- package/dist/media/blob-key.d.ts +1 -1
- package/dist/media/content-type-sniffer.d.ts +3 -1
- package/dist/media/content-type-sniffer.js +1 -1
- package/dist/media/html-attributes.d.ts +9 -5
- package/dist/media/html-attributes.js +8 -6
- package/dist/media/image-transformer.js +1 -1
- package/dist/media/image-transformer.sharp.d.ts +6 -0
- package/dist/media/image-transformer.sharp.js +12 -6
- package/dist/media/import/fetch-image.d.ts +24 -0
- package/dist/media/import/fetch-image.js +137 -0
- package/dist/media/import/index.d.ts +3 -0
- package/dist/media/import/index.js +3 -0
- package/dist/media/import/ports.d.ts +38 -0
- package/dist/media/index.d.ts +7 -1
- package/dist/media/index.js +1 -0
- package/dist/media/media-service.d.ts +14 -10
- package/dist/media/media-service.js +57 -52
- package/dist/media/ports.d.ts +3 -2
- package/dist/media/rendition-service.d.ts +3 -3
- package/dist/media/rendition-service.js +38 -37
- package/dist/media/repo.memory.d.ts +18 -7
- package/dist/media/repo.memory.js +12 -6
- package/dist/media/tool-registrations.d.ts +16 -15
- package/dist/media/tool-registrations.js +84 -72
- package/dist/media/transform-lock.d.ts +22 -12
- package/dist/media/transform-lock.js +14 -13
- package/dist/media/transform-registry.d.ts +3 -3
- package/dist/media/transform-registry.js +19 -18
- package/dist/media/transform-types.d.ts +8 -3
- package/dist/media/transform-types.js +10 -5
- package/dist/media/types.d.ts +21 -4
- package/dist/media/types.js +15 -2
- package/dist/navigation/agent-tools.d.ts +1 -80
- package/dist/navigation/agent-tools.js +15 -3
- package/dist/navigation/contracts.d.ts +1 -1
- package/dist/navigation/index.d.ts +2 -2
- package/dist/navigation/menu-service.d.ts +57 -40
- package/dist/navigation/menu-service.js +80 -69
- package/dist/navigation/ports.d.ts +1 -1
- package/dist/navigation/read-model.d.ts +3 -1
- package/dist/navigation/read-model.js +2 -2
- package/dist/navigation/reconcile.d.ts +2 -2
- package/dist/navigation/reconcile.js +2 -1
- package/dist/navigation/repo.memory.d.ts +7 -3
- package/dist/navigation/repo.memory.js +4 -2
- package/dist/navigation/resolver.d.ts +4 -1
- package/dist/navigation/resolver.js +1 -1
- package/dist/navigation/tool-registrations.d.ts +6 -8
- package/dist/navigation/tool-registrations.js +44 -42
- package/dist/navigation/types.d.ts +1 -1
- package/dist/presentation/presentation.d.ts +11 -3
- package/dist/presentation/presentation.js +13 -6
- package/dist/presentation/repo.memory.d.ts +6 -2
- package/dist/presentation/repo.memory.js +3 -2
- package/dist/settings/agent-tools.d.ts +2 -75
- package/dist/settings/agent-tools.js +49 -45
- package/dist/settings/agent-value-write-tools.d.ts +22 -0
- package/dist/settings/agent-value-write-tools.js +154 -0
- package/dist/settings/agent-writable-preferences.js +8 -20
- package/dist/settings/agent-write-denylist.d.ts +19 -0
- package/dist/settings/agent-write-denylist.js +15 -0
- package/dist/settings/change-feed.d.ts +11 -2
- package/dist/settings/change-feed.js +4 -4
- package/dist/settings/definitions-dispatch.d.ts +8 -3
- package/dist/settings/definitions-dispatch.js +5 -5
- package/dist/settings/dictionaries/index.d.ts +5 -2
- package/dist/settings/dictionaries/index.js +2 -2
- package/dist/settings/dictionaries/settings-dialog.ar.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.bn.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.de.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.es.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.fa.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.fr.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.hi.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.hu.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.id.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.it.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ja.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ko.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.pl.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ru.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.th.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.tr.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.uk.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ur.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +0 -1
- package/dist/settings/ensure-definitions.d.ts +5 -5
- package/dist/settings/errors.d.ts +7 -1
- package/dist/settings/errors.js +7 -1
- package/dist/settings/index.d.ts +4 -1
- package/dist/settings/index.js +1 -0
- package/dist/settings/ports.d.ts +6 -5
- package/dist/settings/principal-lookup.d.ts +9 -0
- package/dist/settings/principal-lookup.js +2 -0
- package/dist/settings/purge-service.d.ts +5 -4
- package/dist/settings/purge-service.js +3 -2
- package/dist/settings/settings.d.ts +22 -7
- package/dist/settings/settings.js +47 -26
- package/dist/settings/tool-registrations.d.ts +25 -32
- package/dist/settings/tool-registrations.js +66 -56
- package/dist/settings/types.d.ts +1 -1
- package/dist/settings/ui-tab-definitions.d.ts +1 -1
- package/dist/settings/write-service.d.ts +32 -26
- package/dist/settings/write-service.js +26 -20
- package/dist/taxonomy/agent-tools.d.ts +1 -66
- package/dist/taxonomy/agent-tools.js +25 -15
- package/dist/taxonomy/content-lookup.js +1 -1
- package/dist/taxonomy/index.d.ts +2 -2
- package/dist/taxonomy/merge-term.d.ts +7 -4
- package/dist/taxonomy/merge-term.js +7 -5
- package/dist/taxonomy/repo.memory.d.ts +42 -19
- package/dist/taxonomy/repo.memory.js +14 -12
- package/dist/taxonomy/validation-chain.d.ts +39 -18
- package/dist/taxonomy/validation-chain.js +35 -25
- package/dist/taxonomy/write-service.d.ts +106 -45
- package/dist/taxonomy/write-service.js +221 -179
- package/dist/trash/adapters/record-store.d.ts +52 -0
- package/dist/trash/adapters/record-store.js +36 -0
- package/dist/trash/cursor.d.ts +20 -0
- package/dist/trash/cursor.js +36 -0
- package/dist/trash/follow-ups.d.ts +76 -0
- package/dist/trash/follow-ups.js +46 -0
- package/dist/trash/index.d.ts +8 -0
- package/dist/trash/index.js +8 -0
- package/dist/trash/ports.d.ts +308 -0
- package/dist/trash/ports.js +2 -0
- package/dist/trash/repo.memory.d.ts +50 -0
- package/dist/trash/repo.memory.js +114 -0
- package/dist/trash/sweeper.d.ts +77 -0
- package/dist/trash/sweeper.js +127 -0
- package/dist/trash/write-service.d.ts +100 -0
- package/dist/trash/write-service.js +242 -0
- package/dist/workspace/agent-tools.d.ts +1 -59
- package/dist/workspace/agent-tools.js +27 -12
- package/dist/workspace/create.d.ts +31 -6
- package/dist/workspace/create.js +18 -5
- package/dist/workspace/delete.d.ts +18 -13
- package/dist/workspace/delete.js +20 -12
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/repo.memory.d.ts +19 -4
- package/dist/workspace/repo.memory.js +26 -4
- package/dist/workspace/tool-registrations.d.ts +2 -2
- package/dist/workspace/tool-registrations.js +13 -13
- package/dist/workspace/update.d.ts +6 -5
- package/dist/workspace/update.js +9 -8
- package/package.json +44 -38
- package/dist/identity/admin-crud-service.d.ts +0 -280
- package/dist/identity/admin-crud-service.js +0 -468
- package/dist/identity/agent-tool-input.d.ts +0 -58
- package/dist/identity/agent-tool-input.js +0 -126
- package/dist/identity/agent-tools.d.ts +0 -101
- package/dist/identity/agent-tools.js +0 -290
- package/dist/identity/auth-service.d.ts +0 -118
- package/dist/identity/auth-service.js +0 -170
- package/dist/identity/authorize.d.ts +0 -90
- package/dist/identity/authorize.js +0 -94
- package/dist/identity/grant-service.d.ts +0 -198
- package/dist/identity/grant-service.js +0 -361
- package/dist/identity/hasher.d.ts +0 -39
- package/dist/identity/hasher.js +0 -39
- package/dist/identity/index.d.ts +0 -62
- package/dist/identity/index.js +0 -54
- package/dist/identity/password-policy.d.ts +0 -57
- package/dist/identity/password-policy.js +0 -66
- package/dist/identity/permission-migrations.d.ts +0 -86
- package/dist/identity/permission-migrations.js +0 -67
- package/dist/identity/permissions.d.ts +0 -35
- package/dist/identity/permissions.js +0 -603
- package/dist/identity/ports.d.ts +0 -201
- package/dist/identity/repo.memory.d.ts +0 -164
- package/dist/identity/repo.memory.js +0 -224
- package/dist/identity/seed.d.ts +0 -54
- package/dist/identity/seed.js +0 -429
- package/dist/identity/tool-registrations.d.ts +0 -50
- package/dist/identity/tool-registrations.js +0 -419
- package/dist/identity/types.d.ts +0 -185
- package/dist/identity/types.js +0 -64
- package/dist/identity/username.d.ts +0 -12
- package/dist/identity/username.js +0 -14
- /package/dist/{identity → media/import}/ports.js +0 -0
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
-
import { resolveEffectivePermissions } from "./authorize.js";
|
|
3
|
-
import { AuthInvalidCredentialsError } from "./types.js";
|
|
4
|
-
import { normalizeUsername } from "./username.js";
|
|
5
|
-
/**
|
|
6
|
-
* @file Login / logout / session validation (REQ-06, state.spec `LOGIN`/`LOGOUT`).
|
|
7
|
-
*
|
|
8
|
-
* Purpose:
|
|
9
|
-
* The credential-verification + session-lifecycle surface `middleware/dev-auth.ts`
|
|
10
|
-
* wires into HTTP. Session tokens are hashed with SHA-256 before storage — the
|
|
11
|
-
* same technique `members/write-service.ts` uses for magic-link/member-session
|
|
12
|
-
* tokens (INV-05: the raw token is never persisted). Password verification is
|
|
13
|
-
* argon2id (`hasher.ts`), never a raw comparison.
|
|
14
|
-
*
|
|
15
|
-
* `createSessionForPrincipal` (2026-09-06) is the one session minter; `login()` calls it after
|
|
16
|
-
* verifying credentials, and a host that identifies a principal by some other means (e.g. a
|
|
17
|
-
* loopback-only single-use boot token, which has nothing to verify a password against) calls it
|
|
18
|
-
* directly instead of re-implementing session construction. Before this export existed, a
|
|
19
|
-
* downstream host had duplicated this file's private `hashToken` byte-for-byte because
|
|
20
|
-
* `login()` was the only minter and hard-requires a password — this closes that fork.
|
|
21
|
-
*
|
|
22
|
-
* Architectural role:
|
|
23
|
-
* Ordinary core functions (like `updatePost`/`membersWriteService`), not a
|
|
24
|
-
* port — session/credential logic has one implementation.
|
|
25
|
-
*/
|
|
26
|
-
/** RT-004/behavior.spec §4: absolute session lifetime, fixed at creation. */
|
|
27
|
-
export const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
28
|
-
/** SHA-256 hex digest of a raw bearer token (mirrors `members/write-service.ts`'s `hashToken`). */
|
|
29
|
-
function hashToken(rawToken) {
|
|
30
|
-
return createHash("sha256").update(rawToken).digest("hex");
|
|
31
|
-
}
|
|
32
|
-
function newRawToken() {
|
|
33
|
-
return randomBytes(32).toString("hex");
|
|
34
|
-
}
|
|
35
|
-
function isoPlusMs(nowIso, ms) {
|
|
36
|
-
return new Date(new Date(nowIso).getTime() + ms).toISOString();
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Mint and persist a session for a principal the caller has ALREADY identified by some means
|
|
40
|
-
* other than this function (password verification in `login()` below, or a caller-specific proof
|
|
41
|
-
* such as a loopback-only single-use boot token). Factored out of `login()`'s former inline
|
|
42
|
-
* session-construction block (2026-09-06) so both paths share one minter — a host with no
|
|
43
|
-
* password to verify no longer has to hand-roll its own token hashing (and risk drifting from
|
|
44
|
-
* this file's private `hashToken`) just to mint a session for a principal it has already proven
|
|
45
|
-
* by other means.
|
|
46
|
-
*
|
|
47
|
-
* @complexity O(1) — one session write.
|
|
48
|
-
* @overallScore 100
|
|
49
|
-
*/
|
|
50
|
-
export async function createSessionForPrincipal(required) {
|
|
51
|
-
const { deps, input } = required;
|
|
52
|
-
const nowIso = deps.clock.nowIso();
|
|
53
|
-
const rawToken = newRawToken();
|
|
54
|
-
const session = {
|
|
55
|
-
id: deps.idGen.newId(),
|
|
56
|
-
workspaceId: input.workspaceId,
|
|
57
|
-
principalId: input.principalId,
|
|
58
|
-
tokenHash: hashToken(rawToken),
|
|
59
|
-
createdAt: nowIso,
|
|
60
|
-
expiresAt: isoPlusMs(nowIso, SESSION_TTL_MS),
|
|
61
|
-
ip: input.ip,
|
|
62
|
-
userAgent: input.userAgent,
|
|
63
|
-
};
|
|
64
|
-
await deps.repos.sessions.save(session);
|
|
65
|
-
return { session, rawToken };
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Verify `username`+`password` for an `active` principal and mint a session
|
|
69
|
-
* (AC-02, REQ-06). Constant-shaped failure: a nonexistent username, a
|
|
70
|
-
* disabled principal, and a wrong password are all `AuthInvalidCredentialsError`
|
|
71
|
-
* with the same message — no user-enumeration signal.
|
|
72
|
-
*
|
|
73
|
-
* @complexity O(1) — one username lookup, one principal lookup, one hash
|
|
74
|
-
* verify, one session write (via `createSessionForPrincipal`), one `lastLoginAt` write.
|
|
75
|
-
* @overallScore 100
|
|
76
|
-
*/
|
|
77
|
-
export async function login(required) {
|
|
78
|
-
const { deps, input } = required;
|
|
79
|
-
const username = normalizeUsername(input.username);
|
|
80
|
-
if (!username || !input.password) {
|
|
81
|
-
throw new AuthInvalidCredentialsError("invalid username or password");
|
|
82
|
-
}
|
|
83
|
-
const userRow = await deps.repos.users.findByUsername({ workspaceId: input.workspaceId, username });
|
|
84
|
-
if (!userRow) {
|
|
85
|
-
throw new AuthInvalidCredentialsError("invalid username or password");
|
|
86
|
-
}
|
|
87
|
-
const principal = await deps.repos.principals.findById({
|
|
88
|
-
workspaceId: input.workspaceId,
|
|
89
|
-
id: userRow.principalId,
|
|
90
|
-
});
|
|
91
|
-
if (!principal || principal.status !== "active") {
|
|
92
|
-
throw new AuthInvalidCredentialsError("invalid username or password");
|
|
93
|
-
}
|
|
94
|
-
const passwordOk = await deps.hasher.verify(userRow.passwordHash, input.password);
|
|
95
|
-
if (!passwordOk) {
|
|
96
|
-
throw new AuthInvalidCredentialsError("invalid username or password");
|
|
97
|
-
}
|
|
98
|
-
const { session, rawToken } = await createSessionForPrincipal({
|
|
99
|
-
deps,
|
|
100
|
-
input: { workspaceId: input.workspaceId, principalId: principal.id, ip: input.ip, userAgent: input.userAgent },
|
|
101
|
-
});
|
|
102
|
-
// Reuses the session's own `createdAt` rather than a second `clock.nowIso()` call — both are
|
|
103
|
-
// meant to be the same instant, and reading one avoids two clock reads ever disagreeing.
|
|
104
|
-
await deps.repos.users.save({ ...userRow, lastLoginAt: session.createdAt });
|
|
105
|
-
return { principal, session, rawToken };
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Resolve a session cookie's raw token to its principal, applying every
|
|
109
|
-
* fail-closed check server-side (EC-02/EC-03/EC-13, AC-05/AC-20): unknown
|
|
110
|
-
* token, revoked, past absolute `expiresAt`, or a disabled bound principal
|
|
111
|
-
* all resolve to `null` (the caller maps that to 401 `UNAUTHENTICATED` and
|
|
112
|
-
* never reaches `authorize()`).
|
|
113
|
-
*
|
|
114
|
-
* @complexity O(1) — one token-hash lookup, one principal lookup.
|
|
115
|
-
* @overallScore 100
|
|
116
|
-
*/
|
|
117
|
-
export async function validateSession(required) {
|
|
118
|
-
const { deps, input } = required;
|
|
119
|
-
const nowIso = input.nowIso ?? deps.clock.nowIso();
|
|
120
|
-
const session = await deps.repos.sessions.findByTokenHash({
|
|
121
|
-
workspaceId: input.workspaceId,
|
|
122
|
-
tokenHash: hashToken(input.rawToken),
|
|
123
|
-
});
|
|
124
|
-
if (!session)
|
|
125
|
-
return null;
|
|
126
|
-
if (session.revokedAt)
|
|
127
|
-
return null;
|
|
128
|
-
// EC-13/AC-20: past absolute expiry is treated as revoked, never reaching authorize().
|
|
129
|
-
if (session.expiresAt <= nowIso)
|
|
130
|
-
return null;
|
|
131
|
-
const principal = await deps.repos.principals.findById({
|
|
132
|
-
workspaceId: input.workspaceId,
|
|
133
|
-
id: session.principalId,
|
|
134
|
-
});
|
|
135
|
-
// EC-02: a disabled principal's live session stops validating immediately.
|
|
136
|
-
if (!principal || principal.status !== "active")
|
|
137
|
-
return null;
|
|
138
|
-
return { principal, session };
|
|
139
|
-
}
|
|
140
|
-
/** Revoke the session bound to `rawToken`. Idempotent — already-revoked/unknown is a no-op. */
|
|
141
|
-
export async function logout(required) {
|
|
142
|
-
const { deps, input } = required;
|
|
143
|
-
const session = await deps.repos.sessions.findByTokenHash({
|
|
144
|
-
workspaceId: input.workspaceId,
|
|
145
|
-
tokenHash: hashToken(input.rawToken),
|
|
146
|
-
});
|
|
147
|
-
if (!session || session.revokedAt)
|
|
148
|
-
return;
|
|
149
|
-
await deps.repos.sessions.revoke({
|
|
150
|
-
workspaceId: input.workspaceId,
|
|
151
|
-
id: session.id,
|
|
152
|
-
revokedAt: deps.clock.nowIso(),
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
/** The introspection surface for `AUTH_ME` (REQ-07): dotted permission strings, `["*"]` for owner. */
|
|
156
|
-
export async function getEffectivePermissions(required) {
|
|
157
|
-
const rows = await resolveEffectivePermissions({
|
|
158
|
-
deps: {
|
|
159
|
-
principals: required.deps.principals,
|
|
160
|
-
principalRoles: required.deps.principalRoles,
|
|
161
|
-
rolePolicies: required.deps.rolePolicies,
|
|
162
|
-
principalPolicies: required.deps.principalPolicies,
|
|
163
|
-
policyPermissions: required.deps.policyPermissions,
|
|
164
|
-
},
|
|
165
|
-
principalId: required.input.principalId,
|
|
166
|
-
workspaceId: required.input.workspaceId,
|
|
167
|
-
});
|
|
168
|
-
return [...new Set(rows.map((row) => row.permission))];
|
|
169
|
-
}
|
|
170
|
-
//# sourceMappingURL=auth-service.js.map
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type { UUID } from "../core/ports.js";
|
|
2
|
-
import type { PolicyPermissionRepoPort, PrincipalPolicyRepoPort, PrincipalRepoPort, PrincipalRoleRepoPort, RolePolicyRepoPort } from "./ports.js";
|
|
3
|
-
import type { PolicyPermissionRecord } from "./types.js";
|
|
4
|
-
/**
|
|
5
|
-
* @file `authorize()` — the RBAC evaluator (REQ-04).
|
|
6
|
-
*
|
|
7
|
-
* Purpose:
|
|
8
|
-
* Ordinary core function, deliberately NOT a port (one evaluator —
|
|
9
|
-
* "no PolicyPort"). The command gateway calls this before every
|
|
10
|
-
* mutation (REQ-05); it is deterministic and side-effect free given the
|
|
11
|
-
* repo snapshot (state.spec §4).
|
|
12
|
-
*
|
|
13
|
-
* How it relates to the project:
|
|
14
|
-
* - `command.ts`'s `executeCommand` calls a bound closure of this shape
|
|
15
|
-
* (typed there as `AuthorizeFn`, kept generic so `core/commands` never
|
|
16
|
-
* imports this feature-level library — see that file's header).
|
|
17
|
-
* - Route handlers call it directly for mutations that do not flow through
|
|
18
|
-
* the command gateway (this pass: `members` disable — see the handoff).
|
|
19
|
-
*
|
|
20
|
-
* Architectural role:
|
|
21
|
-
* Implements the matcher precedence in behavior.spec §1.1 exactly:
|
|
22
|
-
* disabled-principal short-circuit > owner wildcard > exact matching row >
|
|
23
|
-
* fail-closed default. Fail-closed extensibility: a non-null
|
|
24
|
-
* `constraintJson` is never treated as unconstrained, and a `resourceType`
|
|
25
|
-
* mismatch/absence is never treated as a global grant.
|
|
26
|
-
*/
|
|
27
|
-
/** The context an authorization decision is evaluated against (REQ-04). */
|
|
28
|
-
export interface AuthorizeContext {
|
|
29
|
-
workspaceId: UUID;
|
|
30
|
-
entityType?: string;
|
|
31
|
-
entityId?: UUID;
|
|
32
|
-
}
|
|
33
|
-
/** `authorize()`'s result — always fail-closed on `allowed: false` (INV-03). */
|
|
34
|
-
export interface AuthorizeResult {
|
|
35
|
-
allowed: boolean;
|
|
36
|
-
/** Machine-readable reason: `principal_disabled | no_grant | resource_scope_mismatch | unconstrained_deny | owner_wildcard | matched`. */
|
|
37
|
-
reason: string;
|
|
38
|
-
}
|
|
39
|
-
/** Repos `authorize()`/`resolveEffectivePermissions()` read. Not a port bag with alternate adapters — see file header. */
|
|
40
|
-
export interface AuthorizeDeps {
|
|
41
|
-
principals: PrincipalRepoPort;
|
|
42
|
-
principalRoles: PrincipalRoleRepoPort;
|
|
43
|
-
rolePolicies: RolePolicyRepoPort;
|
|
44
|
-
principalPolicies: PrincipalPolicyRepoPort;
|
|
45
|
-
policyPermissions: PolicyPermissionRepoPort;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Resolve a principal's effective permission rows: the union of (a) its
|
|
49
|
-
* roles' policies' permissions and (b) policies attached directly via
|
|
50
|
-
* `principal_policies` (behavior.spec §1.2). Scoped to `workspaceId` by
|
|
51
|
-
* construction (every repo call below is workspace-scoped).
|
|
52
|
-
*
|
|
53
|
-
* @complexity O(r*p + d) round trips, r = roles held, p = policies per role,
|
|
54
|
-
* d = direct policy attachments — all expected small (a handful of rows per
|
|
55
|
-
* principal). No caller-unbounded collection is walked.
|
|
56
|
-
* @overallScore 100
|
|
57
|
-
*/
|
|
58
|
-
export declare function resolveEffectivePermissions(required: {
|
|
59
|
-
deps: AuthorizeDeps;
|
|
60
|
-
principalId: UUID;
|
|
61
|
-
workspaceId: UUID;
|
|
62
|
-
}): Promise<PolicyPermissionRecord[]>;
|
|
63
|
-
/**
|
|
64
|
-
* The RBAC decision (REQ-04). Fail-closed: `allowed` defaults to `false` and
|
|
65
|
-
* only ever flips to `true` on an explicit disabled-short-circuit-cleared,
|
|
66
|
-
* matching row.
|
|
67
|
-
*
|
|
68
|
-
* Precedence (behavior.spec §1.1, highest to lowest):
|
|
69
|
-
* 1. Disabled principal -> `false`, `principal_disabled` (overrides all grants).
|
|
70
|
-
* 2. An unconstrained owner `"*"` row -> `true`, `owner_wildcard`.
|
|
71
|
-
* 3. An exact matching row (permission + workspace + resource-scope + no
|
|
72
|
-
* constraint) -> `true`, `matched`.
|
|
73
|
-
* 4. Fail-closed default -> `false`, with the most specific diagnostic reason
|
|
74
|
-
* available among same-permission candidates (`unconstrained_deny` over
|
|
75
|
-
* `resource_scope_mismatch` over `no_grant` — an inferred, undocumented-but-
|
|
76
|
-
* reasonable tie-break; the spec only pins each reason in isolation, see
|
|
77
|
-
* AC-09/AC-14/EC-04).
|
|
78
|
-
*
|
|
79
|
-
* @complexity O(resolveEffectivePermissions) + O(k) local filtering, k = the
|
|
80
|
-
* principal's effective row count (small, bounded by grants a workspace
|
|
81
|
-
* administrator creates by hand).
|
|
82
|
-
* @overallScore 100
|
|
83
|
-
*/
|
|
84
|
-
export declare function authorize(required: {
|
|
85
|
-
deps: AuthorizeDeps;
|
|
86
|
-
principalId: UUID;
|
|
87
|
-
permission: string;
|
|
88
|
-
context: AuthorizeContext;
|
|
89
|
-
}): Promise<AuthorizeResult>;
|
|
90
|
-
//# sourceMappingURL=authorize.d.ts.map
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve a principal's effective permission rows: the union of (a) its
|
|
3
|
-
* roles' policies' permissions and (b) policies attached directly via
|
|
4
|
-
* `principal_policies` (behavior.spec §1.2). Scoped to `workspaceId` by
|
|
5
|
-
* construction (every repo call below is workspace-scoped).
|
|
6
|
-
*
|
|
7
|
-
* @complexity O(r*p + d) round trips, r = roles held, p = policies per role,
|
|
8
|
-
* d = direct policy attachments — all expected small (a handful of rows per
|
|
9
|
-
* principal). No caller-unbounded collection is walked.
|
|
10
|
-
* @overallScore 100
|
|
11
|
-
*/
|
|
12
|
-
export async function resolveEffectivePermissions(required) {
|
|
13
|
-
const { deps, principalId, workspaceId } = required;
|
|
14
|
-
const roleLinks = await deps.principalRoles.listByPrincipalId({ workspaceId, principalId });
|
|
15
|
-
const rolePermissionGroups = await Promise.all(roleLinks.map(async (roleLink) => {
|
|
16
|
-
const rolePolicyLinks = await deps.rolePolicies.listByRoleId({
|
|
17
|
-
workspaceId,
|
|
18
|
-
roleId: roleLink.roleId,
|
|
19
|
-
});
|
|
20
|
-
const policyPermissionGroups = await Promise.all(rolePolicyLinks.map((rolePolicyLink) => deps.policyPermissions.listByPolicyId({ workspaceId, policyId: rolePolicyLink.policyId })));
|
|
21
|
-
return policyPermissionGroups.flat();
|
|
22
|
-
}));
|
|
23
|
-
const directLinks = await deps.principalPolicies.listByPrincipalId({ workspaceId, principalId });
|
|
24
|
-
const directPermissionGroups = await Promise.all(directLinks.map((directLink) => deps.policyPermissions.listByPolicyId({ workspaceId, policyId: directLink.policyId })));
|
|
25
|
-
return [...rolePermissionGroups.flat(), ...directPermissionGroups.flat()];
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* True iff `row` is an exact, unconstrained-or-scope-matching grant of
|
|
29
|
-
* `permission` in `context` (state.spec §4 `matchesRow`). Assumes `row` has
|
|
30
|
-
* already passed the owner-wildcard check upstream — this never special-cases
|
|
31
|
-
* `"*"`.
|
|
32
|
-
*/
|
|
33
|
-
function matchesRow(row, permission, context) {
|
|
34
|
-
if (row.permission !== permission)
|
|
35
|
-
return false;
|
|
36
|
-
if (row.constraintJson != null)
|
|
37
|
-
return false;
|
|
38
|
-
if (row.resourceType != null && row.resourceType !== context.entityType)
|
|
39
|
-
return false;
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* The RBAC decision (REQ-04). Fail-closed: `allowed` defaults to `false` and
|
|
44
|
-
* only ever flips to `true` on an explicit disabled-short-circuit-cleared,
|
|
45
|
-
* matching row.
|
|
46
|
-
*
|
|
47
|
-
* Precedence (behavior.spec §1.1, highest to lowest):
|
|
48
|
-
* 1. Disabled principal -> `false`, `principal_disabled` (overrides all grants).
|
|
49
|
-
* 2. An unconstrained owner `"*"` row -> `true`, `owner_wildcard`.
|
|
50
|
-
* 3. An exact matching row (permission + workspace + resource-scope + no
|
|
51
|
-
* constraint) -> `true`, `matched`.
|
|
52
|
-
* 4. Fail-closed default -> `false`, with the most specific diagnostic reason
|
|
53
|
-
* available among same-permission candidates (`unconstrained_deny` over
|
|
54
|
-
* `resource_scope_mismatch` over `no_grant` — an inferred, undocumented-but-
|
|
55
|
-
* reasonable tie-break; the spec only pins each reason in isolation, see
|
|
56
|
-
* AC-09/AC-14/EC-04).
|
|
57
|
-
*
|
|
58
|
-
* @complexity O(resolveEffectivePermissions) + O(k) local filtering, k = the
|
|
59
|
-
* principal's effective row count (small, bounded by grants a workspace
|
|
60
|
-
* administrator creates by hand).
|
|
61
|
-
* @overallScore 100
|
|
62
|
-
*/
|
|
63
|
-
export async function authorize(required) {
|
|
64
|
-
const { deps, principalId, permission, context } = required;
|
|
65
|
-
const principal = await deps.principals.findById({ workspaceId: context.workspaceId, id: principalId });
|
|
66
|
-
if (!principal || principal.status === "disabled") {
|
|
67
|
-
return { allowed: false, reason: "principal_disabled" };
|
|
68
|
-
}
|
|
69
|
-
const effectiveRows = await resolveEffectivePermissions({
|
|
70
|
-
deps,
|
|
71
|
-
principalId,
|
|
72
|
-
workspaceId: context.workspaceId,
|
|
73
|
-
});
|
|
74
|
-
const hasUnconstrainedWildcard = effectiveRows.some((row) => row.permission === "*" && row.resourceType == null && row.constraintJson == null);
|
|
75
|
-
if (hasUnconstrainedWildcard) {
|
|
76
|
-
return { allowed: true, reason: "owner_wildcard" };
|
|
77
|
-
}
|
|
78
|
-
const candidates = effectiveRows.filter((row) => row.permission === permission);
|
|
79
|
-
if (candidates.length === 0) {
|
|
80
|
-
return { allowed: false, reason: "no_grant" };
|
|
81
|
-
}
|
|
82
|
-
const matched = candidates.some((row) => matchesRow(row, permission, context));
|
|
83
|
-
if (matched) {
|
|
84
|
-
return { allowed: true, reason: "matched" };
|
|
85
|
-
}
|
|
86
|
-
if (candidates.some((row) => row.constraintJson != null)) {
|
|
87
|
-
return { allowed: false, reason: "unconstrained_deny" };
|
|
88
|
-
}
|
|
89
|
-
if (candidates.some((row) => row.resourceType != null)) {
|
|
90
|
-
return { allowed: false, reason: "resource_scope_mismatch" };
|
|
91
|
-
}
|
|
92
|
-
return { allowed: false, reason: "no_grant" };
|
|
93
|
-
}
|
|
94
|
-
//# sourceMappingURL=authorize.js.map
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import type { UUID } from "../core/ports.js";
|
|
2
|
-
import { type AuthorizeDeps } from "./authorize.js";
|
|
3
|
-
import type { AuthServiceDeps } from "./auth-service.js";
|
|
4
|
-
import type { IdentityRepos } from "./ports.js";
|
|
5
|
-
import { type PolicyRecord, type PrincipalPolicyRecord, type PrincipalRecord, type PrincipalRoleRecord, type RoleRecord, type UserRecord } from "./types.js";
|
|
6
|
-
/**
|
|
7
|
-
* @file Human grant-writing transitions (state.spec.md §3): `CREATE_USER`,
|
|
8
|
-
* `CREATE_ROLE`, `CREATE_POLICY`, `ASSIGN_ROLE`, `ATTACH_POLICY`.
|
|
9
|
-
*
|
|
10
|
-
* Purpose:
|
|
11
|
-
* The only usable identity principal before this file was the seeded owner —
|
|
12
|
-
* there was no API path to create a second user or grant/change permissions.
|
|
13
|
-
* These five functions are that path, following `auth-service.ts`'s exact
|
|
14
|
-
* shape (`{ deps: AuthServiceDeps, input: {...} }`, one exported async
|
|
15
|
-
* function per transition, plain `Error` subclasses for control flow).
|
|
16
|
-
*
|
|
17
|
-
* Out of scope (deferred, see the dispatch directive): `ISSUE_API_KEY`,
|
|
18
|
-
* `CREATE_PRINCIPAL` (api_key/agent principal creation), agent/api_key
|
|
19
|
-
* delegation, `WRITE_POLICY_PERMISSION`, `DISABLE_PRINCIPAL`.
|
|
20
|
-
*
|
|
21
|
-
* Architectural role:
|
|
22
|
-
* Ordinary core functions, not ports (mirrors `auth-service.ts`'s reasoning —
|
|
23
|
-
* grant-writing logic has one implementation). Each function calls
|
|
24
|
-
* `authorize()`/`resolveEffectivePermissions()` from `authorize.ts` itself
|
|
25
|
-
* (rather than pushing the gate into the route layer, the way
|
|
26
|
-
* `members/disable.ts`'s route does) so the caller-permission gate and the
|
|
27
|
-
* INV-07 clamp are exercised directly by unit tests, no HTTP harness needed.
|
|
28
|
-
*/
|
|
29
|
-
/**
|
|
30
|
-
* Assemble the `AuthorizeDeps` bag `authorize()`/`resolveEffectivePermissions()` expect from the
|
|
31
|
-
* flat `IdentityRepos` bag. Exported so `admin-crud-service.ts`'s new transitions
|
|
32
|
-
* (`ENABLE_PRINCIPAL`/`UPDATE_USER`/`RESET_USER_PASSWORD`/`UPDATE_ROLE`/`UPDATE_POLICY`/
|
|
33
|
-
* `DELETE_ROLE`/`DELETE_POLICY`/`WRITE_POLICY_PERMISSION`/`DISABLE_PRINCIPAL`) reuse the identical
|
|
34
|
-
* gate/clamp logic rather than duplicating it — same file-split reasoning `auth-service.ts` and
|
|
35
|
-
* `grant-service.ts` already use (one shared helper set, two transition files).
|
|
36
|
-
*/
|
|
37
|
-
export declare function authorizeDepsFrom(repos: IdentityRepos): AuthorizeDeps;
|
|
38
|
-
/**
|
|
39
|
-
* Fail-closed caller-permission gate shared by every transition in this file.
|
|
40
|
-
* Throws `IdentityForbiddenError` (403 `FORBIDDEN`) unless the caller holds
|
|
41
|
-
* at least one of `permissions` (an OR gate — `CREATE_USER` accepts either
|
|
42
|
-
* `user.manage` or `member.manage`, state.spec §3).
|
|
43
|
-
*
|
|
44
|
-
* @complexity O(p) `authorize()` calls, p = `permissions.length` (1 or 2 in
|
|
45
|
-
* every caller this pass) — each itself O(resolveEffectivePermissions), see
|
|
46
|
-
* that function's doc.
|
|
47
|
-
* @overallScore 100
|
|
48
|
-
*/
|
|
49
|
-
export declare function assertCallerHasAnyPermission(required: {
|
|
50
|
-
deps: AuthServiceDeps;
|
|
51
|
-
workspaceId: UUID;
|
|
52
|
-
callerPrincipalId: UUID;
|
|
53
|
-
permissions: readonly string[];
|
|
54
|
-
}): Promise<void>;
|
|
55
|
-
/**
|
|
56
|
-
* The INV-07 grant-authority clamp (feature.spec.md, state.spec §3
|
|
57
|
-
* `ASSIGN_ROLE`/`ATTACH_POLICY` rows): every permission in `conferredPermissions`
|
|
58
|
-
* must be a matching effective row the caller holds unconstrained
|
|
59
|
-
* (`resourceType`/`constraintJson` both null; owner's `*` qualifies for all).
|
|
60
|
-
* A permission held only conditionally (resource-scoped or constraint-bound)
|
|
61
|
-
* cannot be delegated. Throws `GrantExceedsIssuerError` (403
|
|
62
|
-
* `GRANT_EXCEEDS_ISSUER`) naming every offending permission, and writes no
|
|
63
|
-
* grant row (caller does the write only after this resolves).
|
|
64
|
-
*
|
|
65
|
-
* @complexity O(resolveEffectivePermissions) + O(k) local filtering, k =
|
|
66
|
-
* `conferredPermissions.length` (bounded by one role's/policy's permission
|
|
67
|
-
* rows — a handful, per `identity/INFO.md`'s scale assumptions).
|
|
68
|
-
* @overallScore 100
|
|
69
|
-
*/
|
|
70
|
-
export declare function assertGrantClamp(required: {
|
|
71
|
-
deps: AuthServiceDeps;
|
|
72
|
-
workspaceId: UUID;
|
|
73
|
-
callerPrincipalId: UUID;
|
|
74
|
-
conferredPermissions: readonly string[];
|
|
75
|
-
}): Promise<void>;
|
|
76
|
-
/**
|
|
77
|
-
* `CREATE_USER` (state.spec §3, REQ-01/MF-1). Atomically mints a NEW
|
|
78
|
-
* `kind='user'` principal and its `users` row — the `principalId` is always
|
|
79
|
-
* generated here, never caller-supplied, so this transition can never attach
|
|
80
|
-
* a credential to a pre-existing principal (the MF-1 privilege-escalation
|
|
81
|
-
* class: an earlier draft let an admin bind a password to the seeded owner
|
|
82
|
-
* principal and log in as owner). Gated by `user.manage` OR `member.manage`
|
|
83
|
-
* (admin onboarding, AC-22).
|
|
84
|
-
*
|
|
85
|
-
* "Atomically" here means structurally, not via a DB transaction — the
|
|
86
|
-
* in-memory adapters have no cross-repo transaction (same disclosed gap as
|
|
87
|
-
* `seed.ts`/`executeCommand`'s compensating-rollback path). The invariant
|
|
88
|
-
* that actually matters (no credential ever attaches to an existing
|
|
89
|
-
* principal) holds regardless, because `principalId` is generated inline and
|
|
90
|
-
* the password is hashed before either row is written, so a hashing failure
|
|
91
|
-
* leaves no orphan principal. A future SQLite adapter wraps both writes in
|
|
92
|
-
* one real transaction (port/adapter rule-of-two) without changing this contract.
|
|
93
|
-
*
|
|
94
|
-
* @complexity O(1) — one permission check, one username lookup, one hash,
|
|
95
|
-
* two saves.
|
|
96
|
-
* @overallScore 100
|
|
97
|
-
*/
|
|
98
|
-
export declare function createUser(required: {
|
|
99
|
-
deps: AuthServiceDeps;
|
|
100
|
-
input: {
|
|
101
|
-
workspaceId: UUID;
|
|
102
|
-
callerPrincipalId: UUID;
|
|
103
|
-
username: string;
|
|
104
|
-
email?: string | undefined;
|
|
105
|
-
password: string;
|
|
106
|
-
};
|
|
107
|
-
}): Promise<{
|
|
108
|
-
principal: PrincipalRecord;
|
|
109
|
-
user: UserRecord;
|
|
110
|
-
}>;
|
|
111
|
-
/**
|
|
112
|
-
* `CREATE_ROLE` (state.spec §3). Gated by `role.manage`. Always mints
|
|
113
|
-
* `isBuiltin=false` — built-ins are SEED-only and immutable (INV-06).
|
|
114
|
-
*
|
|
115
|
-
* Deviation from the state.spec §3 table (disclosed): that row lists
|
|
116
|
-
* `name`, `description?` jointly for `CREATE_ROLE`/`CREATE_POLICY`, but
|
|
117
|
-
* `RoleRecord` (types.ts, frozen before this task) has no `description`
|
|
118
|
-
* field — only `PolicyRecord` does. `createRole` therefore takes `name`
|
|
119
|
-
* only; `createPolicy` below takes both.
|
|
120
|
-
*
|
|
121
|
-
* @complexity O(1) — one permission check, one save.
|
|
122
|
-
* @overallScore 100
|
|
123
|
-
*/
|
|
124
|
-
export declare function createRole(required: {
|
|
125
|
-
deps: AuthServiceDeps;
|
|
126
|
-
input: {
|
|
127
|
-
workspaceId: UUID;
|
|
128
|
-
callerPrincipalId: UUID;
|
|
129
|
-
name: string;
|
|
130
|
-
};
|
|
131
|
-
}): Promise<{
|
|
132
|
-
role: RoleRecord;
|
|
133
|
-
}>;
|
|
134
|
-
/**
|
|
135
|
-
* `CREATE_POLICY` (state.spec §3). Gated by `role.manage`. Always mints
|
|
136
|
-
* `isBuiltin=false` and `isFrozen=false` — only the (out-of-scope this pass)
|
|
137
|
-
* `ISSUE_API_KEY` transition ever mints `isFrozen=true` policies.
|
|
138
|
-
*
|
|
139
|
-
* @complexity O(1) — one permission check, one save.
|
|
140
|
-
* @overallScore 100
|
|
141
|
-
*/
|
|
142
|
-
export declare function createPolicy(required: {
|
|
143
|
-
deps: AuthServiceDeps;
|
|
144
|
-
input: {
|
|
145
|
-
workspaceId: UUID;
|
|
146
|
-
callerPrincipalId: UUID;
|
|
147
|
-
name: string;
|
|
148
|
-
description?: string | undefined;
|
|
149
|
-
};
|
|
150
|
-
}): Promise<{
|
|
151
|
-
policy: PolicyRecord;
|
|
152
|
-
}>;
|
|
153
|
-
/**
|
|
154
|
-
* `ASSIGN_ROLE` (state.spec §3, REQ-02, AC-24/AC-25). Gated by `role.manage`;
|
|
155
|
-
* target must be a `kind='user'` principal (AC-25b); the INV-07 clamp must
|
|
156
|
-
* pass over every permission carried by the role's policies (AC-24) — so
|
|
157
|
-
* assigning the built-in `owner` role requires the caller to already hold
|
|
158
|
-
* the wildcard `*` (owner-only). No row is written on any rejection.
|
|
159
|
-
*
|
|
160
|
-
* @complexity O(resolveEffectivePermissions) + O(role's rolePolicy count)
|
|
161
|
-
* round trips to gather conferred permissions — both bounded by the small,
|
|
162
|
-
* hand-authored grant graph this system assumes (see `identity/INFO.md`).
|
|
163
|
-
* @overallScore 100
|
|
164
|
-
*/
|
|
165
|
-
export declare function assignRole(required: {
|
|
166
|
-
deps: AuthServiceDeps;
|
|
167
|
-
input: {
|
|
168
|
-
workspaceId: UUID;
|
|
169
|
-
callerPrincipalId: UUID;
|
|
170
|
-
principalId: UUID;
|
|
171
|
-
roleId: UUID;
|
|
172
|
-
};
|
|
173
|
-
}): Promise<{
|
|
174
|
-
assignment: PrincipalRoleRecord;
|
|
175
|
-
}>;
|
|
176
|
-
/**
|
|
177
|
-
* `ATTACH_POLICY` (state.spec §3, REQ-02, AC-24/AC-25). Gated by
|
|
178
|
-
* `role.manage`; target must be a `kind='user'` principal (AC-25b); the
|
|
179
|
-
* INV-07 clamp must pass over every permission the policy carries (AC-24) —
|
|
180
|
-
* attaching the built-in `owner` policy requires the caller to already hold
|
|
181
|
-
* `*` (owner-only). No row is written on any rejection.
|
|
182
|
-
*
|
|
183
|
-
* @complexity O(resolveEffectivePermissions) + O(policy's permission row
|
|
184
|
-
* count) — both bounded, see `assignRole`'s doc.
|
|
185
|
-
* @overallScore 100
|
|
186
|
-
*/
|
|
187
|
-
export declare function attachPolicy(required: {
|
|
188
|
-
deps: AuthServiceDeps;
|
|
189
|
-
input: {
|
|
190
|
-
workspaceId: UUID;
|
|
191
|
-
callerPrincipalId: UUID;
|
|
192
|
-
principalId: UUID;
|
|
193
|
-
policyId: UUID;
|
|
194
|
-
};
|
|
195
|
-
}): Promise<{
|
|
196
|
-
attachment: PrincipalPolicyRecord;
|
|
197
|
-
}>;
|
|
198
|
-
//# sourceMappingURL=grant-service.d.ts.map
|