ZKRINET Docs
Byggblock

Team — organisationer, medlemmar och roller

På Zkrinet: lägg till blocket under Byggblock i panelen — ett klick, din egen kod rörs inte och en ögonblicksbild tas först. Installationsstegen nedan gäller om du kör PocketBase själv, till exempel efter en export.

Team (organisationer) med medlemmar och tre roller, inbjudningar via mejl, och — det viktiga — färdiga API-regler som du kopierar till dina egna samlingar så att varje team bara ser sin egen data. Testat mot PocketBase 0.40.4. Kräver pluginet mejl (inbjudningarna skickas med det).

Installera

Kopiera pb_hooks/ och pb_migrations/ (och pluginet mejl) in i projektet och starta om. Sätt Application URL under Settings — länken i inbjudningsmejlet byggs från den. SMTP ställs in under Settings → Mail settings.

Det här får du

SamlingInnehållVem ser den
zk_teamnamn, slug (skapas av namnet: "Åkes Bär & Bröd" → akes-bar-brod), skapad_avTeamets medlemmar
zk_team_medlemmarteam, anvandare, roll (agare / admin / medlem)Teamets medlemmar
zk_team_inbjudningarteam, epost, roll, utgar, anvand — koden sparas bara som hashTeamets ägare och admin
RollFår
agareAllt: döpa om och radera teamet, göra andra till ägare, ändra och ta bort vem som helst
adminBjuda in, återkalla inbjudningar, ta bort medlemmar, växla medlem ⇄ admin. Kan inte röra en ägare och inte göra någon till ägare.
medlemLäsa teamet och medlemslistan. Lämna.

Den som skapar ett team blir ägare automatiskt. Ett team har alltid minst en ägare — sista ägaren kan inte lämna, degraderas eller tas bort. Det gäller även i adminpanelen och i din egen kod (skyddet ligger i en hook, inte bara i rutterna).

Användning (PocketBase JS SDK)

// Skapa ett team — du blir ägare
const team = await pb.collection("zk_team").create({ namn: "Bageriet" })

// Mina team
const mina = await pb.collection("zk_team").getFullList({ sort: "namn" })

// Medlemmar med namn och e-post (vanlig expand på users fungerar inte — users är privat per person)
const { items } = await pb.send(`/api/zk/team/${team.id}/medlemmar`, {})

// Bjud in (ägare/admin). roll: "medlem" (standard) eller "admin". dagar: 1–30 (standard 7).
await pb.send(`/api/zk/team/${team.id}/bjud-in`, { method: "POST", body: { epost: "nisse@exempel.se", roll: "medlem", dagar: 7 } })

// Byt roll / ta bort / lämna
await pb.send(`/api/zk/team/${team.id}/roll`,    { method: "POST", body: { anvandare: userId, roll: "admin" } })
await pb.send(`/api/zk/team/${team.id}/ta-bort`, { method: "POST", body: { anvandare: userId } })
await pb.send(`/api/zk/team/${team.id}/lamna`,   { method: "POST" })

// Öppna inbjudningar (ägare/admin) och återkalla en
const inb = await pb.collection("zk_team_inbjudningar").getFullList({ filter: `team = "${team.id}" && anvand = ""` })
await pb.send(`/api/zk/team/inbjudan/${inb[0].id}/aterkalla`, { method: "POST" })

// Döp om / radera teamet (bara ägare). Radering tar med medlemskap och inbjudningar.
await pb.collection("zk_team").update(team.id, { namn: "Nya namnet" })
await pb.collection("zk_team").delete(team.id)

Alla rutter kräver en inloggad användare i users. Fel kommer som { message: "..." } på svenska: 400 (ogiltigt, sista ägaren, redan använd/utgången inbjudan), 403 (fel roll, fel e-post), 404 (teamet finns inte eller du är inte medlem — vi avslöjar inte vilket).

Inbjudan

Mottagaren får ett mejl (mallen team_inbjudan i zk_mejlmallar, redigerbar) med en länk till en sida med knappen Gå med. Sidan använder inloggningen som PocketBase JS SDK sparat (pocketbase_auth i webbläsaren) och skickar sedan till /. Ligger din frontend på en annan adress: ta kod ur länken och anropa själv:

await pb.send("/api/zk/team/inbjudan/acceptera", { method: "POST", body: { kod } })   // → { team: {id, namn, slug}, roll }

Inbjudan accepteras bara av en inloggad användare med samma e-postadress som bjöds in och bekräftad e-post (verified). Den fungerar en gång och tills utgar. Har mottagaren inget konto: låt hen skapa ett (eller använd pluginet inloggningslankar) och öppna länken igen.

Skydda DINA samlingar per team

Ge samlingen en relation team (→ zk_team, en post). Klistra in reglerna under API Rules:

RegelVärde
List / View@request.auth.id != '' && team.zk_team_medlemmar_via_team.anvandare ?= @request.auth.id
Create@request.auth.id != '' && team.zk_team_medlemmar_via_team.anvandare ?= @request.auth.id
Update@request.auth.id != '' && team.zk_team_medlemmar_via_team.anvandare ?= @request.auth.id && @request.body.team:changed = false
Delete (bara ägare/admin)@request.auth.id != '' && team.zk_team_medlemmar_via_team.anvandare ?= @request.auth.id && team.zk_team_medlemmar_via_team.roll ?!= 'medlem'

Bara ägare: byt sista delen mot team.zk_team_medlemmar_via_team.roll ?= 'agare'. zk_team_medlemmar_via_team betyder "raderna i zk_team_medlemmar vars fält team pekar hit" (PocketBase back-relation).

Det här är bevisat i testbänken mot en riktig samling projekt (se test.py, test_regel_*):

Två fällor, båda uppmätta:

Har din samling relationen under ett annat namn (t.ex. organisation), byt bara första ordet: organisation.zk_team_medlemmar_via_team.anvandare ?= @request.auth.id.

Säkerhet

Begränsningar

Testa

./bank.py team

17 tester: ägarskap vid skapande (även via adminpanelen), gäst nekas överallt, direktskrivning av medlemskap nekas, bara ägare ändrar teamet, admin kan inte röra ägare eller göra ägare, medlem kan bara läsa, sista ägaren skyddad (rutter, adminpanel, kontoradering) och samtidiga försök, inbjudan hela vägen (mejl, skannerskydd, fel e-post, obekräftad, en gång, samtidigt), utgång och 1–30 dagar, kod bara som hash, återkallning, utomstående får 404, och regelmönstret: isolering mellan team, samma-rad-fällan och varför = inte fungerar. Säkerhetstesterna är mutantverifierade: 37 mutanter (varje skydd brutet med flit); 36 fångade, den 37:e (accept utan transaktion) är ofarlig eftersom unika indexet (team, användare) ändå stoppar dubbelt medlemskap — tas indexet bort också fångas den.