Fix bugs
All checks were successful
Build and Push / build (push) Successful in 1m30s

This commit is contained in:
2026-04-29 12:16:00 +02:00
parent 542a607b53
commit c46f27edef
13 changed files with 1017 additions and 61 deletions

View File

@@ -45,6 +45,18 @@ export interface OrgMember {
* yet — appears as "no role" in the UI.
*/
roles: string[];
/**
* The ZITADEL authorization ID backing the role assignment, if any.
* Used by the team UI's role-change flow to call UpdateAuthorization.
* Empty string if the member has no authorization (orphan / pre-Slice-7
* legacy / mid-invite race).
*
* If a member somehow holds multiple authorization rows (not expected
* at our project-grant scope of [owner, user]), only the first is
* surfaced here. The team page joins per-user, so the UI sees one
* row per member; mutations target that authorization.
*/
authorizationId: string;
}
/**
@@ -61,14 +73,22 @@ export async function getOrgMembers(orgId: string): Promise<OrgMember[]> {
listOrgAuthorizations(orgId),
]);
// Group authorizations by userId — one user could in principle hold
// multiple authorization rows (one per role assigned at different
// times). Flatten roleKeys.
// Group authorizations by userId. We track BOTH the union of role
// keys (for display) and the first authorizationId we see (for the
// role-change flow). A user could in principle hold multiple
// authorization rows, but at our project-grant scope of [owner, user]
// each member ends up with exactly one. If a future config produces
// multi-row members the UI surfaces the first; cleanup belongs in
// ZITADEL Console.
const rolesByUser = new Map<string, Set<string>>();
const authIdByUser = new Map<string, string>();
for (const a of auths) {
const set = rolesByUser.get(a.userId) ?? new Set<string>();
for (const r of a.roleKeys) set.add(r);
rolesByUser.set(a.userId, set);
if (!authIdByUser.has(a.userId) && a.authorizationId) {
authIdByUser.set(a.userId, a.authorizationId);
}
}
return users.map((u) => ({
@@ -78,6 +98,7 @@ export async function getOrgMembers(orgId: string): Promise<OrgMember[]> {
givenName: u.givenName,
familyName: u.familyName,
roles: Array.from(rolesByUser.get(u.userId) ?? []),
authorizationId: authIdByUser.get(u.userId) ?? "",
}));
}

View File

@@ -250,6 +250,35 @@ export async function createAuthorization(params: {
);
}
/**
* Replace the role keys on an existing authorization.
*
* Connect RPC: zitadel.authorization.v2.AuthorizationService/UpdateAuthorization
*
* Replace, not merge: any role keys previously held by this authorization
* that are NOT in the new list are revoked. Pass the complete desired
* role set every time. The authorization's user/org/project bindings
* are immutable — to move a user to a different org, delete and recreate.
*
* Used by the team UI's role change flow (Bug 25). For new role grants
* use {@link createAuthorization}; for revocations of an entire role
* assignment, delete the authorization (not yet exposed; not needed at
* the time of writing).
*/
export async function updateAuthorizationRoles(
authorizationId: string,
roleKeys: string[]
): Promise<{ changeDate?: string }> {
return connectRpc<{ changeDate?: string }>(
"zitadel.authorization.v2.AuthorizationService",
"UpdateAuthorization",
{
id: authorizationId,
roleKeys,
}
);
}
// ---------------------------------------------------------------------------
// Delete Organization (for rollback on partial failure)
// ---------------------------------------------------------------------------