@tearleads/client-sdk 0.1.104 → 0.1.106
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/client/organizations/index.js +5 -2
- package/client/organizations/index.js.map +1 -1
- package/client/organizations/organizationMemberships.js +26 -18
- package/client/organizations/organizationMemberships.js.map +1 -1
- package/client/organizations/organizationReadModels.d.ts +9 -4
- package/client/organizations/organizationReadModels.js +20 -9
- package/client/organizations/organizationReadModels.js.map +1 -1
- package/data/keyingProjectionVerification/checkpoints/checkpointContext.js +4 -1
- package/data/keyingProjectionVerification/checkpoints/checkpointContext.js.map +1 -1
- package/data/keyingProjectionVerification/checkpoints/verifiedBundleBytes.js +27 -0
- package/data/keyingProjectionVerification/checkpoints/verifiedBundleBytes.js.map +1 -0
- package/data/keyingProjectionVerification/container/containerManifestVerification.js +2 -0
- package/data/keyingProjectionVerification/container/containerManifestVerification.js.map +1 -1
- package/data/keyingProjectionVerification/document/documentManifestVerification.js +2 -0
- package/data/keyingProjectionVerification/document/documentManifestVerification.js.map +1 -1
- package/data/persistence/container-contents/containerContentsPersistence.js +3 -1
- package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerMetadataRows.js +9 -0
- package/data/persistence/container-contents/containerMetadataRows.js.map +1 -1
- package/data/principals/principalPolicyAdminSigners.js +50 -0
- package/data/principals/principalPolicyAdminSigners.js.map +1 -1
- package/internal/api-client/ApiClient.d.ts +7 -0
- package/internal/crypto/organizationLeaveRequest.d.ts +1 -1
- package/internal/crypto/organizationLeaveRequest.js +9 -2
- package/internal/crypto/organizationLeaveRequest.js.map +1 -1
- package/internal/validators/operation/organizationMemberships.d.ts +7 -0
- package/internal/validators/operation/organizations.d.ts +56 -0
- package/internal/validators/response/organization.d.ts +91 -0
- package/internal/validators/response/organizationReadModel.d.ts +70 -0
- package/internal/validators/util/index.d.ts +1 -1
- package/internal/validators/util/index.js.map +1 -1
- package/internal/validators/util/organizationLeaveRequest.d.ts +25 -2
- package/internal/validators/util/organizationLeaveRequest.js +19 -3
- package/internal/validators/util/organizationLeaveRequest.js.map +1 -1
- package/package.json +1 -1
- package/workflows/organizations/memberships/leaveRequest.d.ts +6 -2
- package/workflows/organizations/memberships/leaveRequest.js +20 -5
- package/workflows/organizations/memberships/leaveRequest.js.map +1 -1
- package/workflows/organizations/memberships/leaveRequestMembersEvidence.js +80 -0
- package/workflows/organizations/memberships/leaveRequestMembersEvidence.js.map +1 -0
- package/workflows/organizations/memberships/leaveRequestMembership.d.ts +24 -0
- package/workflows/organizations/memberships/leaveRequestMembership.js +65 -0
- package/workflows/organizations/memberships/leaveRequestMembership.js.map +1 -0
- package/workflows/organizations/memberships/leaveRequestVerification.d.ts +15 -7
- package/workflows/organizations/memberships/leaveRequestVerification.js +18 -11
- package/workflows/organizations/memberships/leaveRequestVerification.js.map +1 -1
- package/workflows/principals/policyCache/principalPolicyCacheValidation.js +20 -2
- package/workflows/principals/policyCache/principalPolicyCacheValidation.js.map +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ProjectionDependencyUnavailableError } from "../../../data/keyingProjectionVerification/dependencyUnavailable.js";
|
|
2
|
+
import { assertProjectionVerificationCurrent } from "../../../data/keyingProjectionVerification/types.js";
|
|
3
|
+
import { requireOrganizationGroupHead } from "../../../data/principals/organizationAuthorityDescriptor.js";
|
|
4
|
+
import { createRuntimePrincipalPolicyCurrentResolver, } from "../../principals/runtimePolicyRecovery.js";
|
|
5
|
+
import { loadCurrentOrganizationAuthority } from "../currentOrganizationAuthority.js";
|
|
6
|
+
import { loadLocalOrganizationPolicyReference } from "../readModel/localReadModelDetails.js";
|
|
7
|
+
import { createLeaveRequestMembershipCheck, } from "./leaveRequestMembership.js";
|
|
8
|
+
/**
|
|
9
|
+
* The organization's current Members head as the member's own device
|
|
10
|
+
* discovers and verifies it now, for a leave request to cite.
|
|
11
|
+
*/
|
|
12
|
+
export async function loadCurrentMembersHead(input) {
|
|
13
|
+
const resolveCurrentPolicy = createRuntimePrincipalPolicyCurrentResolver(input.runtime);
|
|
14
|
+
if (!resolveCurrentPolicy)
|
|
15
|
+
throw new ProjectionDependencyUnavailableError("Asking to leave requires private paged recovery");
|
|
16
|
+
const authority = await loadCurrentOrganizationAuthority({
|
|
17
|
+
execSql: input.runtime.infra.execSql,
|
|
18
|
+
organizationId: input.organizationId,
|
|
19
|
+
resolveCurrentPolicy,
|
|
20
|
+
stillCurrent: input.stillCurrent,
|
|
21
|
+
});
|
|
22
|
+
assertProjectionVerificationCurrent(authority.stillCurrent);
|
|
23
|
+
const head = requireOrganizationGroupHead(authority.descriptor, authority.descriptor.memberGroupId);
|
|
24
|
+
return {
|
|
25
|
+
keyEpoch: head.keyEpoch,
|
|
26
|
+
keyFingerprint: head.keyFingerprint,
|
|
27
|
+
principalId: head.principalId,
|
|
28
|
+
stateHash: head.stateHash,
|
|
29
|
+
version: head.version,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Checks leave requests against the Members head in the organization policy
|
|
34
|
+
* this device's read model selected, verified exactly through the private
|
|
35
|
+
* paged verifier (from local evidence when it has it), and walks verified
|
|
36
|
+
* Members history the same way when a request cites an earlier key epoch.
|
|
37
|
+
*/
|
|
38
|
+
export function createRuntimeLeaveRequestMembershipCheck(input) {
|
|
39
|
+
const resolve = createRuntimePrincipalPolicyCurrentResolver(input.runtime);
|
|
40
|
+
return createLeaveRequestMembershipCheck({
|
|
41
|
+
async loadMembersHead() {
|
|
42
|
+
if (!resolve)
|
|
43
|
+
return null;
|
|
44
|
+
const organizationReference = await loadLocalOrganizationPolicyReference({
|
|
45
|
+
currentUserId: input.currentUserId,
|
|
46
|
+
execSql: input.runtime.infra.execSql,
|
|
47
|
+
organizationId: input.organizationId,
|
|
48
|
+
principalId: input.organizationId,
|
|
49
|
+
principalType: "organization",
|
|
50
|
+
});
|
|
51
|
+
if (!organizationReference)
|
|
52
|
+
return null;
|
|
53
|
+
const authority = await loadCurrentOrganizationAuthority({
|
|
54
|
+
execSql: input.runtime.infra.execSql,
|
|
55
|
+
organizationId: input.organizationId,
|
|
56
|
+
organizationReference,
|
|
57
|
+
resolveCurrentPolicy: resolve,
|
|
58
|
+
stillCurrent: input.stillCurrent,
|
|
59
|
+
});
|
|
60
|
+
assertProjectionVerificationCurrent(authority.stillCurrent);
|
|
61
|
+
return requireOrganizationGroupHead(authority.descriptor, authority.descriptor.memberGroupId);
|
|
62
|
+
},
|
|
63
|
+
loadHistoryPage: resolve
|
|
64
|
+
? async (head, beforeVersion) => {
|
|
65
|
+
const resolved = await resolve({
|
|
66
|
+
organizationId: input.organizationId,
|
|
67
|
+
reference: head,
|
|
68
|
+
preferLocalCurrent: true,
|
|
69
|
+
stillCurrent: input.stillCurrent,
|
|
70
|
+
historyPage: beforeVersion === undefined ? {} : { beforeVersion },
|
|
71
|
+
});
|
|
72
|
+
assertProjectionVerificationCurrent(resolved.stillCurrent);
|
|
73
|
+
if (!resolved.historyPage)
|
|
74
|
+
throw new Error("Verified Members history page is missing");
|
|
75
|
+
return resolved.historyPage;
|
|
76
|
+
}
|
|
77
|
+
: undefined,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=leaveRequestMembersEvidence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaveRequestMembersEvidence.js","sourceRoot":"","sources":["leaveRequestMembersEvidence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OrganizationLeaveRequestMembersHead } from \"@tearleads/validators/util\";\nimport { ProjectionDependencyUnavailableError } from \"../../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport { assertProjectionVerificationCurrent } from \"../../../data/keyingProjectionVerification/types\";\nimport { requireOrganizationGroupHead } from \"../../../data/principals/organizationAuthorityDescriptor\";\nimport {\n createRuntimePrincipalPolicyCurrentResolver,\n type PrincipalPolicyRecoveryRuntime,\n} from \"../../principals/runtimePolicyRecovery\";\nimport { loadCurrentOrganizationAuthority } from \"../currentOrganizationAuthority\";\nimport { loadLocalOrganizationPolicyReference } from \"../readModel/localReadModelDetails\";\nimport {\n createLeaveRequestMembershipCheck,\n type LeaveRequestMembershipCheck,\n} from \"./leaveRequestMembership\";\n\ninterface MembersEvidenceInput {\n readonly runtime: PrincipalPolicyRecoveryRuntime;\n readonly organizationId: string;\n readonly stillCurrent: () => boolean;\n}\n\n/**\n * The organization's current Members head as the member's own device\n * discovers and verifies it now, for a leave request to cite.\n */\nexport async function loadCurrentMembersHead(\n input: MembersEvidenceInput,\n): Promise<OrganizationLeaveRequestMembersHead> {\n const resolveCurrentPolicy = createRuntimePrincipalPolicyCurrentResolver(\n input.runtime,\n );\n if (!resolveCurrentPolicy)\n throw new ProjectionDependencyUnavailableError(\n \"Asking to leave requires private paged recovery\",\n );\n const authority = await loadCurrentOrganizationAuthority({\n execSql: input.runtime.infra.execSql,\n organizationId: input.organizationId,\n resolveCurrentPolicy,\n stillCurrent: input.stillCurrent,\n });\n assertProjectionVerificationCurrent(authority.stillCurrent);\n const head = requireOrganizationGroupHead(\n authority.descriptor,\n authority.descriptor.memberGroupId,\n );\n return {\n keyEpoch: head.keyEpoch,\n keyFingerprint: head.keyFingerprint,\n principalId: head.principalId,\n stateHash: head.stateHash,\n version: head.version,\n };\n}\n\n/**\n * Checks leave requests against the Members head in the organization policy\n * this device's read model selected, verified exactly through the private\n * paged verifier (from local evidence when it has it), and walks verified\n * Members history the same way when a request cites an earlier key epoch.\n */\nexport function createRuntimeLeaveRequestMembershipCheck(\n input: MembersEvidenceInput & { readonly currentUserId: string },\n): LeaveRequestMembershipCheck {\n const resolve = createRuntimePrincipalPolicyCurrentResolver(input.runtime);\n return createLeaveRequestMembershipCheck({\n async loadMembersHead() {\n if (!resolve) return null;\n const organizationReference = await loadLocalOrganizationPolicyReference({\n currentUserId: input.currentUserId,\n execSql: input.runtime.infra.execSql,\n organizationId: input.organizationId,\n principalId: input.organizationId,\n principalType: \"organization\",\n });\n if (!organizationReference) return null;\n const authority = await loadCurrentOrganizationAuthority({\n execSql: input.runtime.infra.execSql,\n organizationId: input.organizationId,\n organizationReference,\n resolveCurrentPolicy: resolve,\n stillCurrent: input.stillCurrent,\n });\n assertProjectionVerificationCurrent(authority.stillCurrent);\n return requireOrganizationGroupHead(\n authority.descriptor,\n authority.descriptor.memberGroupId,\n );\n },\n loadHistoryPage: resolve\n ? async (head, beforeVersion) => {\n const resolved = await resolve({\n organizationId: input.organizationId,\n reference: head,\n preferLocalCurrent: true,\n stillCurrent: input.stillCurrent,\n historyPage: beforeVersion === undefined ? {} : { beforeVersion },\n });\n assertProjectionVerificationCurrent(resolved.stillCurrent);\n if (!resolved.historyPage)\n throw new Error(\"Verified Members history page is missing\");\n return resolved.historyPage;\n }\n : undefined,\n });\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ReferencedPrincipalHead } from "../../../internal/crypto/index.js";
|
|
2
|
+
import type { OrganizationLeaveRequest } from "../../../internal/validators/util/index.js";
|
|
3
|
+
import type { RecoveredPrincipalHistoryPage } from "../../principals/historyRecovery/loadRecoveredPrincipalHistoryPage.js";
|
|
4
|
+
export interface LeaveRequestMembershipEvidence {
|
|
5
|
+
/** The Members head this device has verified, or null when it has none. */
|
|
6
|
+
readonly loadMembersHead: () => Promise<ReferencedPrincipalHead | null>;
|
|
7
|
+
/**
|
|
8
|
+
* Verified Members history ending at `head`, one page at a time and in
|
|
9
|
+
* ascending order. Absent where this device cannot read history.
|
|
10
|
+
*/
|
|
11
|
+
readonly loadHistoryPage?: ((head: ReferencedPrincipalHead, beforeVersion: number | undefined) => Promise<RecoveredPrincipalHistoryPage>) | undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Whether a leave request still belongs to its member's current membership. */
|
|
14
|
+
export type LeaveRequestMembershipCheck = (request: OrganizationLeaveRequest) => Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Checks each leave request against this device's verified Members head.
|
|
17
|
+
* Removing anyone from Members rotates its key epoch and key, so a request
|
|
18
|
+
* citing the head's own epoch and key has seen no removal since. One citing an earlier epoch
|
|
19
|
+
* stands only if verified history shows its member in every Members state
|
|
20
|
+
* from the cited one on; a request from a membership a removal ended never
|
|
21
|
+
* does, whatever the server serves beside it. Display only: a request this
|
|
22
|
+
* device cannot place is shown as no request.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createLeaveRequestMembershipCheck(evidence: LeaveRequestMembershipEvidence): LeaveRequestMembershipCheck;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// How far back one request's check walks verified Members history: a request
|
|
2
|
+
// stays pending for days, not for hundreds of Members changes.
|
|
3
|
+
const MAX_MEMBERS_HISTORY_PAGES = 8;
|
|
4
|
+
function citesState(request, state) {
|
|
5
|
+
const cited = request.membersHead;
|
|
6
|
+
return (state.version === cited.version &&
|
|
7
|
+
state.stateHash === cited.stateHash &&
|
|
8
|
+
state.keyEpoch === cited.keyEpoch &&
|
|
9
|
+
state.keyFingerprint === cited.keyFingerprint);
|
|
10
|
+
}
|
|
11
|
+
// Walks verified Members history back from the head to the cited state,
|
|
12
|
+
// requiring the member in every state on the way.
|
|
13
|
+
async function memberStayedSinceCitedState(request, head, loadHistoryPage) {
|
|
14
|
+
let expectedVersion = head.version;
|
|
15
|
+
let beforeVersion;
|
|
16
|
+
for (let pages = 0; pages < MAX_MEMBERS_HISTORY_PAGES; pages += 1) {
|
|
17
|
+
const page = await loadHistoryPage(head, beforeVersion);
|
|
18
|
+
for (const entry of [...page.entries].reverse()) {
|
|
19
|
+
const { state } = entry;
|
|
20
|
+
if (state.principalType !== head.principalType ||
|
|
21
|
+
state.principalId !== head.principalId ||
|
|
22
|
+
state.version !== expectedVersion ||
|
|
23
|
+
(state.version === head.version && state.stateHash !== head.stateHash))
|
|
24
|
+
return false;
|
|
25
|
+
if (!entry.projection.some((member) => member.userId === request.userId))
|
|
26
|
+
return false;
|
|
27
|
+
if (state.version === request.membersHead.version)
|
|
28
|
+
return citesState(request, state);
|
|
29
|
+
expectedVersion -= 1;
|
|
30
|
+
}
|
|
31
|
+
if (page.nextBeforeVersion === null || page.entries.length === 0)
|
|
32
|
+
return false;
|
|
33
|
+
beforeVersion = page.nextBeforeVersion;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Checks each leave request against this device's verified Members head.
|
|
39
|
+
* Removing anyone from Members rotates its key epoch and key, so a request
|
|
40
|
+
* citing the head's own epoch and key has seen no removal since. One citing an earlier epoch
|
|
41
|
+
* stands only if verified history shows its member in every Members state
|
|
42
|
+
* from the cited one on; a request from a membership a removal ended never
|
|
43
|
+
* does, whatever the server serves beside it. Display only: a request this
|
|
44
|
+
* device cannot place is shown as no request.
|
|
45
|
+
*/
|
|
46
|
+
export function createLeaveRequestMembershipCheck(evidence) {
|
|
47
|
+
let membersHead;
|
|
48
|
+
return async (request) => {
|
|
49
|
+
membersHead ??= evidence.loadMembersHead().catch(() => null);
|
|
50
|
+
const head = await membersHead;
|
|
51
|
+
const cited = request.membersHead;
|
|
52
|
+
if (head?.principalType !== "group" ||
|
|
53
|
+
cited.principalId !== head.principalId ||
|
|
54
|
+
cited.keyEpoch > head.keyEpoch)
|
|
55
|
+
return false;
|
|
56
|
+
// One signed chain holds one key per epoch, so a matching epoch with a
|
|
57
|
+
// different key can only come from a fork, never from this history.
|
|
58
|
+
if (cited.keyEpoch === head.keyEpoch)
|
|
59
|
+
return cited.keyFingerprint === head.keyFingerprint;
|
|
60
|
+
if (!evidence.loadHistoryPage || cited.version >= head.version)
|
|
61
|
+
return false;
|
|
62
|
+
return memberStayedSinceCitedState(request, head, evidence.loadHistoryPage).catch(() => false);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=leaveRequestMembership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaveRequestMembership.js","sourceRoot":"","sources":["leaveRequestMembership.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReferencedPrincipalHead } from \"@tearleads/crypto\";\nimport type { OrganizationLeaveRequest } from \"@tearleads/validators/util\";\nimport type { RecoveredPrincipalHistoryPage } from \"../../principals/historyRecovery/loadRecoveredPrincipalHistoryPage\";\n\n// How far back one request's check walks verified Members history: a request\n// stays pending for days, not for hundreds of Members changes.\nconst MAX_MEMBERS_HISTORY_PAGES = 8;\n\nexport interface LeaveRequestMembershipEvidence {\n /** The Members head this device has verified, or null when it has none. */\n readonly loadMembersHead: () => Promise<ReferencedPrincipalHead | null>;\n /**\n * Verified Members history ending at `head`, one page at a time and in\n * ascending order. Absent where this device cannot read history.\n */\n readonly loadHistoryPage?:\n | ((\n head: ReferencedPrincipalHead,\n beforeVersion: number | undefined,\n ) => Promise<RecoveredPrincipalHistoryPage>)\n | undefined;\n}\n\n/** Whether a leave request still belongs to its member's current membership. */\nexport type LeaveRequestMembershipCheck = (\n request: OrganizationLeaveRequest,\n) => Promise<boolean>;\n\nfunction citesState(\n request: OrganizationLeaveRequest,\n state: RecoveredPrincipalHistoryPage[\"entries\"][number][\"state\"],\n): boolean {\n const cited = request.membersHead;\n return (\n state.version === cited.version &&\n state.stateHash === cited.stateHash &&\n state.keyEpoch === cited.keyEpoch &&\n state.keyFingerprint === cited.keyFingerprint\n );\n}\n\n// Walks verified Members history back from the head to the cited state,\n// requiring the member in every state on the way.\nasync function memberStayedSinceCitedState(\n request: OrganizationLeaveRequest,\n head: ReferencedPrincipalHead,\n loadHistoryPage: NonNullable<\n LeaveRequestMembershipEvidence[\"loadHistoryPage\"]\n >,\n): Promise<boolean> {\n let expectedVersion = head.version;\n let beforeVersion: number | undefined;\n for (let pages = 0; pages < MAX_MEMBERS_HISTORY_PAGES; pages += 1) {\n const page = await loadHistoryPage(head, beforeVersion);\n for (const entry of [...page.entries].reverse()) {\n const { state } = entry;\n if (\n state.principalType !== head.principalType ||\n state.principalId !== head.principalId ||\n state.version !== expectedVersion ||\n (state.version === head.version && state.stateHash !== head.stateHash)\n )\n return false;\n if (!entry.projection.some((member) => member.userId === request.userId))\n return false;\n if (state.version === request.membersHead.version)\n return citesState(request, state);\n expectedVersion -= 1;\n }\n if (page.nextBeforeVersion === null || page.entries.length === 0)\n return false;\n beforeVersion = page.nextBeforeVersion;\n }\n return false;\n}\n\n/**\n * Checks each leave request against this device's verified Members head.\n * Removing anyone from Members rotates its key epoch and key, so a request\n * citing the head's own epoch and key has seen no removal since. One citing an earlier epoch\n * stands only if verified history shows its member in every Members state\n * from the cited one on; a request from a membership a removal ended never\n * does, whatever the server serves beside it. Display only: a request this\n * device cannot place is shown as no request.\n */\nexport function createLeaveRequestMembershipCheck(\n evidence: LeaveRequestMembershipEvidence,\n): LeaveRequestMembershipCheck {\n let membersHead: Promise<ReferencedPrincipalHead | null> | undefined;\n return async (request) => {\n membersHead ??= evidence.loadMembersHead().catch(() => null);\n const head = await membersHead;\n const cited = request.membersHead;\n if (\n head?.principalType !== \"group\" ||\n cited.principalId !== head.principalId ||\n cited.keyEpoch > head.keyEpoch\n )\n return false;\n // One signed chain holds one key per epoch, so a matching epoch with a\n // different key can only come from a fork, never from this history.\n if (cited.keyEpoch === head.keyEpoch)\n return cited.keyFingerprint === head.keyFingerprint;\n if (!evidence.loadHistoryPage || cited.version >= head.version)\n return false;\n return memberStayedSinceCitedState(\n request,\n head,\n evidence.loadHistoryPage,\n ).catch(() => false);\n };\n}\n"]}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import type { TrustedUserIdentityResolver } from "../../../data/trustedUserIdentity/index.js";
|
|
2
2
|
import type { OrganizationDirectoryAndGroups, OrganizationDirectoryUser, OrganizationUserDetail } from "../readModel/index.js";
|
|
3
|
+
import type { LeaveRequestMembershipCheck } from "./leaveRequestMembership.js";
|
|
4
|
+
/** What a shown leave request is checked against. */
|
|
5
|
+
export interface LeaveRequestVerifier {
|
|
6
|
+
readonly resolveTrustedUserIdentity: TrustedUserIdentityResolver;
|
|
7
|
+
readonly membershipIsCurrent: LeaveRequestMembershipCheck;
|
|
8
|
+
}
|
|
3
9
|
/**
|
|
4
10
|
* The user as shown, with its leave request kept only when the member signed
|
|
5
|
-
* it. Every directory user a client presents
|
|
6
|
-
* read model or a mutation response carried
|
|
11
|
+
* it during their current membership. Every directory user a client presents
|
|
12
|
+
* passes through here, whether a read model or a mutation response carried
|
|
13
|
+
* it.
|
|
7
14
|
*/
|
|
8
|
-
export declare function withVerifiedDirectoryUser(organizationId: string, user: OrganizationDirectoryUser,
|
|
15
|
+
export declare function withVerifiedDirectoryUser(organizationId: string, user: OrganizationDirectoryUser, verifier: LeaveRequestVerifier): Promise<OrganizationDirectoryUser>;
|
|
9
16
|
/**
|
|
10
|
-
* Keeps only leave requests their member signed
|
|
11
|
-
*
|
|
17
|
+
* Keeps only leave requests their member signed during their current
|
|
18
|
+
* membership, checked against the member's trusted identity and this device's
|
|
19
|
+
* verified Members history; any other is shown as no request at all.
|
|
12
20
|
*/
|
|
13
|
-
export declare function withVerifiedLeaveRequests<Result extends OrganizationDirectoryAndGroups | null | undefined>(result: Result,
|
|
21
|
+
export declare function withVerifiedLeaveRequests<Result extends OrganizationDirectoryAndGroups | null | undefined>(result: Result, verifier: LeaveRequestVerifier): Promise<Result>;
|
|
14
22
|
/** The user detail counterpart of withVerifiedLeaveRequests. */
|
|
15
|
-
export declare function withVerifiedUserLeaveRequest(detail: OrganizationUserDetail | null, organizationId: string,
|
|
23
|
+
export declare function withVerifiedUserLeaveRequest(detail: OrganizationUserDetail | null, organizationId: string, verifier: LeaveRequestVerifier): Promise<OrganizationUserDetail | null>;
|
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
import { isValidOrganizationLeaveRequest } from "../../../internal/crypto/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* The user as shown, with its leave request kept only when the member signed
|
|
4
|
-
* it. Every directory user a client presents
|
|
5
|
-
* read model or a mutation response carried
|
|
4
|
+
* it during their current membership. Every directory user a client presents
|
|
5
|
+
* passes through here, whether a read model or a mutation response carried
|
|
6
|
+
* it.
|
|
6
7
|
*/
|
|
7
|
-
export async function withVerifiedDirectoryUser(organizationId, user,
|
|
8
|
+
export async function withVerifiedDirectoryUser(organizationId, user, verifier) {
|
|
8
9
|
const request = user.leaveRequest;
|
|
9
10
|
if (!request)
|
|
10
11
|
return user;
|
|
11
12
|
// The member's pinned identity, never the key the directory served beside
|
|
12
13
|
// the request: a server that could swap both could forge any member's ask.
|
|
13
|
-
const identity = await
|
|
14
|
-
|
|
14
|
+
const identity = await verifier
|
|
15
|
+
.resolveTrustedUserIdentity(user.userId)
|
|
16
|
+
.catch(() => null);
|
|
17
|
+
const signed = identity !== null &&
|
|
15
18
|
request.organizationId === organizationId &&
|
|
16
19
|
request.userId === user.userId &&
|
|
17
20
|
isValidOrganizationLeaveRequest(request, identity.signingPublicKey);
|
|
21
|
+
// A genuine request from a membership that has since ended would otherwise
|
|
22
|
+
// look current to an administrator once the member rejoined.
|
|
23
|
+
const verified = signed && (await verifier.membershipIsCurrent(request));
|
|
18
24
|
return verified ? user : { ...user, leaveRequest: null };
|
|
19
25
|
}
|
|
20
26
|
/**
|
|
21
|
-
* Keeps only leave requests their member signed
|
|
22
|
-
*
|
|
27
|
+
* Keeps only leave requests their member signed during their current
|
|
28
|
+
* membership, checked against the member's trusted identity and this device's
|
|
29
|
+
* verified Members history; any other is shown as no request at all.
|
|
23
30
|
*/
|
|
24
|
-
export async function withVerifiedLeaveRequests(result,
|
|
31
|
+
export async function withVerifiedLeaveRequests(result, verifier) {
|
|
25
32
|
if (!result?.directory.users.some((user) => user.leaveRequest)) {
|
|
26
33
|
return result;
|
|
27
34
|
}
|
|
@@ -30,17 +37,17 @@ export async function withVerifiedLeaveRequests(result, resolveTrustedUserIdenti
|
|
|
30
37
|
...result,
|
|
31
38
|
directory: {
|
|
32
39
|
...result.directory,
|
|
33
|
-
users: await Promise.all(result.directory.users.map((user) => withVerifiedDirectoryUser(organizationId, user,
|
|
40
|
+
users: await Promise.all(result.directory.users.map((user) => withVerifiedDirectoryUser(organizationId, user, verifier))),
|
|
34
41
|
},
|
|
35
42
|
};
|
|
36
43
|
}
|
|
37
44
|
/** The user detail counterpart of withVerifiedLeaveRequests. */
|
|
38
|
-
export async function withVerifiedUserLeaveRequest(detail, organizationId,
|
|
45
|
+
export async function withVerifiedUserLeaveRequest(detail, organizationId, verifier) {
|
|
39
46
|
if (!detail?.user.leaveRequest)
|
|
40
47
|
return detail;
|
|
41
48
|
return {
|
|
42
49
|
...detail,
|
|
43
|
-
user: await withVerifiedDirectoryUser(organizationId, detail.user,
|
|
50
|
+
user: await withVerifiedDirectoryUser(organizationId, detail.user, verifier),
|
|
44
51
|
};
|
|
45
52
|
}
|
|
46
53
|
//# sourceMappingURL=leaveRequestVerification.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaveRequestVerification.js","sourceRoot":"","sources":["leaveRequestVerification.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"leaveRequestVerification.js","sourceRoot":"","sources":["leaveRequestVerification.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC;AAepE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,cAAsB,EACtB,IAA+B,EAC/B,QAA8B;IAE9B,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC;IAClC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,0EAA0E;IAC1E,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,MAAM,QAAQ;SAC5B,0BAA0B,CAAC,IAAI,CAAC,MAAM,CAAC;SACvC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACrB,MAAM,MAAM,GACV,QAAQ,KAAK,IAAI;QACjB,OAAO,CAAC,cAAc,KAAK,cAAc;QACzC,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAC9B,+BAA+B,CAAC,OAAO,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACtE,2EAA2E;IAC3E,6DAA6D;IAC7D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,QAAQ,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;IACzE,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAE7C,MAAc,EAAE,QAA8B;IAC9C,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC/D,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC;IAC5C,OAAO;QACL,GAAG,MAAM;QACT,SAAS,EAAE;YACT,GAAG,MAAM,CAAC,SAAS;YACnB,KAAK,EAAE,MAAM,OAAO,CAAC,GAAG,CACtB,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAClC,yBAAyB,CAAC,cAAc,EAAE,IAAI,EAAE,QAAQ,CAAC,CAC1D,CACF;SACF;KACF,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,MAAqC,EACrC,cAAsB,EACtB,QAA8B;IAE9B,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY;QAAE,OAAO,MAAM,CAAC;IAC9C,OAAO;QACL,GAAG,MAAM;QACT,IAAI,EAAE,MAAM,yBAAyB,CACnC,cAAc,EACd,MAAM,CAAC,IAAI,EACX,QAAQ,CACT;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { isValidOrganizationLeaveRequest } from \"@tearleads/crypto\";\nimport type { TrustedUserIdentityResolver } from \"../../../data/trustedUserIdentity\";\nimport type {\n OrganizationDirectoryAndGroups,\n OrganizationDirectoryUser,\n OrganizationUserDetail,\n} from \"../readModel\";\nimport type { LeaveRequestMembershipCheck } from \"./leaveRequestMembership\";\n\n/** What a shown leave request is checked against. */\nexport interface LeaveRequestVerifier {\n readonly resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n readonly membershipIsCurrent: LeaveRequestMembershipCheck;\n}\n\n/**\n * The user as shown, with its leave request kept only when the member signed\n * it during their current membership. Every directory user a client presents\n * passes through here, whether a read model or a mutation response carried\n * it.\n */\nexport async function withVerifiedDirectoryUser(\n organizationId: string,\n user: OrganizationDirectoryUser,\n verifier: LeaveRequestVerifier,\n): Promise<OrganizationDirectoryUser> {\n const request = user.leaveRequest;\n if (!request) return user;\n // The member's pinned identity, never the key the directory served beside\n // the request: a server that could swap both could forge any member's ask.\n const identity = await verifier\n .resolveTrustedUserIdentity(user.userId)\n .catch(() => null);\n const signed =\n identity !== null &&\n request.organizationId === organizationId &&\n request.userId === user.userId &&\n isValidOrganizationLeaveRequest(request, identity.signingPublicKey);\n // A genuine request from a membership that has since ended would otherwise\n // look current to an administrator once the member rejoined.\n const verified = signed && (await verifier.membershipIsCurrent(request));\n return verified ? user : { ...user, leaveRequest: null };\n}\n\n/**\n * Keeps only leave requests their member signed during their current\n * membership, checked against the member's trusted identity and this device's\n * verified Members history; any other is shown as no request at all.\n */\nexport async function withVerifiedLeaveRequests<\n Result extends OrganizationDirectoryAndGroups | null | undefined,\n>(result: Result, verifier: LeaveRequestVerifier): Promise<Result> {\n if (!result?.directory.users.some((user) => user.leaveRequest)) {\n return result;\n }\n const { organizationId } = result.directory;\n return {\n ...result,\n directory: {\n ...result.directory,\n users: await Promise.all(\n result.directory.users.map((user) =>\n withVerifiedDirectoryUser(organizationId, user, verifier),\n ),\n ),\n },\n };\n}\n\n/** The user detail counterpart of withVerifiedLeaveRequests. */\nexport async function withVerifiedUserLeaveRequest(\n detail: OrganizationUserDetail | null,\n organizationId: string,\n verifier: LeaveRequestVerifier,\n): Promise<OrganizationUserDetail | null> {\n if (!detail?.user.leaveRequest) return detail;\n return {\n ...detail,\n user: await withVerifiedDirectoryUser(\n organizationId,\n detail.user,\n verifier,\n ),\n };\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { KeyingVerificationError, } from "../../../internal/crypto/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ProjectionDependencyUnavailableError } from "../../../data/keyingProjectionVerification/dependencyUnavailable.js";
|
|
3
|
+
import { externalAuthorityCouldAuthorize, verifyPrincipalPolicyBundleWithExternalOrganizationAdmins, } from "../../../data/principals/principalPolicyAdminSigners.js";
|
|
3
4
|
import { collectPrincipalPolicySignerPublicKeys, principalPolicyStates, } from "../policyVerification.js";
|
|
4
5
|
function getBundleReferenceFailure(reference, bundle) {
|
|
5
6
|
const states = principalPolicyStates(bundle);
|
|
@@ -131,7 +132,24 @@ export async function validatePrincipalPolicyBundleForCache(input) {
|
|
|
131
132
|
expectedReference: input.reference,
|
|
132
133
|
loadExternalAuthority: async () => {
|
|
133
134
|
usedExternalAdminPolicy = true;
|
|
134
|
-
|
|
135
|
+
const authority = (await input.loadExternalAdminPolicy())
|
|
136
|
+
?.externalAuthority;
|
|
137
|
+
if (authority)
|
|
138
|
+
return authority;
|
|
139
|
+
// The loader returns null when the organization's Admins policy could
|
|
140
|
+
// not be fetched, and an honest server always serves it. When that
|
|
141
|
+
// evidence is all the chain lacks, the gap is availability, not a
|
|
142
|
+
// refused signer, so it must not surface as an incident. A chain no
|
|
143
|
+
// evidence could authorize keeps its unauthorized refusal.
|
|
144
|
+
if (await externalAuthorityCouldAuthorize({
|
|
145
|
+
bundle: input.bundle,
|
|
146
|
+
expectedReference: input.reference,
|
|
147
|
+
localCheckpoint: input.localCheckpoint,
|
|
148
|
+
signerPublicKeys: signerPublicKeys.signerPublicKeys,
|
|
149
|
+
})) {
|
|
150
|
+
throw new ProjectionDependencyUnavailableError("Organization Admins evidence is unavailable");
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
135
153
|
},
|
|
136
154
|
localCheckpoint: input.localCheckpoint,
|
|
137
155
|
signerPublicKeys: signerPublicKeys.signerPublicKeys,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principalPolicyCacheValidation.js","sourceRoot":"","sources":["principalPolicyCacheValidation.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type KeyingVerificationCode,\n KeyingVerificationError,\n type PrincipalPolicyCheckpoint,\n type VerifiedPrincipalPolicy,\n} from \"@tearleads/crypto\";\nimport type {\n PrincipalPolicyBundleResponse,\n ReferencedPrincipalStateResponse,\n} from \"@tearleads/validators/response\";\nimport { verifyPrincipalPolicyBundleWithExternalOrganizationAdmins } from \"../../../data/principals/principalPolicyAdminSigners\";\nimport type { TrustedUserIdentityResolver } from \"../../../data/trustedUserIdentity\";\nimport type { VerifiedExternalAdminPolicy } from \"../externalAdminPolicy\";\nimport {\n collectPrincipalPolicySignerPublicKeys,\n type PrincipalPolicySignerPublicKeyLoadErrorCode,\n principalPolicyStates,\n} from \"../policyVerification\";\n\ninterface PrincipalPolicyValidationFailure {\n readonly code: KeyingVerificationCode;\n readonly message: string;\n}\n\nfunction getBundleReferenceFailure(\n reference: ReferencedPrincipalStateResponse,\n bundle: PrincipalPolicyBundleResponse,\n): PrincipalPolicyValidationFailure | null {\n const states = principalPolicyStates(bundle);\n const hasReferencedPrincipal = states.some(\n (state) =>\n state.principalType === reference.principalType &&\n state.principalId === reference.principalId,\n );\n if (!hasReferencedPrincipal) {\n return {\n code: \"object_mismatch\",\n message: \"bundle principal does not match referenced principal\",\n };\n }\n if (\n !states.some(\n (state) =>\n state.principalType === reference.principalType &&\n state.principalId === reference.principalId &&\n state.version === reference.version &&\n state.keyEpoch === reference.keyEpoch &&\n state.stateHash === reference.stateHash &&\n state.keyFingerprint === reference.keyFingerprint,\n )\n ) {\n return {\n code: \"hash_mismatch\",\n message: \"bundle chain does not contain referenced principal\",\n };\n }\n if (\n bundle.currentMemberEnvelopes.principalType !==\n bundle.currentState.principalType ||\n bundle.currentMemberEnvelopes.principalId !==\n bundle.currentState.principalId\n ) {\n return {\n code: \"object_mismatch\",\n message: \"member envelope principal does not match current principal\",\n };\n }\n if (\n bundle.currentMemberEnvelopes.stateHash !== bundle.currentState.stateHash\n ) {\n return {\n code: \"hash_mismatch\",\n message: \"member envelope state hash does not match current principal\",\n };\n }\n if (bundle.currentMemberEnvelopes.epoch !== bundle.currentState.keyEpoch) {\n return {\n code: \"hash_mismatch\",\n message: \"member envelope epoch does not match current principal\",\n };\n }\n if (\n bundle.currentPayload.principalType !== bundle.currentState.principalType ||\n bundle.currentPayload.principalId !== bundle.currentState.principalId\n ) {\n return {\n code: \"object_mismatch\",\n message: \"payload principal does not match current principal\",\n };\n }\n if (bundle.currentPayload.stateHash !== bundle.currentState.stateHash) {\n return {\n code: \"hash_mismatch\",\n message: \"payload state hash does not match current principal\",\n };\n }\n return null;\n}\n\nfunction getCheckpointFailure(\n checkpoint: PrincipalPolicyCheckpoint | null,\n bundle: PrincipalPolicyBundleResponse,\n): PrincipalPolicyValidationFailure | null {\n if (!checkpoint) {\n return null;\n }\n const current = bundle.currentState;\n if (current.version < checkpoint.version) {\n return {\n code: \"rollback\",\n message: \"principal policy state is older than the local checkpoint\",\n };\n }\n if (\n current.version === checkpoint.version &&\n current.stateHash !== checkpoint.stateHash\n ) {\n return {\n code: \"equivocation\",\n message: \"principal policy state conflicts with the local checkpoint\",\n };\n }\n if (current.version === checkpoint.version) {\n return null;\n }\n const checkpointState = principalPolicyStates(bundle).find(\n (state) => state.version === checkpoint.version,\n );\n return checkpointState?.stateHash === checkpoint.stateHash\n ? null\n : {\n code: \"stale_predecessor\",\n message: \"principal policy chain does not extend the local checkpoint\",\n };\n}\n\nfunction signerPublicKeyLoadFailure(\n code: PrincipalPolicySignerPublicKeyLoadErrorCode,\n): PrincipalPolicyValidationFailure {\n switch (code) {\n case \"fingerprint-mismatch\":\n return {\n code: \"signer_mismatch\",\n message: \"signer key fingerprint does not match state signer\",\n };\n case \"not-found\":\n // The identity source throws for transport failures, so an absent\n // signer here is the server asserting the cited user does not exist.\n return {\n code: \"missing_dependency\",\n message: \"signer identity is unknown to the server\",\n };\n }\n}\n\nfunction verificationError(\n failure: PrincipalPolicyValidationFailure,\n): KeyingVerificationError {\n return new KeyingVerificationError(failure.code, failure.message);\n}\n\nexport async function validatePrincipalPolicyBundleForCache(input: {\n readonly bundle: PrincipalPolicyBundleResponse;\n readonly loadExternalAdminPolicy: () => Promise<VerifiedExternalAdminPolicy | null>;\n readonly localCheckpoint: PrincipalPolicyCheckpoint | null;\n readonly reference: ReferencedPrincipalStateResponse;\n readonly resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n}): Promise<\n | {\n readonly externalAdminPolicy: VerifiedExternalAdminPolicy | null;\n readonly ok: true;\n readonly policy: VerifiedPrincipalPolicy;\n }\n | { readonly error: KeyingVerificationError; readonly ok: false }\n> {\n const referenceFailure = getBundleReferenceFailure(\n input.reference,\n input.bundle,\n );\n if (referenceFailure) {\n return { error: verificationError(referenceFailure), ok: false };\n }\n const checkpointFailure = getCheckpointFailure(\n input.localCheckpoint,\n input.bundle,\n );\n if (checkpointFailure) {\n return { error: verificationError(checkpointFailure), ok: false };\n }\n const signerPublicKeys = await collectPrincipalPolicySignerPublicKeys({\n bundle: input.bundle,\n resolveTrustedUserIdentity: input.resolveTrustedUserIdentity,\n });\n if (\"error\" in signerPublicKeys) {\n return {\n error: verificationError(\n signerPublicKeyLoadFailure(signerPublicKeys.error),\n ),\n ok: false,\n };\n }\n\n let usedExternalAdminPolicy = false;\n const verified =\n await verifyPrincipalPolicyBundleWithExternalOrganizationAdmins({\n bundle: input.bundle,\n expectedReference: input.reference,\n loadExternalAuthority: async () => {\n usedExternalAdminPolicy = true;\n return (\n (await input.loadExternalAdminPolicy())?.externalAuthority ?? null\n );\n },\n localCheckpoint: input.localCheckpoint,\n signerPublicKeys: signerPublicKeys.signerPublicKeys,\n });\n return verified.ok\n ? {\n externalAdminPolicy: usedExternalAdminPolicy\n ? await input.loadExternalAdminPolicy()\n : null,\n ok: true,\n policy: verified.value,\n }\n : { error: verified.error, ok: false };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"principalPolicyCacheValidation.js","sourceRoot":"","sources":["principalPolicyCacheValidation.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type KeyingVerificationCode,\n KeyingVerificationError,\n type PrincipalPolicyCheckpoint,\n type VerifiedPrincipalPolicy,\n} from \"@tearleads/crypto\";\nimport type {\n PrincipalPolicyBundleResponse,\n ReferencedPrincipalStateResponse,\n} from \"@tearleads/validators/response\";\nimport { ProjectionDependencyUnavailableError } from \"../../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport {\n externalAuthorityCouldAuthorize,\n verifyPrincipalPolicyBundleWithExternalOrganizationAdmins,\n} from \"../../../data/principals/principalPolicyAdminSigners\";\nimport type { TrustedUserIdentityResolver } from \"../../../data/trustedUserIdentity\";\nimport type { VerifiedExternalAdminPolicy } from \"../externalAdminPolicy\";\nimport {\n collectPrincipalPolicySignerPublicKeys,\n type PrincipalPolicySignerPublicKeyLoadErrorCode,\n principalPolicyStates,\n} from \"../policyVerification\";\n\ninterface PrincipalPolicyValidationFailure {\n readonly code: KeyingVerificationCode;\n readonly message: string;\n}\n\nfunction getBundleReferenceFailure(\n reference: ReferencedPrincipalStateResponse,\n bundle: PrincipalPolicyBundleResponse,\n): PrincipalPolicyValidationFailure | null {\n const states = principalPolicyStates(bundle);\n const hasReferencedPrincipal = states.some(\n (state) =>\n state.principalType === reference.principalType &&\n state.principalId === reference.principalId,\n );\n if (!hasReferencedPrincipal) {\n return {\n code: \"object_mismatch\",\n message: \"bundle principal does not match referenced principal\",\n };\n }\n if (\n !states.some(\n (state) =>\n state.principalType === reference.principalType &&\n state.principalId === reference.principalId &&\n state.version === reference.version &&\n state.keyEpoch === reference.keyEpoch &&\n state.stateHash === reference.stateHash &&\n state.keyFingerprint === reference.keyFingerprint,\n )\n ) {\n return {\n code: \"hash_mismatch\",\n message: \"bundle chain does not contain referenced principal\",\n };\n }\n if (\n bundle.currentMemberEnvelopes.principalType !==\n bundle.currentState.principalType ||\n bundle.currentMemberEnvelopes.principalId !==\n bundle.currentState.principalId\n ) {\n return {\n code: \"object_mismatch\",\n message: \"member envelope principal does not match current principal\",\n };\n }\n if (\n bundle.currentMemberEnvelopes.stateHash !== bundle.currentState.stateHash\n ) {\n return {\n code: \"hash_mismatch\",\n message: \"member envelope state hash does not match current principal\",\n };\n }\n if (bundle.currentMemberEnvelopes.epoch !== bundle.currentState.keyEpoch) {\n return {\n code: \"hash_mismatch\",\n message: \"member envelope epoch does not match current principal\",\n };\n }\n if (\n bundle.currentPayload.principalType !== bundle.currentState.principalType ||\n bundle.currentPayload.principalId !== bundle.currentState.principalId\n ) {\n return {\n code: \"object_mismatch\",\n message: \"payload principal does not match current principal\",\n };\n }\n if (bundle.currentPayload.stateHash !== bundle.currentState.stateHash) {\n return {\n code: \"hash_mismatch\",\n message: \"payload state hash does not match current principal\",\n };\n }\n return null;\n}\n\nfunction getCheckpointFailure(\n checkpoint: PrincipalPolicyCheckpoint | null,\n bundle: PrincipalPolicyBundleResponse,\n): PrincipalPolicyValidationFailure | null {\n if (!checkpoint) {\n return null;\n }\n const current = bundle.currentState;\n if (current.version < checkpoint.version) {\n return {\n code: \"rollback\",\n message: \"principal policy state is older than the local checkpoint\",\n };\n }\n if (\n current.version === checkpoint.version &&\n current.stateHash !== checkpoint.stateHash\n ) {\n return {\n code: \"equivocation\",\n message: \"principal policy state conflicts with the local checkpoint\",\n };\n }\n if (current.version === checkpoint.version) {\n return null;\n }\n const checkpointState = principalPolicyStates(bundle).find(\n (state) => state.version === checkpoint.version,\n );\n return checkpointState?.stateHash === checkpoint.stateHash\n ? null\n : {\n code: \"stale_predecessor\",\n message: \"principal policy chain does not extend the local checkpoint\",\n };\n}\n\nfunction signerPublicKeyLoadFailure(\n code: PrincipalPolicySignerPublicKeyLoadErrorCode,\n): PrincipalPolicyValidationFailure {\n switch (code) {\n case \"fingerprint-mismatch\":\n return {\n code: \"signer_mismatch\",\n message: \"signer key fingerprint does not match state signer\",\n };\n case \"not-found\":\n // The identity source throws for transport failures, so an absent\n // signer here is the server asserting the cited user does not exist.\n return {\n code: \"missing_dependency\",\n message: \"signer identity is unknown to the server\",\n };\n }\n}\n\nfunction verificationError(\n failure: PrincipalPolicyValidationFailure,\n): KeyingVerificationError {\n return new KeyingVerificationError(failure.code, failure.message);\n}\n\nexport async function validatePrincipalPolicyBundleForCache(input: {\n readonly bundle: PrincipalPolicyBundleResponse;\n readonly loadExternalAdminPolicy: () => Promise<VerifiedExternalAdminPolicy | null>;\n readonly localCheckpoint: PrincipalPolicyCheckpoint | null;\n readonly reference: ReferencedPrincipalStateResponse;\n readonly resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n}): Promise<\n | {\n readonly externalAdminPolicy: VerifiedExternalAdminPolicy | null;\n readonly ok: true;\n readonly policy: VerifiedPrincipalPolicy;\n }\n | { readonly error: KeyingVerificationError; readonly ok: false }\n> {\n const referenceFailure = getBundleReferenceFailure(\n input.reference,\n input.bundle,\n );\n if (referenceFailure) {\n return { error: verificationError(referenceFailure), ok: false };\n }\n const checkpointFailure = getCheckpointFailure(\n input.localCheckpoint,\n input.bundle,\n );\n if (checkpointFailure) {\n return { error: verificationError(checkpointFailure), ok: false };\n }\n const signerPublicKeys = await collectPrincipalPolicySignerPublicKeys({\n bundle: input.bundle,\n resolveTrustedUserIdentity: input.resolveTrustedUserIdentity,\n });\n if (\"error\" in signerPublicKeys) {\n return {\n error: verificationError(\n signerPublicKeyLoadFailure(signerPublicKeys.error),\n ),\n ok: false,\n };\n }\n\n let usedExternalAdminPolicy = false;\n const verified =\n await verifyPrincipalPolicyBundleWithExternalOrganizationAdmins({\n bundle: input.bundle,\n expectedReference: input.reference,\n loadExternalAuthority: async () => {\n usedExternalAdminPolicy = true;\n const authority = (await input.loadExternalAdminPolicy())\n ?.externalAuthority;\n if (authority) return authority;\n // The loader returns null when the organization's Admins policy could\n // not be fetched, and an honest server always serves it. When that\n // evidence is all the chain lacks, the gap is availability, not a\n // refused signer, so it must not surface as an incident. A chain no\n // evidence could authorize keeps its unauthorized refusal.\n if (\n await externalAuthorityCouldAuthorize({\n bundle: input.bundle,\n expectedReference: input.reference,\n localCheckpoint: input.localCheckpoint,\n signerPublicKeys: signerPublicKeys.signerPublicKeys,\n })\n ) {\n throw new ProjectionDependencyUnavailableError(\n \"Organization Admins evidence is unavailable\",\n );\n }\n return null;\n },\n localCheckpoint: input.localCheckpoint,\n signerPublicKeys: signerPublicKeys.signerPublicKeys,\n });\n return verified.ok\n ? {\n externalAdminPolicy: usedExternalAdminPolicy\n ? await input.loadExternalAdminPolicy()\n : null,\n ok: true,\n policy: verified.value,\n }\n : { error: verified.error, ok: false };\n}\n"]}
|