476 lines
15 KiB
TypeScript
476 lines
15 KiB
TypeScript
import { apiFetch, apiPatchJson, apiPostJson, type ApiSettings } from "@govoplan/core-webui";
|
|
|
|
export type OrganizationUnitItem = {
|
|
id: string;
|
|
tenant_id: string;
|
|
unit_type_id?: string | null;
|
|
parent_id?: string | null;
|
|
slug: string;
|
|
name: string;
|
|
description?: string | null;
|
|
is_active: boolean;
|
|
settings: Record<string, unknown>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type OrganizationFunctionItem = {
|
|
id: string;
|
|
tenant_id: string;
|
|
function_type_id?: string | null;
|
|
organization_unit_id: string;
|
|
slug: string;
|
|
name: string;
|
|
description?: string | null;
|
|
delegable: boolean;
|
|
act_in_place_allowed: boolean;
|
|
is_active: boolean;
|
|
settings: Record<string, unknown>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type OrganizationModel = {
|
|
units: OrganizationUnitItem[];
|
|
functions: OrganizationFunctionItem[];
|
|
};
|
|
|
|
export type IdentityOption = {
|
|
id: string;
|
|
display_name?: string | null;
|
|
external_subject?: string | null;
|
|
source: string;
|
|
primary_account_id?: string | null;
|
|
account_ids: string[];
|
|
status: string;
|
|
};
|
|
|
|
export type IdentityListResponse = {
|
|
identities: IdentityOption[];
|
|
};
|
|
|
|
export type TypedGroupItem = {
|
|
id: string;
|
|
tenant_id: string;
|
|
key: string;
|
|
name: string;
|
|
group_type: string;
|
|
description?: string | null;
|
|
status: "active" | "inactive";
|
|
source_provider: string;
|
|
source_resource_type?: string | null;
|
|
source_resource_id?: string | null;
|
|
source_revision?: string | null;
|
|
properties: Record<string, unknown>;
|
|
provenance: Record<string, unknown>;
|
|
revision: number;
|
|
created_at?: string | null;
|
|
updated_at?: string | null;
|
|
};
|
|
|
|
export type TypedGroupList = {
|
|
groups: TypedGroupItem[];
|
|
total: number;
|
|
};
|
|
|
|
export type TypedGroupPayload = {
|
|
key: string;
|
|
name: string;
|
|
group_type: string;
|
|
description?: string | null;
|
|
source_provider?: string;
|
|
source_resource_type?: string | null;
|
|
source_resource_id?: string | null;
|
|
source_revision?: string | null;
|
|
properties?: Record<string, unknown>;
|
|
provenance?: Record<string, unknown>;
|
|
};
|
|
|
|
export type TypedGroupUpdatePayload = Partial<TypedGroupPayload> & {
|
|
base_revision: number;
|
|
status?: "active" | "inactive";
|
|
};
|
|
|
|
export type IdentityRelationshipItem = {
|
|
id: string;
|
|
tenant_id: string;
|
|
relationship_kind: string;
|
|
subject_identity_id: string;
|
|
target_group_id?: string | null;
|
|
related_identity_id?: string | null;
|
|
role?: string | null;
|
|
valid_from?: string | null;
|
|
valid_until?: string | null;
|
|
status: "active" | "revoked";
|
|
revoked_at?: string | null;
|
|
revoked_by?: string | null;
|
|
revocation_reason?: string | null;
|
|
expired_event_at?: string | null;
|
|
source_provider: string;
|
|
source_resource_type?: string | null;
|
|
source_resource_id?: string | null;
|
|
source_revision?: string | null;
|
|
properties: Record<string, unknown>;
|
|
provenance: Record<string, unknown>;
|
|
revision: number;
|
|
created_at?: string | null;
|
|
updated_at?: string | null;
|
|
};
|
|
|
|
export type IdentityRelationshipList = {
|
|
relationships: IdentityRelationshipItem[];
|
|
total: number;
|
|
};
|
|
|
|
export type IdentityRelationshipPayload = {
|
|
relationship_kind: string;
|
|
subject_identity_id: string;
|
|
target_group_id?: string | null;
|
|
related_identity_id?: string | null;
|
|
role?: string | null;
|
|
valid_from?: string | null;
|
|
valid_until?: string | null;
|
|
source_provider?: string;
|
|
source_resource_type?: string | null;
|
|
source_resource_id?: string | null;
|
|
source_revision?: string | null;
|
|
properties?: Record<string, unknown>;
|
|
provenance?: Record<string, unknown>;
|
|
};
|
|
|
|
export type IdentityRelationshipUpdatePayload = Omit<
|
|
Partial<IdentityRelationshipPayload>,
|
|
"subject_identity_id"
|
|
> & {
|
|
base_revision: number;
|
|
};
|
|
|
|
export type IdentityRelationshipDecisionItem = {
|
|
relationship: IdentityRelationshipItem;
|
|
included: boolean;
|
|
code: string;
|
|
explanation: string;
|
|
identity_status?: string | null;
|
|
};
|
|
|
|
export type TypedGroupMembershipResolution = {
|
|
group: TypedGroupItem;
|
|
effective_at: string;
|
|
decisions: IdentityRelationshipDecisionItem[];
|
|
identity_ids: string[];
|
|
};
|
|
|
|
export type OrganizationFunctionAssignmentItem = {
|
|
id: string;
|
|
tenant_id: string;
|
|
identity_id: string;
|
|
account_id?: string | null;
|
|
function_id: string;
|
|
organization_unit_id: string;
|
|
applies_to_subunits: boolean;
|
|
source: string;
|
|
delegated_from_assignment_id?: string | null;
|
|
acting_for_account_id?: string | null;
|
|
valid_from?: string | null;
|
|
valid_until?: string | null;
|
|
is_active: boolean;
|
|
settings: Record<string, unknown>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type OrganizationFunctionAssignmentList = {
|
|
assignments: OrganizationFunctionAssignmentItem[];
|
|
total?: number;
|
|
page?: number;
|
|
page_size?: number;
|
|
pages?: number;
|
|
};
|
|
|
|
export type IdmSettings = {
|
|
tenant_id: string;
|
|
require_assignment_change_requests: boolean;
|
|
audit_detail_level: "summary" | "standard" | "full";
|
|
change_retention_days?: number | null;
|
|
settings: Record<string, unknown>;
|
|
created_at?: string | null;
|
|
updated_at?: string | null;
|
|
};
|
|
|
|
export type OrganizationFunctionAssignmentPayload = {
|
|
identity_id: string;
|
|
function_id: string;
|
|
account_id?: string | null;
|
|
applies_to_subunits?: boolean;
|
|
source?: string;
|
|
delegated_from_assignment_id?: string | null;
|
|
acting_for_account_id?: string | null;
|
|
valid_from?: string | null;
|
|
valid_until?: string | null;
|
|
is_active?: boolean;
|
|
settings?: Record<string, unknown>;
|
|
change_request_id?: string | null;
|
|
governance_override_reason?: string | null;
|
|
governance_override_evidence?: string[];
|
|
};
|
|
|
|
export type IdmSettingsPayload = Partial<Pick<IdmSettings, "require_assignment_change_requests" | "audit_detail_level" | "change_retention_days" | "settings">>;
|
|
|
|
export type FunctionAssignmentChangeKind = "request" | "grant";
|
|
export type FunctionAssignmentChangeAction = "approve" | "reject" | "accept" | "request_changes" | "respond" | "withdraw" | "recover";
|
|
|
|
export type FunctionAssignmentChangeEvent = {
|
|
id: string;
|
|
sequence: number;
|
|
action: string;
|
|
from_state?: string | null;
|
|
to_state: string;
|
|
actor_account_id?: string | null;
|
|
actor_identity_id?: string | null;
|
|
comment?: string | null;
|
|
evidence: string[];
|
|
policy_decision: Record<string, unknown>;
|
|
workflow_step_id?: string | null;
|
|
details: Record<string, unknown>;
|
|
created_at: string;
|
|
};
|
|
|
|
export type FunctionAssignmentChange = {
|
|
id: string;
|
|
tenant_id: string;
|
|
kind: FunctionAssignmentChangeKind;
|
|
state: string;
|
|
profile: string;
|
|
function_id: string;
|
|
organization_unit_id: string;
|
|
candidate_identity_id: string;
|
|
candidate_account_id?: string | null;
|
|
initiator_account_id: string;
|
|
initiator_identity_id?: string | null;
|
|
justification: string;
|
|
evidence: string[];
|
|
requested_valid_from?: string | null;
|
|
requested_valid_until?: string | null;
|
|
required_steps: string[];
|
|
completed_steps: string[];
|
|
policy_decision: Record<string, unknown>;
|
|
workflow_definition_revision?: number | null;
|
|
workflow_definition_hash?: string | null;
|
|
workflow_instance_id?: string | null;
|
|
resulting_assignment_id?: string | null;
|
|
expires_at?: string | null;
|
|
review_deadline_at?: string | null;
|
|
escalated_at?: string | null;
|
|
escalation_from_state?: string | null;
|
|
escalation_target_function_id?: string | null;
|
|
outcome_reason?: string | null;
|
|
resource_revision: number;
|
|
etag: string;
|
|
metadata: Record<string, unknown>;
|
|
events: FunctionAssignmentChangeEvent[];
|
|
available_actions: FunctionAssignmentChangeAction[];
|
|
availability_reason?: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type FunctionAssignmentChangeList = {
|
|
changes: FunctionAssignmentChange[];
|
|
total: number;
|
|
page: number;
|
|
page_size: number;
|
|
pages: number;
|
|
};
|
|
|
|
export type FunctionAssignmentChangePayload = {
|
|
kind: FunctionAssignmentChangeKind;
|
|
function_id: string;
|
|
candidate_identity_id: string;
|
|
candidate_account_id?: string | null;
|
|
justification: string;
|
|
evidence?: string[];
|
|
requested_valid_from?: string | null;
|
|
requested_valid_until?: string | null;
|
|
applies_to_subunits?: boolean;
|
|
assignment_source?: "governance" | "delegated";
|
|
represented_assignment_id?: string | null;
|
|
idempotency_key: string;
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
export function getOrganizationModel(settings: ApiSettings): Promise<OrganizationModel> {
|
|
return apiFetch<OrganizationModel>(settings, "/api/v1/organizations/model");
|
|
}
|
|
|
|
export async function getOrganizationFunctionAssignments(settings: ApiSettings): Promise<OrganizationFunctionAssignmentList> {
|
|
const pageSize = 500;
|
|
const assignments: OrganizationFunctionAssignmentItem[] = [];
|
|
let total = 0;
|
|
for (let page = 1; ; page += 1) {
|
|
const response = await apiFetch<OrganizationFunctionAssignmentList>(
|
|
settings,
|
|
`/api/v1/idm/organization-function-assignments?page=${page}&page_size=${pageSize}`
|
|
);
|
|
assignments.push(...response.assignments);
|
|
total = response.total ?? assignments.length;
|
|
if (page >= (response.pages ?? 1)) {
|
|
return {
|
|
assignments,
|
|
total,
|
|
page: 1,
|
|
page_size: assignments.length,
|
|
pages: 1
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getIdmSettings(settings: ApiSettings): Promise<IdmSettings> {
|
|
return apiFetch<IdmSettings>(settings, "/api/v1/idm/settings");
|
|
}
|
|
|
|
export function patchIdmSettings(settings: ApiSettings, payload: IdmSettingsPayload): Promise<IdmSettings> {
|
|
return apiPatchJson(settings, "/api/v1/idm/settings", payload);
|
|
}
|
|
|
|
export function searchOrganizationIdentityOptions(
|
|
settings: ApiSettings,
|
|
query = "",
|
|
limit = 50,
|
|
signal?: AbortSignal
|
|
): Promise<IdentityListResponse> {
|
|
const params = new URLSearchParams();
|
|
const trimmed = query.trim();
|
|
if (trimmed) params.set("query", trimmed);
|
|
params.set("limit", String(limit));
|
|
return apiFetch<IdentityListResponse>(settings, `/api/v1/idm/organization-identities?${params.toString()}`, { signal });
|
|
}
|
|
|
|
export function getTypedGroups(
|
|
settings: ApiSettings,
|
|
options: { query?: string; includeInactive?: boolean; limit?: number; signal?: AbortSignal } = {}
|
|
): Promise<TypedGroupList> {
|
|
const params = new URLSearchParams();
|
|
if (options.query?.trim()) params.set("query", options.query.trim());
|
|
if (options.includeInactive) params.set("include_inactive", "true");
|
|
params.set("limit", String(options.limit ?? 1000));
|
|
return apiFetch<TypedGroupList>(settings, `/api/v1/idm/typed-groups?${params.toString()}`, { signal: options.signal });
|
|
}
|
|
|
|
export function createTypedGroup(settings: ApiSettings, payload: TypedGroupPayload): Promise<TypedGroupItem> {
|
|
return apiPostJson(settings, "/api/v1/idm/typed-groups", payload);
|
|
}
|
|
|
|
export function patchTypedGroup(
|
|
settings: ApiSettings,
|
|
groupId: string,
|
|
payload: TypedGroupUpdatePayload
|
|
): Promise<TypedGroupItem> {
|
|
return apiPatchJson(settings, `/api/v1/idm/typed-groups/${encodeURIComponent(groupId)}`, payload);
|
|
}
|
|
|
|
export function getIdentityRelationships(
|
|
settings: ApiSettings,
|
|
options: { includeRevoked?: boolean; identityId?: string; groupId?: string; relationshipKind?: string; limit?: number } = {}
|
|
): Promise<IdentityRelationshipList> {
|
|
const params = new URLSearchParams();
|
|
if (options.includeRevoked) params.set("include_revoked", "true");
|
|
if (options.identityId) params.set("identity_id", options.identityId);
|
|
if (options.groupId) params.set("group_id", options.groupId);
|
|
if (options.relationshipKind) params.set("relationship_kind", options.relationshipKind);
|
|
params.set("limit", String(options.limit ?? 1000));
|
|
return apiFetch<IdentityRelationshipList>(settings, `/api/v1/idm/relationships?${params.toString()}`);
|
|
}
|
|
|
|
export function createIdentityRelationship(
|
|
settings: ApiSettings,
|
|
payload: IdentityRelationshipPayload
|
|
): Promise<IdentityRelationshipItem> {
|
|
return apiPostJson(settings, "/api/v1/idm/relationships", payload);
|
|
}
|
|
|
|
export function patchIdentityRelationship(
|
|
settings: ApiSettings,
|
|
relationshipId: string,
|
|
payload: IdentityRelationshipUpdatePayload
|
|
): Promise<IdentityRelationshipItem> {
|
|
return apiPatchJson(settings, `/api/v1/idm/relationships/${encodeURIComponent(relationshipId)}`, payload);
|
|
}
|
|
|
|
export function revokeIdentityRelationship(
|
|
settings: ApiSettings,
|
|
relationship: Pick<IdentityRelationshipItem, "id" | "revision">,
|
|
reason: string
|
|
): Promise<IdentityRelationshipItem> {
|
|
return apiPostJson(settings, `/api/v1/idm/relationships/${encodeURIComponent(relationship.id)}/revoke`, {
|
|
base_revision: relationship.revision,
|
|
reason
|
|
});
|
|
}
|
|
|
|
export function resolveTypedGroupMemberships(
|
|
settings: ApiSettings,
|
|
groupId: string,
|
|
options: { effectiveAt?: string; relationshipKinds?: string[] } = {}
|
|
): Promise<TypedGroupMembershipResolution> {
|
|
const params = new URLSearchParams();
|
|
if (options.effectiveAt) params.set("effective_at", options.effectiveAt);
|
|
for (const kind of options.relationshipKinds ?? ["member"]) params.append("relationship_kind", kind);
|
|
return apiFetch<TypedGroupMembershipResolution>(
|
|
settings,
|
|
`/api/v1/idm/typed-groups/${encodeURIComponent(groupId)}/memberships?${params.toString()}`
|
|
);
|
|
}
|
|
|
|
export function createOrganizationFunctionAssignment(
|
|
settings: ApiSettings,
|
|
payload: OrganizationFunctionAssignmentPayload
|
|
): Promise<OrganizationFunctionAssignmentItem> {
|
|
return apiPostJson(settings, "/api/v1/idm/organization-function-assignments", payload);
|
|
}
|
|
|
|
export function patchOrganizationFunctionAssignment(
|
|
settings: ApiSettings,
|
|
id: string,
|
|
payload: Partial<OrganizationFunctionAssignmentPayload>
|
|
): Promise<OrganizationFunctionAssignmentItem> {
|
|
return apiPatchJson(settings, `/api/v1/idm/organization-function-assignments/${encodeURIComponent(id)}`, payload);
|
|
}
|
|
|
|
export function getFunctionAssignmentChanges(settings: ApiSettings): Promise<FunctionAssignmentChangeList> {
|
|
return apiFetch<FunctionAssignmentChangeList>(settings, "/api/v1/idm/function-assignment-changes?page_size=200");
|
|
}
|
|
|
|
export function getFunctionAssignmentChange(settings: ApiSettings, id: string): Promise<FunctionAssignmentChange> {
|
|
return apiFetch<FunctionAssignmentChange>(settings, `/api/v1/idm/function-assignment-changes/${encodeURIComponent(id)}`);
|
|
}
|
|
|
|
export function createFunctionAssignmentChange(
|
|
settings: ApiSettings,
|
|
payload: FunctionAssignmentChangePayload
|
|
): Promise<FunctionAssignmentChange> {
|
|
return apiPostJson(settings, "/api/v1/idm/function-assignment-changes", payload);
|
|
}
|
|
|
|
export function actOnFunctionAssignmentChange(
|
|
settings: ApiSettings,
|
|
change: FunctionAssignmentChange,
|
|
action: FunctionAssignmentChangeAction,
|
|
comment?: string
|
|
): Promise<FunctionAssignmentChange> {
|
|
return apiFetch<FunctionAssignmentChange>(
|
|
settings,
|
|
`/api/v1/idm/function-assignment-changes/${encodeURIComponent(change.id)}/actions`,
|
|
{
|
|
method: "POST",
|
|
headers: { "If-Match": change.etag },
|
|
body: JSON.stringify({
|
|
action,
|
|
base_revision: change.resource_revision,
|
|
comment: comment?.trim() || null,
|
|
evidence: []
|
|
})
|
|
}
|
|
);
|
|
}
|