mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* feat: add organization.passwordReset PBAC permission Allow org admins/owners to reset passwords for members of their organization via a new PBAC permission. Previously this was only available to system-level admins. - Add PasswordReset to CustomAction enum and PERMISSION_REGISTRY - Create migration to grant permission to admin_role (owner has wildcard) - Add org-scoped tRPC endpoint using createOrgPbacProcedure - Handler validates org membership, prevents self-targeting, and blocks resetting owner passwords - Wire permission through MemberPermissions, getOrgMembersPageData, and the org members table UI dropdown Co-Authored-By: Claude Opus 4.5 <[email protected]> * fix: use targeted select in org password reset to avoid over-fetching Replace findById with findForPasswordReset repository method that only selects email, name, and locale instead of the full userSelect which includes sensitive fields like twoFactorSecret and backupCodes. Co-Authored-By: Claude Opus 4.5 <[email protected]> * test: add unit tests for sendPasswordReset handler Co-Authored-By: Claude Opus 4.5 <[email protected]> --------- Co-authored-by: Claude Opus 4.5 <[email protected]>
90 lines
2.6 KiB
TypeScript
90 lines
2.6 KiB
TypeScript
import { MembershipRole } from "@calcom/prisma/enums";
|
|
import { CustomAction, Resource } from "../domain/types/permission-registry";
|
|
import { getSpecificPermissions } from "./resource-permissions";
|
|
|
|
export interface MemberPermissions {
|
|
canListMembers: boolean;
|
|
canInvite: boolean;
|
|
canChangeMemberRole: boolean;
|
|
canRemove: boolean;
|
|
canImpersonate: boolean;
|
|
canResetPassword?: boolean;
|
|
canEditAttributesForUser?: boolean;
|
|
canViewAttributes?: boolean;
|
|
}
|
|
|
|
interface TeamWithMembership {
|
|
id: number;
|
|
isPrivate: boolean;
|
|
membership: {
|
|
role: MembershipRole;
|
|
accepted: boolean;
|
|
};
|
|
}
|
|
|
|
interface GetTeamMemberPermissionsOptions {
|
|
userId: number;
|
|
team: TeamWithMembership;
|
|
}
|
|
|
|
/**
|
|
* Gets team member permissions using PBAC or fallback to role-based permissions.
|
|
*/
|
|
export async function getTeamMemberPermissions({
|
|
userId,
|
|
team,
|
|
}: GetTeamMemberPermissionsOptions): Promise<MemberPermissions> {
|
|
// Determine fallback roles for ListMembers based on team privacy
|
|
const fallbackRolesCanListMembers: MembershipRole[] = [MembershipRole.ADMIN, MembershipRole.OWNER];
|
|
if (!team.isPrivate) {
|
|
fallbackRolesCanListMembers.push(MembershipRole.MEMBER);
|
|
}
|
|
|
|
// Get specific PBAC permissions for team member actions
|
|
const permissions = await getSpecificPermissions({
|
|
userId,
|
|
teamId: team.id,
|
|
resource: Resource.Team,
|
|
userRole: team.membership.role,
|
|
actions: [
|
|
CustomAction.Invite,
|
|
CustomAction.ChangeMemberRole,
|
|
CustomAction.Remove,
|
|
CustomAction.ListMembers,
|
|
CustomAction.ListMembersPrivate,
|
|
CustomAction.Impersonate,
|
|
],
|
|
fallbackRoles: {
|
|
[CustomAction.Invite]: {
|
|
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
|
|
},
|
|
[CustomAction.ChangeMemberRole]: {
|
|
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
|
|
},
|
|
[CustomAction.Remove]: {
|
|
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
|
|
},
|
|
[CustomAction.ListMembers]: {
|
|
roles: fallbackRolesCanListMembers,
|
|
},
|
|
[CustomAction.Impersonate]: {
|
|
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
|
|
},
|
|
[CustomAction.ListMembersPrivate]: {
|
|
roles: fallbackRolesCanListMembers,
|
|
},
|
|
},
|
|
});
|
|
|
|
// Map specific permissions to member actions
|
|
return {
|
|
canListMembers: team.isPrivate
|
|
? permissions[CustomAction.ListMembersPrivate]
|
|
: permissions[CustomAction.ListMembers],
|
|
canInvite: permissions[CustomAction.Invite],
|
|
canChangeMemberRole: permissions[CustomAction.ChangeMemberRole],
|
|
canRemove: permissions[CustomAction.Remove],
|
|
canImpersonate: permissions[CustomAction.Impersonate],
|
|
};
|
|
}
|