mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-02 16:15:39 +00:00
refactor: keep the share rights presets in one module for every share dialog
This commit is contained in:
@@ -6,130 +6,11 @@ import { Button } from "@/components/ui/button";
|
||||
import { Avatar } from "@/components/ui/avatar";
|
||||
import { X, Loader2, UserPlus, Trash2, Users, ChevronDown } from "@/components/icons";
|
||||
import type { IJMAPClient } from "@/lib/jmap/client-interface";
|
||||
import type { Principal, PrincipalDirectoryStatus, CalendarRights, AddressBookRights, FileNodeRights, MailboxRights } from "@/lib/jmap/types";
|
||||
import type { Principal, PrincipalDirectoryStatus } from "@/lib/jmap/types";
|
||||
import { detectPreset, presetRights, type AnyRights, type RolePreset, type ShareKind } from "@/lib/jmap/share-rights";
|
||||
import { loadPrincipalDirectory } from "@/lib/jmap/principal-directory";
|
||||
import { toast } from "@/stores/toast-store";
|
||||
|
||||
type ShareKind = "calendar" | "addressBook" | "file" | "mailbox";
|
||||
type AnyRights = CalendarRights | AddressBookRights | FileNodeRights | MailboxRights;
|
||||
|
||||
type RolePreset = "freeBusy" | "read" | "readWrite" | "manager" | "custom";
|
||||
|
||||
const CALENDAR_PRESETS: Record<Exclude<RolePreset, "custom">, CalendarRights> = {
|
||||
freeBusy: {
|
||||
mayReadFreeBusy: true, mayReadItems: false, mayWriteAll: false, mayWriteOwn: false,
|
||||
mayUpdatePrivate: false, mayRSVP: false, mayShare: false, mayDelete: false,
|
||||
},
|
||||
read: {
|
||||
mayReadFreeBusy: true, mayReadItems: true, mayWriteAll: false, mayWriteOwn: false,
|
||||
mayUpdatePrivate: false, mayRSVP: false, mayShare: false, mayDelete: false,
|
||||
},
|
||||
readWrite: {
|
||||
mayReadFreeBusy: true, mayReadItems: true, mayWriteAll: true, mayWriteOwn: true,
|
||||
mayUpdatePrivate: true, mayRSVP: true, mayShare: false, mayDelete: false,
|
||||
},
|
||||
manager: {
|
||||
mayReadFreeBusy: true, mayReadItems: true, mayWriteAll: true, mayWriteOwn: true,
|
||||
mayUpdatePrivate: true, mayRSVP: true, mayShare: true, mayDelete: true,
|
||||
},
|
||||
};
|
||||
|
||||
const ADDRESS_BOOK_PRESETS: Record<Exclude<RolePreset, "custom" | "freeBusy">, AddressBookRights> = {
|
||||
read: { mayRead: true, mayWrite: false, mayShare: false, mayDelete: false },
|
||||
readWrite: { mayRead: true, mayWrite: true, mayShare: false, mayDelete: false },
|
||||
manager: { mayRead: true, mayWrite: true, mayShare: true, mayDelete: true },
|
||||
};
|
||||
|
||||
const FILE_PRESETS: Record<Exclude<RolePreset, "custom" | "freeBusy">, FileNodeRights> = {
|
||||
read: {
|
||||
mayRead: true, mayAddChildren: false, mayRename: false,
|
||||
mayDelete: false, mayModifyContent: false, mayShare: false,
|
||||
},
|
||||
readWrite: {
|
||||
mayRead: true, mayAddChildren: true, mayRename: true,
|
||||
mayDelete: true, mayModifyContent: true, mayShare: false,
|
||||
},
|
||||
manager: {
|
||||
mayRead: true, mayAddChildren: true, mayRename: true,
|
||||
mayDelete: true, mayModifyContent: true, mayShare: true,
|
||||
},
|
||||
};
|
||||
|
||||
// Mail folder rights (RFC 8621 §2 + mail:share). "Read & write" lets the
|
||||
// grantee file, flag and remove mail; "Manager" additionally lets them
|
||||
// rename/delete the folder, create subfolders, send as it and re-share.
|
||||
const MAILBOX_PRESETS: Record<Exclude<RolePreset, "custom" | "freeBusy">, MailboxRights> = {
|
||||
read: {
|
||||
mayReadItems: true, mayAddItems: false, mayRemoveItems: false, maySetSeen: true,
|
||||
maySetKeywords: false, mayCreateChild: false, mayRename: false, mayDelete: false,
|
||||
maySubmit: false, mayShare: false,
|
||||
},
|
||||
readWrite: {
|
||||
mayReadItems: true, mayAddItems: true, mayRemoveItems: true, maySetSeen: true,
|
||||
maySetKeywords: true, mayCreateChild: false, mayRename: false, mayDelete: false,
|
||||
maySubmit: false, mayShare: false,
|
||||
},
|
||||
manager: {
|
||||
mayReadItems: true, mayAddItems: true, mayRemoveItems: true, maySetSeen: true,
|
||||
maySetKeywords: true, mayCreateChild: true, mayRename: true, mayDelete: true,
|
||||
maySubmit: true, mayShare: true,
|
||||
},
|
||||
};
|
||||
|
||||
function detectMailboxPreset(r: MailboxRights): RolePreset {
|
||||
for (const [name, preset] of Object.entries(MAILBOX_PRESETS) as [Exclude<RolePreset, "custom" | "freeBusy">, MailboxRights][]) {
|
||||
const keys = Object.keys(preset) as (keyof MailboxRights)[];
|
||||
if (keys.every((k) => preset[k] === (r[k] ?? false))) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return "custom";
|
||||
}
|
||||
|
||||
function detectCalendarPreset(r: CalendarRights): RolePreset {
|
||||
for (const [name, preset] of Object.entries(CALENDAR_PRESETS) as [Exclude<RolePreset, "custom">, CalendarRights][]) {
|
||||
if ((Object.keys(preset) as (keyof CalendarRights)[]).every((k) => preset[k] === r[k])) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return "custom";
|
||||
}
|
||||
|
||||
function detectAddressBookPreset(r: AddressBookRights): RolePreset {
|
||||
for (const [name, preset] of Object.entries(ADDRESS_BOOK_PRESETS) as [Exclude<RolePreset, "custom" | "freeBusy">, AddressBookRights][]) {
|
||||
const keys = Object.keys(preset) as (keyof AddressBookRights)[];
|
||||
if (keys.every((k) => preset[k] === (r[k] ?? false))) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return "custom";
|
||||
}
|
||||
|
||||
function detectFilePreset(r: FileNodeRights): RolePreset {
|
||||
for (const [name, preset] of Object.entries(FILE_PRESETS) as [Exclude<RolePreset, "custom" | "freeBusy">, FileNodeRights][]) {
|
||||
const keys = Object.keys(preset) as (keyof FileNodeRights)[];
|
||||
if (keys.every((k) => preset[k] === (r[k] ?? false))) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return "custom";
|
||||
}
|
||||
|
||||
function presetRights(kind: ShareKind, preset: RolePreset): AnyRights | undefined {
|
||||
if (preset === "custom") return undefined;
|
||||
if (kind === "calendar") return CALENDAR_PRESETS[preset as keyof typeof CALENDAR_PRESETS];
|
||||
if (kind === "file") return FILE_PRESETS[preset as keyof typeof FILE_PRESETS];
|
||||
if (kind === "mailbox") return MAILBOX_PRESETS[preset as keyof typeof MAILBOX_PRESETS];
|
||||
return ADDRESS_BOOK_PRESETS[preset as keyof typeof ADDRESS_BOOK_PRESETS];
|
||||
}
|
||||
|
||||
function detectPreset(kind: ShareKind, rights: AnyRights): RolePreset {
|
||||
if (kind === "calendar") return detectCalendarPreset(rights as CalendarRights);
|
||||
if (kind === "file") return detectFilePreset(rights as FileNodeRights);
|
||||
if (kind === "mailbox") return detectMailboxPreset(rights as MailboxRights);
|
||||
return detectAddressBookPreset(rights as AddressBookRights);
|
||||
}
|
||||
|
||||
interface ShareCollectionDialogProps {
|
||||
client: IJMAPClient;
|
||||
kind: ShareKind;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* The rights Bulwark grants when it shares a folder, calendar, address book
|
||||
* or file (`shareWith/<principalId>`), as named presets, and the preset a
|
||||
* stored grant amounts to.
|
||||
*/
|
||||
|
||||
import type { AddressBookRights, CalendarRights, FileNodeRights, MailboxRights } from './types';
|
||||
|
||||
export type ShareKind = 'calendar' | 'addressBook' | 'file' | 'mailbox';
|
||||
export type AnyRights = CalendarRights | AddressBookRights | FileNodeRights | MailboxRights;
|
||||
export type RolePreset = 'freeBusy' | 'read' | 'readWrite' | 'manager' | 'custom';
|
||||
type NamedPreset = Exclude<RolePreset, 'custom' | 'freeBusy'>;
|
||||
|
||||
export const CALENDAR_PRESETS: Record<Exclude<RolePreset, 'custom'>, CalendarRights> = {
|
||||
freeBusy: {
|
||||
mayReadFreeBusy: true, mayReadItems: false, mayWriteAll: false, mayWriteOwn: false,
|
||||
mayUpdatePrivate: false, mayRSVP: false, mayShare: false, mayDelete: false,
|
||||
},
|
||||
read: {
|
||||
mayReadFreeBusy: true, mayReadItems: true, mayWriteAll: false, mayWriteOwn: false,
|
||||
mayUpdatePrivate: false, mayRSVP: false, mayShare: false, mayDelete: false,
|
||||
},
|
||||
readWrite: {
|
||||
mayReadFreeBusy: true, mayReadItems: true, mayWriteAll: true, mayWriteOwn: true,
|
||||
mayUpdatePrivate: true, mayRSVP: true, mayShare: false, mayDelete: false,
|
||||
},
|
||||
manager: {
|
||||
mayReadFreeBusy: true, mayReadItems: true, mayWriteAll: true, mayWriteOwn: true,
|
||||
mayUpdatePrivate: true, mayRSVP: true, mayShare: true, mayDelete: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const ADDRESS_BOOK_PRESETS: Record<NamedPreset, AddressBookRights> = {
|
||||
read: { mayRead: true, mayWrite: false, mayShare: false, mayDelete: false },
|
||||
readWrite: { mayRead: true, mayWrite: true, mayShare: false, mayDelete: false },
|
||||
manager: { mayRead: true, mayWrite: true, mayShare: true, mayDelete: true },
|
||||
};
|
||||
|
||||
export const FILE_PRESETS: Record<NamedPreset, FileNodeRights> = {
|
||||
read: {
|
||||
mayRead: true, mayAddChildren: false, mayRename: false,
|
||||
mayDelete: false, mayModifyContent: false, mayShare: false,
|
||||
},
|
||||
readWrite: {
|
||||
mayRead: true, mayAddChildren: true, mayRename: true,
|
||||
mayDelete: true, mayModifyContent: true, mayShare: false,
|
||||
},
|
||||
manager: {
|
||||
mayRead: true, mayAddChildren: true, mayRename: true,
|
||||
mayDelete: true, mayModifyContent: true, mayShare: true,
|
||||
},
|
||||
};
|
||||
|
||||
// Mail folder rights (RFC 8621 §2 + mail:share). "Read & write" lets the
|
||||
// grantee file, flag and remove mail; "Manager" additionally lets them
|
||||
// rename/delete the folder, create subfolders, send as it and re-share.
|
||||
export const MAILBOX_PRESETS: Record<NamedPreset, MailboxRights> = {
|
||||
read: {
|
||||
mayReadItems: true, mayAddItems: false, mayRemoveItems: false, maySetSeen: true,
|
||||
maySetKeywords: false, mayCreateChild: false, mayRename: false, mayDelete: false,
|
||||
maySubmit: false, mayShare: false,
|
||||
},
|
||||
readWrite: {
|
||||
mayReadItems: true, mayAddItems: true, mayRemoveItems: true, maySetSeen: true,
|
||||
maySetKeywords: true, mayCreateChild: false, mayRename: false, mayDelete: false,
|
||||
maySubmit: false, mayShare: false,
|
||||
},
|
||||
manager: {
|
||||
mayReadItems: true, mayAddItems: true, mayRemoveItems: true, maySetSeen: true,
|
||||
maySetKeywords: true, mayCreateChild: true, mayRename: true, mayDelete: true,
|
||||
maySubmit: true, mayShare: true,
|
||||
},
|
||||
};
|
||||
|
||||
function presetsFor(kind: ShareKind): Partial<Record<RolePreset, AnyRights>> {
|
||||
if (kind === 'calendar') return CALENDAR_PRESETS;
|
||||
if (kind === 'file') return FILE_PRESETS;
|
||||
if (kind === 'mailbox') return MAILBOX_PRESETS;
|
||||
return ADDRESS_BOOK_PRESETS;
|
||||
}
|
||||
|
||||
function matches(preset: AnyRights, rights: AnyRights): boolean {
|
||||
const have = rights as unknown as Record<string, boolean | undefined>;
|
||||
return Object.entries(preset).every(([key, want]) => {
|
||||
const got = have[key] ?? false;
|
||||
return got === want;
|
||||
});
|
||||
}
|
||||
|
||||
/** The rights a preset grants; undefined for "custom". */
|
||||
export function presetRights(kind: ShareKind, preset: RolePreset): AnyRights | undefined {
|
||||
if (preset === 'custom') return undefined;
|
||||
return presetsFor(kind)[preset];
|
||||
}
|
||||
|
||||
/** The preset a stored grant amounts to, or "custom". */
|
||||
export function detectPreset(kind: ShareKind, rights: AnyRights): RolePreset {
|
||||
for (const [name, preset] of Object.entries(presetsFor(kind)) as [RolePreset, AnyRights][]) {
|
||||
if (matches(preset, rights)) return name;
|
||||
}
|
||||
return 'custom';
|
||||
}
|
||||
Reference in New Issue
Block a user