feat(idm): administer typed identity relationships
Module Package Release / publish-packages (push) Successful in 12s

This commit is contained in:
2026-08-21 22:04:06 +02:00
parent 65ff14a613
commit b0eda35195
15 changed files with 1590 additions and 36 deletions
+194 -2
View File
@@ -49,6 +49,117 @@ 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;
@@ -218,12 +329,93 @@ export function patchIdmSettings(settings: ApiSettings, payload: IdmSettingsPayl
return apiPatchJson(settings, "/api/v1/idm/settings", payload);
}
export function searchOrganizationIdentityOptions(settings: ApiSettings, query = "", limit = 50): Promise<IdentityListResponse> {
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()}`);
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(