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
| Samling | Innehåll | Vem ser den |
|---|---|---|
zk_team | namn, slug (skapas av namnet: "Åkes Bär & Bröd" → akes-bar-brod), skapad_av | Teamets medlemmar |
zk_team_medlemmar | team, anvandare, roll (agare / admin / medlem) | Teamets medlemmar |
zk_team_inbjudningar | team, epost, roll, utgar, anvand — koden sparas bara som hash | Teamets ägare och admin |
| Roll | Får |
|---|---|
agare | Allt: döpa om och radera teamet, göra andra till ägare, ändra och ta bort vem som helst |
admin | Bjuda in, återkalla inbjudningar, ta bort medlemmar, växla medlem ⇄ admin. Kan inte röra en ägare och inte göra någon till ägare. |
medlem | Lä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:
| Regel | Vä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_*):
- Medlem i team A ser, hämtar, filtrerar och expanderar bara A:s poster — aldrig B:s, inte ens med B:s id.
- Ingen kan skapa en post i ett team hen inte är med i, eller flytta en post till ett annat team (
:changed = false). - Gäster och utomstående ser ingenting. Den som lämnar teamet slutar se datan direkt.
- Villkoren på medlemskap och roll träffar samma medlemsrad. Testat med fällan: personen är medlem i A men ägare i B — hen får inte radera i A.
Två fällor, båda uppmätta:
- Använd
?=, aldrig=.=betyder i PocketBase att alla medlemsrader ska matcha — så fort teamet har två medlemmar ser ingen av dem något. - Bygg inte rollkollen med
@collection.zk_team_medlemmar...utan alias-koppling till rätt team och person; då kan villkoren träffa olika rader. Håll dig tillteam.zk_team_medlemmar_via_team...som ovan.
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
- Medlemskap och inbjudningar kan inte skrivas direkt via API:t (create/update/delete-regler är stängda) — bara via rutterna, som kollar rollen.
- Den som skapar ett team via API:t blir alltid
skapad_avsjälv, oavsett vad som skickas.skapad_avkan inte ändras via API:t. - Ett medlemskap kan inte flyttas till ett annat team eller en annan person (inte ens av superuser).
- Inbjudningskoden (40 tecken) sparas bara som SHA-256-hash i ett dolt fält. Den syns aldrig i API:t, inte ens för ägaren.
- Länken i mejlet accepterar ingenting av sig själv — mejlskannrar öppnar länkar men trycker inte på knappar. Sidan skickas med
Cache-Control: no-storeochReferrer-Policy: no-referrer. - Ändringar sker i transaktioner. Testat: två ägare som lämnar samtidigt → exakt en lyckas; fyra samtidiga accepter av samma inbjudan → exakt en lyckas.
#Begränsningar
- Sista ägaren kan inte radera sitt konto så länge hen äger ett team (raderingen av medlemskapet stoppas). Gör någon annan till ägare eller radera teamet först. Tänk på det i ditt GDPR-flöde.
- Bara samlingen
usersstöds som medlemmar. - Ingen hastighetsbegränsning på inbjudningar: en ägare/admin kan skicka hur många mejl som helst via din SMTP. Lägg en gräns framför om det behövs.
- Inbjudan kräver bekräftad e-post (
verified). Appar som aldrig bekräftar e-post måste göra det (t.ex. PocketBaserequestVerification). Att OTP-inloggning viainloggningslankarsätterverifiedär inte testat i detta plugin. - En inbjudan kan inte ge rollen ägare — bjud in som admin/medlem och befordra efteråt.
- Landningssidan hittar bara inloggningen om frontend ligger på samma adress som PocketBase; annars anropa
accepterafrån din app. updatepåzk_teamfrån SDK:n får inte skicka medskapad_av(även samma värde nekas) — skicka bara fälten du ändrar.- Regelmönstret är verifierat i 0.40.4. Back-relationer och "samma rad"-beteendet är PocketBase-intern semantik — kör
./bank.py teamigen innan du uppgraderar PocketBase. - Ingen inbyggd "byt ägare"-knapp: gör den nya till ägare, och degradera dig själv sedan.
#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.