Pohled na celou firmu dostal kazdy, kdo do ni patril: scopes.push('tenant')
se v access.ts neptalo na nic. mine byl dobrovolny filtr, ne strop, takze
resitel s roli agent poslal ?scope=tenant a dostal cely provoz.
Rozdil, na kterem to ted stoji: pohled je co chci videt, strop je co vubec smim
videt. Existoval jen pohled a klientovi se veril.
Model:
- Membership.seesAllTenant - vidi cely provoz firmy. Postaveni uctu ve firme,
ne vlastnost resitele: clovek muze byt ve dvou firmach jednou reditel
a jednou brigadnik
- PersonGroup.members je { personId, seesAll } misto holeho personIds. seesAll
je vedouci sekce. Priznak visi na clenstvi, ne na cloveku a ne na skupine -
diky tomu muze byt clovek v peti sekcich a jen ve dvou videt vsechno, coz
role rict neumi, ta je jedna na celou firmu
- stara podoba personIds se dal cte, prevadi ji groupMembers
Vypocet stropu (visibilityFor), sjednoceni ne prunik: spravce platformy nebo
seesAllTenant vidi celou firmu, jinak svoje tickety plus vse ze sekci, kde ma
zaskrtnuto, plus jejich fronta. U zaznamu bez priznaku rozhoduje pravo
ticket.assign.others - kdo smel prehazovat cizi praci, uz stejne cely provoz
videl, takze se mu nic nebere.
Vynuceni:
- strop je povinna soucast TicketFilter, stejne jako tenantIds. Nepovinny filtr
na prava je filtr, ktery jednou nekde chybi - takhle prekladac ukazal vsech
trinact mist, ktera ho jeste nemela
- getWorkload a getAgentStats uz nesahaji do pole ticketu primo, jdou pres
listTickets. Driv obchazely kazde omezeni viditelnosti
- getTicket kontroluje strop i u jednoho ticketu, bez toho by stacilo znat ID
- detail a prevzeti pocitaji strop za firmu ticketu, ne za prave prepnutou
Helpdesk se ridi toutez hierarchii, jen "moje" znamena, co jsem zalozil -
zadavatel pozadavek nikdy nema prirazeny. Ticket proto nese createdById.
Zalozka Firma: pod jednim mistem Prehled, Resitele, Sekce, Role a prava, Typy
ticketu a Pozvanky. V Nastaveni zustal Muj ucet a platformni veci. Role a Typy
jsou samostatne komponenty, sekce maji vlastni panel - u kazdeho clenstvi je
prepinac a radek na cloveka obecny EntityAdmin neumi.
Obsah ticketu se cte i v helpdesku, v seznamu jako jednoradkovy nahled.
Overeno na bezici instanci se tremi ucty nad tymiz daty: spravce platformy vidi
vsechny tri tickety vcetne neprirazeneho, Vomacka jako vedouci Servicedesku dva
(tickety obou clenu sekce), Kriz jako radovy clen jeden, jen svuj. Adresa cizho
ticketu vraci 404, vytizeni tymu ukazuje jen viditelne a po zaskrtnuti priznaku
se rozsah zmeni hned.
Co z toho plyne: neprirazeny ticket bez skupiny nevidi nikdo krome toho, kdo
vidi celou firmu. Prichozi praci musi nekdo smerovat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
/**
|
|
* Uzivatel muze patrit do vic firem najednou (napr. externista, ktery dela
|
|
* servicedesk dvema klientum). Proto `memberships`, ne jedno `tenantId`.
|
|
*
|
|
* Role je vzdy az uvnitr firmy. Pristup napric firmami je zvlast, jako
|
|
* `platformAdmin` - to je nase pravo, ne zakaznicke.
|
|
*/
|
|
|
|
/**
|
|
* Clenstvi ve firme.
|
|
*
|
|
* `roleIds` je seznam, ne jedna role. Duvod: `'admin' | 'agent'` na ucetni,
|
|
* skladnika a vedouciho nestacilo a pridavat dalsi hodnoty do unionu je slepa
|
|
* ulicka, kazdy klient chce jine. Role jsou proto zaznamy, viz `data/permissions.ts`.
|
|
*
|
|
* Odkaz smi byt ID role, nebo klic systemove role (`admin`, `agent`), aby slo
|
|
* zapsat oboji a stara data dal fungovala.
|
|
*/
|
|
export interface Membership {
|
|
tenantId: string;
|
|
roleIds: string[];
|
|
/**
|
|
* Vidi vsechny tickety firmy, ne jen svoje a svych sekci.
|
|
*
|
|
* Je to **postaveni uctu ve firme**, ne vlastnost resitele, proto to visi
|
|
* tady a ne na `Person`: clovek muze byt ve dvou firmach jednou reditel
|
|
* a jednou brigadnik.
|
|
*
|
|
* `undefined` u zaznamu ulozenych driv. Co pak plati, resi `visibilityFor`
|
|
* v data/access.ts - nemuze to byt tise `false`, jinak by pri nasazeni
|
|
* kazdemu spravci zmizela vetsina ticketu.
|
|
*/
|
|
seesAllTenant?: boolean;
|
|
}
|
|
|
|
export interface User {
|
|
id: string;
|
|
email: string;
|
|
/** bcrypt hash - nikdy neposilat na klienta */
|
|
passwordHash: string;
|
|
name: string;
|
|
/** Vidi napric vsemi firmami a muze mezi nimi prepinat. */
|
|
platformAdmin: boolean;
|
|
memberships: Membership[];
|
|
}
|
|
|
|
/** Verze uzivatele bezpecna pro odeslani na klienta. */
|
|
export interface PublicUser {
|
|
id: string;
|
|
email: string;
|
|
name: string;
|
|
platformAdmin: boolean;
|
|
memberships: Membership[];
|
|
}
|
|
|
|
export interface JwtPayload {
|
|
sub: string;
|
|
email: string;
|
|
/**
|
|
* Jen u tokenu impersonace: kdo se za uzivatele vydava.
|
|
* Bez toho by v auditu zustalo jen "uzivatel to udelal sam".
|
|
*/
|
|
act?: string;
|
|
actEmail?: string;
|
|
/** Jen u impersonace: smi i zapisovat. */
|
|
writes?: boolean;
|
|
}
|
|
|
|
export function toPublicUser(user: User): PublicUser {
|
|
const { passwordHash: _passwordHash, ...rest } = user;
|
|
return rest;
|
|
}
|
|
|
|
/** Role uzivatele v dane firme. Prazdne pole = do firmy nepatri. */
|
|
export function rolesIn(user: User, tenantId: string): string[] {
|
|
return user.memberships.find((m) => m.tenantId === tenantId)?.roleIds ?? [];
|
|
}
|