Skip to main content
Petanque Life
← Back to all features
02

Identity & Access Management

131 features · 7 subsystems

User identity, authentication, authorization, and role management across the entire federation hierarchy. Built on a comprehensive auth stack and capability-based access control.

User Registration & Profile

F02.01
Shipped
How it works
  • F02.01.01 Shipped

    Self-registration with email/phone verification

    ✅ PL-F0201a (OTP via craft_easy.core.auth.otp, best-effort delivery sink)
  • F02.01.02 Shipped

    OAuth2 social login (Google, Microsoft, GitHub)

    ✅ PL-1801 (gateway), ✅ PL-1802/03/04 (providers), ✅ PL-1805 (app + admin SSO-knappar), ✅ PL-1806 (flera identiteter per user, Connected Accounts-vy, fresh-auth-fönster, last_auth_method-policy, upstream-revokation)
  • F02.01.03 Shipped

    Player profile (name, DOB, nationality, photo, playing position)

    ✅ PL-F0201a (base model PL-105, per-tenant matrix SE/FR/DE), ✅ PL-T311 (premium app-shell — cover-gradient + −48 px overlap-avatar + sticky <SegmentedControl> Översikt/Stats/Lic/Settings, mobil 1-kol / desktop 2-kol, gateadepad bakom pl_premium_profile_v1)
  • F02.01.04 Shipped

    Official profile (umpire grade, certifications, languages)

    ✅ PL-F0201a (umpire grade L1–L5, certification expiry validation, language list)
  • F02.01.05 Shipped

    Administrator profile (federation, role, mandate period)

    ✅ PL-F0201a (role enum, mandate range validator, currently-serving filter)
  • F02.01.06 Shipped

    Multi-identity support (same person = player + umpire + coach)

    ✅ PL-F0201a (GET /users/{user_id}/profiles aggregates player/official/coach/administrator)
  • F02.01.07 Shipped

    Profile photo upload and management

    ✅ PL-F0201b (multipart upload, PNG/JPEG/WebP, max 5 MB, magic-byte + Pillow-validering, SHA-256 ETag, en bild per profil över alla fyra profil-typer)
  • F02.01.08 Shipped

    Profile completion tracking and prompts

    ✅ PL-F0201a (completion_pct + currently_serving on administrator), ✅ PL-F0201b (GET /{profile}/prompts med aktionsbara meddelanden och severity high/medium/low)
  • F02.01.09 Shipped

    GDPR-compliant data export and deletion

    ✅ PL-F0201b (profile_photos + check_in_records inkluderade i /me/data-export; foton raderas och administrator-profiler anonymiseras i raderingsflödet)
  • F02.01.10 Shipped

    Player ID card generation (digital + printable)

    ✅ PL-F0201b (GET /player-profiles/{id}/card?format=json|pdf|png, ISO/IEC 7810 ID-1 PDF, 1050×660 PNG, stabil JSON + base64-url QR-payload), ✅ PL-T311 (app <LicensePassCard> — bottle-green gradient + embossed QR + holografisk sheen + multi-tenant pager, mountad på (tabs)/license-routen och i profilens License-tab), ♻️ Rebuild 525ece23.m-3.t-1 (rena /v1-kontraktet GET /v1/player-profiles/{id}/card?format=json|png|pdf|pkpass i Go: json/png MUST, pdf/pkpass SHOULD, 415 på okänt format; pkpass-pass (Wallet) med live giltighet/spärr; se specs/api/endpoints/card-and-checkin.md)
  • F02.01.11 Shipped

    QR code identification for check-in at events

    ✅ PL-F0201b (HMAC-SHA256 v1-token, SVG/JSON QR, POST /major-events/{id}/check-in med replay-, expiry- och tampering-försvar, audit-log via CheckInRecord), ♻️ Rebuild 525ece23.m-3.t-1 (Go: GET /v1/me/check-in-token HMAC-signerad self-contained token + POST /v1/events/{id}/check-in append-only check_in_record med token_jti UNIQUE-replay-grind; felkontrakt check_in_token_replayed/_expired/_invalid; offline-verifierbar för Wallet/dålig täckning)
  • F02.01.12 Shipped

    Player stats dashboard (ELO trend, win-rate, partners, opponents, history)

    ✅ PL-T311 (GET /me/stats?period=30d|90d|365d|all aggregator + app <StatsTab> med <EloTrendChart>, <WinRateRing>, top-3 partners/opponents, last-20-matches; Cache-Control: private, no-store)
  • F02.01.13 Planned

    ICE/nödkontakt + skyddad medicinsk basinfo (art.9) med break-glass

    ♻️ Rebuild 525ece23.m-5.t-1 (Go: additiv ALTER player_profile (emergency_contact/medical_flags jsonb); GET/PUT /v1/me/emergency-medical — samtyckesgrindad (ConsentResolver-port, fail-closed) + minderårig-parental_consent + optimistisk låsning + audit; maskerad i alla icke-ägar-läsvägar (FR-3); break-glass GET /v1/events/{id}/participants/{aid}/emergency-medical capability-gatead (incident:emergency-read) + incheckad + pågående event + obligatoriskt audit-skäl; felkontrakt consent_required/parental_consent_required. Samtyckes-/retention-modellen ägs av D-GDPR; se specs/api/endpoints/dusers-emergency-medical.md + docs/domain/emergency-medical.md)
  • F02.01.14 Planned

    Egen ICS-kalender (SHOULD) — personlig prenumerationsfeed över alla tenants

    ♻️ Rebuild 525ece23.m-6 (Go t-1: GET /v1/me/calendar.ics?token= publik, token-autentiserad RFC 5545-feed som aggregerar personens egna poster över alla tenants (BR06.08) via en CalendarSource-port — D-VENUE-banbokningar wirade, D-COMP/D-EVENTS som seams (noll poster, aldrig fel, §5d/NFR-9); revokerbar, endast hashad feed-token; GET /v1/me/calendar-subscription + POST /v1/me/calendar.ics:rotate-token; stabil UID per post. app-UI t-2: Inställningar → "Min kalender & favoriter" visar/kopierar feed-URL:en + roterar/spärrar (app/app/kalender-favoriter.tsx); se specs/api/endpoints/me-calendar-favorites.md + specs/app/views/kalender-favoriter.md)
  • F02.01.15 Planned

    Spara favoriter (NICE) — favoritklubb/-venue/-spelare/-tävling

    ♻️ Rebuild 525ece23.m-6 (Go t-1: favorite-tabell (polymorf, tenant_id nullable, CHECK-enum favorite_type, UNIQUE (auth_identity_id, favorite_type, target_ref), ingen version) + GET/POST/DELETE /v1/me/favorites — self-skopad (IDOR-skydd), 409 favorite_exists, 422 på okänd typ/icke-UUID, 404-icke-läckage; server-side tenant-skopning (player → tenant_id=NULL); bokmärkes-semantik, ingen cross-domän-validering. app-UI t-2: favorit-snabblista (lägg till/ta bort) i app/app/kalender-favoriter.tsx; se specs/api/endpoints/me-calendar-favorites.md + specs/app/views/kalender-favoriter.md)
  • F02.01.16 Planned

    Tenant-medlemsregister — admin listar den aktiva tenantens medlemmar och öppnar en LIVE profil-detalj

    ♻️ Rebuild be2f9ef1.m-1.t-1 (Go: GET /v1/tenant/members — den tenant-skopade läsytan admin-registret (admin/lib/members.tsx, useMemberRegister) konsumerar, tidigare 404. Capability-gatead member:read (ingen ny capability), aktiv tenant ur kontexten (requireTenant/reqctx, aldrig path/query/body/header), {members:[MemberRow]}-kuvert där varje rad bär ett konkret profile_id (= <type>_profile.id, inte auth_identity_id) som löser GET /v1/{profile_type}-profiles/{id}. Riktig data: UNION ALL över player/official/coach/administrator_profile JOIN auth_identity, tenant-filter på <type>_profile.tenant_id (NFR-1 hård cross-tenant-isolation), stabil ordning, NULL display_name → "". Read-only, ingen migration/audit. Stängde den sista hårda frontend↔backend-kontraktsluckan på webbplattformen. Se specs/api/endpoints/tenant-members.md + specs/admin/views/member-register.md)

Authentication

F02.02
Shipped
How it works
  • F02.02.01 Shipped

    Email + OTP login

    ✅ PL-1901 (admin console), ✅ PL-F0202a (per-tenant-matris Sverige/France/Deutschland, enumeration-skydd, abuse-limits), ✅ PL-T310 (premium passwordless flow i app: split-screen desktop / glass-mobil <AuthShell>, refined 6-digit <OtpInput> med auto-advance + paste-spread + monospace, 30 s resend-cooldown, 10 min expiry-countdown, <AuthSuccess>-anim, reduced-motion-fallback, aria-live error-announce)
  • F02.02.02 Shipped

    OAuth2 (Google, Microsoft, GitHub) via SSO-gateway with per-app routing

    ✅ PL-1801 (infra), ✅ PL-1802 (Microsoft/Entra ID), ✅ PL-1803 (Google), ✅ PL-1804 (GitHub), ✅ PL-1805 (frontend SSO-knappar app + admin, /auth/complete + /auth/native-complete landings, shared gateway-kontrakt), ✅ PL-1806 (Connected Accounts-vy i app + admin, GET /auth/me, GET/DELETE /auth/me/oauth-identities, POST /auth/oauth/{provider}/link/{app} med 5-min fresh-auth-fönster), ✅ PL-F0202a (surface-test Google+Microsoft i providers-listan), ✅ PL-T310 (refined <FederatedRow> icon-buttons med brand-tinted ring + per-provider a11y-label i app-login), ✅ Go-backend 4848185c.m-2 (F1-3 m-2): ren OIDC relying party (Google/Microsoft/Apple) — discovery + JWKS-cache (kid-miss-refetch), PKCE-S256-obligatorisk authorize, one-shot state + binding-cookie, fullverifierad ID-token, 4 provisioneringsgrenar med per-provider compute_email_verified, web/native-leverans utan token-i-URL, ?error=-/problem+json-felkoder, anti-abuse-rate-limits + lokal Keycloak-IdP-harness (t-1/t-2/t-3; api/internal/http/oidc.go, api/internal/auth/oidc/)
  • F02.02.03 Shipped

    TOTP/2FA (authenticator app)

    ✅ PL-F0202a (/auth/2fa/setup, /enable, /verify, /status, /disable, recovery-koder engångsbruk, PreAuthToken → UserToken-upgrade, petanque-override av den tidigare require_auth-buggen)
  • F02.02.04 Shipped

    WebAuthn passkeys

    ✅ PL-F0202a (/webauthn/register/options, /register, /credentials list+delete, petanque-override av an upstream user_id-typbug, clientDataJSON/challenge/origin-verifiering, duplicate-guard)
  • F02.02.05 Shipped

    JWT ES512 token-based sessions

    ✅ PL-1801 (OAuth-gateway), ✅ PL-F0202b (GET /auth/session/me med explicit algorithm-fält, ES512 verifierat via JWT-headerinspektion)
  • F02.02.06 Shipped

    Refresh token rotation

    ✅ PL-F0202b (RefreshTokenFamily/RefreshToken med SHA-256-hash, family-wide theft detection, POST /auth/session/issue + /refresh + /logout, plrt_-prefix, 32-byte entropi)
  • F02.02.07 Shipped

    Rate limiting and abuse detection

    ✅ PL-1801 (slowapi på /authorize, /callback, /exchange), ✅ PL-F0202b (AuthAbuseMonitor sliding-window per IP + subject, 60/min slowapi-gräns på /session/refresh, /session/logout, /m2m/token, failure-bucket 10/5 min → 429 med Retry-After), ✅ Go-backend 4848185c.m-1 (F1-3 m-1, t-3): disposable-domän-block + DB-backad per-mottagare-/global-kvot på /auth/{otp,magic}/start + per-IP exponentiell verify-backoff/CAPTCHA-fäste på /auth/otp/verify, enumeration-säkert, 429 + Retry-After via RFC 9457 (api/internal/http/auth_antiabuse.go), ✅ Go-backend 4848185c.m-2 (F1-3 m-2, t-3): per-IP/min OIDC-budgetar ovanpå den globala limitern — authorize 10/min, callback 20/min, exchange 20/min — 429 rate_limited + Retry-After + RateLimit-* (in-memory/per-instans, api/internal/http/oidc_antiabuse.go)
  • F02.02.08 Shipped

    M2M OAuth2 client credentials for integrations

    ✅ PL-F0202b (POST /auth/m2m/token RFC 6749 §4.4, M2MClient-modell med capability-scope, plm2m_-prefix, admin-CRUD på /auth/m2m/clients, rotate-secret, revoke, IP-allowlist, RFC 6749 §5.2-felform, Cache-Control: no-store), ✅ Go-backend 4848185c.m-5 (F1-3 m-5, t-1): rena client_credentials-modellen — m2m_client (secret_hash bytea hash-only, scopes text[], ip_allowlist cidr[], opakt nullable tenant_id utan FK), token bär client:true → decode klassar actor_kind=m2m och binder aldrig som mänsklig session (NFR-5), enumeration-säker invalid_client, sys-skopad CRUD (sys-allowlist + fresh-auth), per-IP-rate-limit, audit (api/internal/http/auth_m2m.go, api/internal/store/m2m.go, 00007_m2m.sql)
  • F02.02.09 Shipped

    Service-account M2M-tokens med admin-scopes

    ✅ PL-T293 (ServiceAccount-dokument, ApiToken.service_account_id, admin-scopes tenants:write / federations:write / cms:write / admin_invitations:create / users:write / seed:run, /sys/service-accounts CRUD + token-mint/revoke, agent-driven prod-bootstrap utan WebAuthn-step-up, 90-d default TTL, governance + system audit). 🔁 Go-backend 4848185c.m-5 (F1-3 m-5, t-2) — agent-aktör (ren ny modell, ej portning): agent_token_issuance-revocation-list (token_id pk, parent_actor, impersonated_identity_id FK, inget token-värde lagras); claims agent:true + token_id + impersonerad sub (ingen fid); POST /auth/sys/agent-tokens/issue (sys + fresh, TTL ≤ 24 h, batch ≤ 20) + revoke-all; decode-revocation-check; mutation-scope-block (403 agent_action_blocked, reason=scope_block); env-gatad POST /auth/dev/login-as (local/test + allowlist, omonterad i prod → 404) mintar mänsklig session genom UI:t (PL-T347-mekanismen); audit agent_token_issued/agent_tokens_revoked_all/agent_action_blocked/dev_login_as. Riktig sys_security-roll + capability-enforcement + JIT-Tier-2 är F1-4/framtid (api/internal/http/auth_agent.go, auth_devlogin.go, api/internal/store/agent.go, 00008_agent.sql)
  • F02.02.10 Shipped

    Anti-abuse signup-pipeline

    ✅ PL-T301 — client_ip_cidr24 + time_to_fill_ms + honeypot_triggered + turnstile_score på SignupRequest, fraud-signal-aggregator (ip-cluster/email-repeat/honeypot/fast-fill/stripe-decline-rate) cachat 15 min, sys_security flag-fraud + email-domain-blocklist applicerad direkt vid POST /public/signup, audit-events signup.fraud_signal_triggered + signup.flagged_fraud + signup.email_domain_blocked, auto-SEV3-incident vid alert-severity. Sys-konsol: /customer-base/signups.

Role & Permission Management

F02.03
Shipped

### Predefined Role Templates

How it works
  • F02.03.01 Shipped

    Capability-based access control (deny-by-default)

    ✅ PL-F0203a, 🔁 Go-backend f07177c7.m-1 (F1-4 AuthZ m-1) — register + persistens: capability-registret materialiserat som tunt internt Go-paket api/internal/authz/ — capabilities-referenstabell (00009_authz.sql) + deklarativ in-process-katalog (catalog.go) med namnrymds-grammatik (<namespace>.<…> / <resurs>:<verb>, validerad av ValidateName) och is_sensitive_field-markör; idempotent seed-reconcile av katalogen. ✅ Go-backend f07177c7.m-2 (F1-4 AuthZ m-2) — enforcement-spine: beslutsoraklet authz.Decide (rent, fail-closed, en beslutskälla); route-markerande primitiv (RouteRegistry.Public/SelfGuarded/Require) + deny-by-default-boot-gate (CheckRoutes → serve-vägran + api authz-check-subkommando i CI — en omarkerad route = boot-fel, ej runtime-läcka); gaten fyller F1-1:s reserverade middleware.Authz()-slot; tenant-resolution + X-Tenant-Id-fallback + spoof (JWT-claim auktoritativ, fail-closed, 403 tenant_spoof); agent-scope-block på beslutsnivå (finance.* nekas agent-aktörer, 403 agent_action_blocked); audit på varje deny/spoof/agent-block; tunn orakel-HTTP-yta (GET /me/capabilities, POST /authz/capability-check(-batch)); felkod capability_denied. ✅ Go-backend f07177c7.m-3 (F1-4 AuthZ m-3) — data-lager-isolering + fält-maskering: tree-aware orakel + authz.ScopeTree-subträdsexpansion (fixtur-impl tills F1-9), ScopeFilter (fail-closed list-filtrering till caller:s synliga subträd), ScopedLookup (enforce:at 404-ej-403 på cross-scope/cross-tenant-miss + intern scope-miss-audit), scope-medveten RequireCapability (RequireScoped, beslut i resursens scope) och MaskSensitive (generisk DTO-agnostisk fält-maskering — authz:"sensitive=<cap>"-fält utelämnas för caller utan <cap>, 200 på resten, aldrig 403 på hela posten; VisibilityPolicy-krok för privacy_settings). Roll-/sys-CRUD och UI är senare F1-4-milstolpar; den riktiga org_nodes-hierarkin ägs av F1-9 (plugg:as i ScopeTree-seamen utan signaturändring). ✅ Go-backend f07177c7.m-4 (F1-4 AuthZ m-4) — sys cross-tenant-roller + agent-block-härdning: deterministisk sys-roll → capability-mappning (sysroles.go) + fail-closed sys-capability-resolver (sysresolver.go, cross-tenant); /sys/roles-ytan (GET /sys/roles, GET/POST /sys/users/{email}/roles, DELETE /sys/users/{email}/roles/{role}) med four-eyes self-grant-block (400 self_grant_blocked), fresh-auth (401 reauth_required) och idempotent grant; sys-roll-backad gate ersätter env-allowlisten (allowlist = A4-bootstrap); agent-scope-block härdat — konfigurerbar finans-lista (PL_AGENT_BLOCKED_NAMESPACES) + garantin att en agent som impersonerar sys_finance ändå nekas finans (agent_scope_block); audit sys.role.grant/sys.role.revoke. Kopierbart mönster i api/README.md → "AuthZ fundament (F1-4)" §6–§7
  • F02.03.02 Shipped

    Role definition with capability sets

    ✅ PL-F0203a, 🔁 Go-backend f07177c7.m-1 (F1-4 m-1): roll-modellen (roles, role_capabilities) + de sju seedade plattforms-mall-rollerna (player, umpire, coach, clubadmin, district_admin, federation_admin, support_member — tenant_id=NULL, is_system=true, deterministisk dokumenterad capability-uppsättning per roll); additiva roller (api/internal/authz/seed.go)
  • F02.03.03 Shipped

    Role inheritance (e.g., national-admin inherits regional-admin)

    ✅ PL-F0203a
  • F02.03.04 Shipped

    Role assignment per user per tenant (+ OrgNode scope for regional/club roles)

    ✅ PL-F0203a, 🔁 Go-backend f07177c7.m-1 (F1-4 m-1): role_assignments (principal IN A SCOPE — opakt tenant_id/scope_id/principal_id utan FK, polymorf principal, idempotent UNIQUE-tuple) + den rena fail-closed datalager-resolvern (EffectiveCapabilities — additiv union över aktiva, icke-utgångna tilldelningar, scope-bevarande, tom mängd vid saknad tenant = F-AT-03-skydd; ingen trädexpansion/Allow-Deny — senare milstolpe)
  • F02.03.05 Shipped

    Wildcard capabilities (e.g., competitions:*)

    ✅ PL-F0203a
  • F02.03.06 Shipped

    Sensitive field restrictions

    ✅ PL-F0203b (tabelldriven SENSITIVE_FIELD_SPECS, filter_document/filter_documents, assert_write_allowed, profile:read_sensitive-override, GET /sensitive-fields, POST /sensitive-fields/check), 🔁 Go-backend f07177c7.m-3 (F1-4 AuthZ m-3): MaskSensitive (api/internal/http/authz_mask.go) — generiskt, DTO-agnostiskt primitiv som vid serialisering utelämnar varje fält taggat authz:"sensitive=<capability>" när caller saknar <capability> i resursens scope (prövat via samma orakel), 200 på resten av posten, aldrig 403 på hela; field-level-capabilities privacy.personal_data.read/privacy.medical.read/privacy.safeguarding.read/finance.account.read (is_sensitive_field=true) idempotent seedade; VisibilityPolicy-krok för privacy_settings (full modell ägs av D-MEMBER/D-GDPR)
  • F02.03.07 Shipped

    Petanque-specific role templates (see below)

    ✅ PL-F0203b (category/suggested_scope/is_temporal/is_petanque_specific/requires_approval metadata på RoleTemplate, GET /role-templates?category=...&scope=...)
  • F02.03.08 Shipped

    Access debugging tools

    ✅ PL-F0203b (GET /access-debug/role-tree/{name} BFS inkl. cycle/missing-markering, GET /access-debug/users/{id}?at=... active/inactive split, POST /access-debug/check med granting_roles)
  • F02.03.09 Shipped

    Temporary role grants (e.g., tournament director for one event)

    ✅ PL-F0203b (POST /role-grants/temporary, GET /role-grants/temporary?active_only=..., DELETE /role-grants/{id}, exkluderar permanenta grants automatiskt)
  • F02.03.10 Shipped

    Role request and approval workflow

    ✅ PL-F0203b (RoleRequest-dokument, POST /role-requests med dubblettguard 409, /approve med scope-narrowing, /deny, /cancel med requester-only guard 403)

Organization Hierarchy Access

F02.04
Shipped

Two-layer access model: **Standalone tenants** for hard isolation (every federation is independent), **OrgNodes** (districts → clubs) for scoping within national tenants.

How it works
  • F02.04.01 Shipped

    Tenant-scoped data isolation (every federation is a standalone tenant)

    ✅ PL-F0204a (TENANT_SCOPED_MODEL_REGISTRY, describe_tenant_isolation, assert_tenant_match + structlog-audit hierarchy_access.cross_tenant_violation, GET /hierarchy-access/tenant-isolation)
  • F02.04.02 Shipped

    Public APIs for cross-tenant interactions (license verification, ITC, squad submission)

    ✅ PL-F0204a (CROSS_TENANT_PUBLIC_APIS-registret med 8 ytor: /public/licenses/verify, /public/itc/{request,release,complete}, /public/squads/{submit,{ref}}, /public/federations/directory; GET /hierarchy-access/public-apis)
  • F02.04.03 Shipped

    Linked player identity across tenants (same auth, separate profiles)

    ✅ PL-F0204a (resolve_linked_identities bypassar tenant-filter via en direkt fråga i datalagret, grupperar player/official/coach/administrator per tenant, GET /hierarchy-access/linked-identities/{user_id})
  • F02.04.04 Shipped

    District-based scope filtering (district admin sees clubs/players in their OrgNode subtree)

    ✅ PL-F0204a (scope_district_id-query + X-District-Scope-header på GET /player-profiles, BFS-traversal via resolve_district_subtree, GET /hierarchy-access/district-scope/{district_id} med audit-log), 🔁 Go-backend f07177c7.m-3 (F1-4 AuthZ m-3): ScopeFilter + tree-aware orakel (authz.ScopeTree.Subtree) filtrerar automatiskt list-endpoints till distriktsadmins synliga subträd (syskondistrikt aldrig synliga), ScopedLookup ger 404-ej-403 på cross-scope/cross-tenant-miss; fail-closed (olöst tenant → 0 rader, F-AT-03); expansionsriktning enligt docs/domain/publication-scopes.md; riktig org_nodes-hierarki ägs av F1-9 (ScopeTree-seam)
  • F02.04.05 Shipped

    Delegation of authority (federation admin delegates to assistant)

    ✅ PL-F0204b (DelegationOfAuthority-modell med scope_type tenant/district/club, POST /hierarchy-access/delegations skapa, GET lista/hämta, POST .../revoke med audit-log, duplicate-guard 409, tid-begränsade eller permanenta delegationer)
  • F02.04.06 Shipped

    Club OrgNode management within national tenant

    ✅ PL-F0204b (GET /hierarchy-access/manageable-clubs scopad lista baserat på roll/delegation, GET /manageable-districts, POST /check-club-access med granting_scope-svar, federation-admin→alla, distriktsadmin→subtree, klubbordförande→egen klubb)
  • F02.04.07 Shipped

    Role assignment scoped to OrgNode (club president only manages their club)

    ✅ PL-F0204b (GET /hierarchy-access/scopes/{user_id} aggregerar rolltilldelningar + delegationer, POST /check-org-node-access scope-enforcement, assert_can_manage_club i service-layer)
  • F02.04.08 Shipped

    Role assignment scoped to district OrgNode (district admin)

    ✅ PL-F0204b (POST /hierarchy-access/check-district-access med subtree-traversal, assert_can_manage_district verifierar direkt scope, parent-subtree eller tenant-nivå, distriktsadmin ser alla underliggande distrikt + klubbar)

Privacy & Consent

F02.05
Shipped
How it works
  • F02.05.01 Shipped

    GDPR data subject rights — GET /me/data-export, POST /me/data-deletion-request with anonymisation (PL-208, PL-F0205a). 🔁 Go-backend 525ece23.m-4 (D-USERS m-4 t-1): rena GET /v1/me/data-export — portabelt aggregat av den data D-USERS äger (identitet med maskerad e-post, profiler i alla tenants + privacy, foto-metadata, check_in_record utan token, samtycke=unavailable tills D-GDPR finns); ren /me-ägar-väg, inga secrets (api/internal/http/dusers_gdpr.go, specs/api/endpoints/me-data-export.md). POST /v1/me/data-deletion-request + /v1/me/consents graderas till m-4b (gated på D-GDPR:s erasure-request_type resp. samtyckessubstrat, §5d.1). app-UI 525ece23.m-4 t-2: Inställningar → Integritet & data → "Ladda ner min data" konsumerar GET /v1/me/data-export (app/app/integritet-och-data.tsx, specs/app/views/integritet-och-data.md); samtycke/radering listas som "kommande", wiras ej.

    ✅ PL-F0205a
  • F02.05.02 Shipped

    Data retention policies — configurable per tenant via DataRetentionPolicy model (PL-208, PL-F0205a)

    ✅ PL-F0205a
  • F02.05.03 Shipped

    Consent management — ConsentRecord model with marketing, photo_publication, data_sharing, analytics types (PL-208, PL-F0205a)

    ✅ PL-F0205a
  • F02.05.04 Shipped

    Minor/youth data protection — ParentalConsent model with guardian verification, blocks consent for minors without parental approval (PL-208, PL-F0205a)

    ✅ PL-F0205a
  • F02.05.05 Shipped

    Privacy settings per user — PrivacySettings embedded in PlayerProfile (profile visibility, results, ranking, club, DOB) — GET/PUT /player-profiles/{id}/privacy-settings subresource, owner-only update, hidden profiles return 404 to non-owners, public search respects visibility

    ✅ PL-F0205b
  • F02.05.06 Shipped

    Audit log of all data access — DataAccessAuditLog append-only model, record_access() best-effort service, GET /data-access-audit admin listing, GET /data-access-audit/me GDPR Art. 15 right-of-access, audit rows on profile reads/lists/exports

    ✅ PL-F0205b
  • F02.05.07 Shipped

    Cookie consent and tracking preferences — CookieConsentRecord model with 4 canonical categories, GET /cookie-consent/categories, POST /cookie-consent upsert, GET /cookie-consent/me, GET /cookie-consent/anonymous/{id}, PATCH /cookie-consent/{id}, POST /cookie-consent/{id}/withdraw, dual subject identification authenticated+anonymous, GET /me/privacy-overview combined snapshot

    ✅ PL-F0205b
  • F02.05.08 Shipped

    Support-access consent — user-side portal at /me/support/access-grants for consent over sys-operator impersonation. Approve / deny / revoke grants that reference a SysSupportTicket; every transition is audited. Full contract in docs/engineering/security/support-access-consent.md; operator-side in F21.02a.

    ✅ PL-T141
  • F02.05.09 Shipped

    User support portal — /me/support landing aggregate in both app/ (mobile-first) and admin/ (desktop-first) with three columns (pending requests, open tickets, recent history). Surfaces consent decisions inline without navigating away.

    ✅ PL-T143
  • F02.05.10 Shipped

    Access-history drill-in — GET /me/support/access-grants/{id}/activity-summary aggregates sys-audit rows by impersonation_id so the target user can see exactly how many reads / writes the operator performed and when.

    ✅ PL-T143
  • F02.05.11 Shipped

    Real-time pending-request notifier — SSE stream GET /me/support/access-grants/stream pushes new grant requests to the in-app banner; 2-second polling loop in the service, heartbeat every 15 s, 10-minute client-reconnect cap.

    ✅ PL-T143
  • F02.05.12 Shipped

    User-scoped support tickets — GET/POST /me/support/tickets, GET /me/support/tickets/{id}, POST …/reply, POST …/close. Reporter-only; operator-internal notes hidden from the thread; reuses the T130 intake_ticket service.

    ✅ PL-T143
  • F02.05.13 Shipped

    Per-channel notification preferences — GET/PUT /me/support/notifications/preferences toggles email / push / in-app. Break-glass transitions bypass opt-outs by compliance rule.

    ✅ PL-T143
  • F02.05.14 Shipped

    User activity log (self-view) — GET /me/activity-timeline?user_email=…&kinds=… returns the user-facing subset of the sys-timeline (PL-T145). Login, billing, notification, support-grant, incident and legal events flow through unchanged; sys_action rows are redacted to "Support agent viewed your account" and operator identities may be masked per tenant impersonation_actor_visibility. Surfaces: app/app/settings/activity-log.tsx (mobile) + admin/app/(dashboard)/privacy/activity-log.tsx (federation admins). Read-only; scoped automatically to the caller by email. 🔁 Go-backend 525ece23.m-4 (D-USERS m-4 t-1): rena GET /v1/me/activity-timeline — självvänd projektion av audit_log (F1-5, ingen ny tabell), självet som aktör∨subjekt i aktiv tenant (ur claim, aldrig body), användarvänd action-allowlist (auth./login./session./consent./parental_consent./profile./checkin./support./impersonation.), keyset/ListEnvelope, maskering bevarad verbatim, stödåtkomst-grants med aktör+tid (api/internal/http/dusers_gdpr.go, specs/api/endpoints/me-activity-timeline.md). app-UI 525ece23.m-4 t-2: Inställningar → Integritet & data visar aktivitetsloggen (ikon+text per typ, "Visa fler"-paginering, stödåtkomst med vem+när) via GET /v1/me/activity-timeline (app/app/integritet-och-data.tsx, specs/app/views/integritet-och-data.md).

    ✅ PL-T145
  • F02.05.15 Shipped

    Granular consent scope versioning — ConsentScopeDefinition per (tenant_id, scope_key, version) with monotonic version-bump and auto-superseding. Categories: marketing / analytics / operational / subprocessor / photo / research. Existing user consents stay attributable to the exact text the subject saw — no auto-revoke on republish. Endpoints: GET/POST /dpo/consent-scopes, GET /dpo/consent-scopes/{key}/versions, POST /dpo/consent-scopes/{key}/retire. Audit-log category gdpr_consent_versioning.

    ✅ PL-T221
  • F02.05.16 Shipped

    DPO-on-behalf DSAR — DpoAccessRequest model with status FSM (draft → submitted → in_review → fulfilled / rejected / cancelled). DPO files data_export / rectification / erasure / restriction / portability with mandatory legal_ground. Inline append-only audit_trail mirrored to global AuditLog (category=gdpr_dpo). Endpoints: GET/POST /dpo/access-requests, GET /dpo/access-requests/{id}, POST .../submit, POST .../cancel. Cancel blocked once fulfilment_started_at is set.

    ✅ PL-T221
  • F02.05.17 Shipped

    DSAR-job proxy for DPO portal — GET /dpo/data-export-jobs lists GdprPortabilityExport jobs initiated through the DPO console; GET /dpo/data-export-jobs/{id} returns status + signed download URL + expires_at. Reuses F16-DSAR pipeline — PL-T221 only initiates and links.

    ✅ PL-T221
  • F02.05.18 Shipped

    Cross-tenant DPO overview — POST /dpo/cross-tenant-overview for international DPOs (CEP/FIPJP) holding gdpr:dpo:read in 1–20 tenants. Returns per-tenant {tenant_id, open_requests, active_scopes}. In-memory parallel fan-out — no automatic cross-tenant DB join. Operator's own tenant always allowed; system-scope tokens get full body.tenants.

    ✅ PL-T221
  • F02.05.19 Shipped

    DPO capability gates — new capabilities gdpr:dpo:read, gdpr:dpo:write. Capability-deny logged to AuditLog with action=capability.deny so federation security teams can chase unauthorised attempts. System-scope tokens implicitly hold all capabilities.

    ✅ PL-T221
  • F02.05.20 Shipped

    DPO admin console — /(dashboard)/dpo (overview), /dpo/consent-scopes (publish + version history + retire), /dpo/access-requests (kanban with 6 columns + detail with shared AuditTrail component), /dpo/cross-tenant (per-tenant tile with setActiveTenant deep-link). Hidden for affiliate / venue / individual tenant types.

    ✅ PL-T221

Federated Login (OAuth Gateway)

F02.06
Shipped
How it works
  • F02.06.01 Shipped

    OAuth gateway — single redirect URI per provider, per-app routing via {app} path parameter, PKCE S256, one-shot state with SHA-256 binding hash, web (cookie) and native (exchange code) flows

    ✅ PL-1801 (infra), ✅ PL-F0206a (Apple provider + account linking)
  • F02.06.02 Shipped

    Sign in with Microsoft (Entra ID + personal MS account) — multitenant common endpoint, id_token JWKS-verifiering, email priority chain (Graph mail → id_token email → preferred_username → UPN), personal tenant → email_verified=False

    ✅ PL-1802, ✅ PL-F0206a
  • F02.06.03 Shipped

    Sign in with Google — id_token JWKS-verifiering mot accounts.google.com, sub som provider_user_id, email_verified trustas från id_token claim

    ✅ PL-1803, ✅ PL-F0206a
  • F02.06.04 Shipped

    Sign in with GitHub — read:user user:email scope, access token-validering via GET /user, X-OAuth-Scopes-enforcement, GET /user/emails primary+verified-policy, str(user["id"]) som provider_user_id

    ✅ PL-1804, ✅ PL-F0206a
  • F02.06.05 Shipped

    Apple Sign In för iOS — ES256 JWT client_secret (team_id+key_id+private_key), id_token-verifiering mot Apple JWKS, POST /auth/oauth/apple/native för ASAuthorizationController-flödet, email_verified=True (Apple-policy), private relay email-stöd

    ✅ PL-F0206a
  • F02.06.06 Shipped

    Account linking — flera identiteter per user, POST /auth/oauth/{provider}/link/{app} med 5-min fresh-auth-fönster (auth_time claim), GET /auth/me session-introspection, GET /auth/me/oauth-identities (safe fields only), DELETE /auth/me/oauth-identities/{provider} med last_auth_method-policy och upstream token-revokation

    ✅ PL-1806, ✅ PL-F0206a, ✅ Go-backend 4848185c.m-4 (F1-3 m-4, t-2): ren *inloggad self-service* — GET /auth/me/identities (säkra fält, IDOR-säker), POST /auth/oidc/{provider}/link/{application} (länk-läge ovanpå m-2:s OIDC-callback, fresh-auth, PKCE-S256 + binding-cookie, ingen ny session mintas), DELETE /auth/me/identities/{provider}; ?error=/problem+json-felkoder (reauth_required/link_conflict/email_unverified_conflict/last_auth_method); audit + anti-abuse (api/internal/http/auth_connected.go)
  • F02.06.07 Shipped

    Säker avlänkning med last-auth-method-skydd — DELETE /auth/me/oauth-identities/{provider} räknar email + phone + OAuth-identiteter, blockerar med 409 last_auth_method om borttagning skulle lämna noll metoder, upstream token-revokation (Google/GitHub/Apple)

    ✅ PL-F0206b, ✅ Go-backend 4848185c.m-4 (F1-3 m-4, t-2): DELETE /auth/me/identities/{provider} med atomär last-auth-method-räkning i WithTx (icke-revokerade OIDC-länkar + 1 om verifierad primary_email; andrafaktorer räknas ej) → 409 last_auth_method, plus best-effort upstream-revokations-hook (PL_OIDC_<NAME>_REVOCATION_URL, nere endpoint failar aldrig avlänk, A5) + upstream_revocation_attempted-audit
  • F02.06.08 Shipped

    Connected accounts UI i app och admin — admin/app/(dashboard)/connected-accounts.tsx (desktop-first, slate/indigo), app/app/settings/connected-accounts.tsx (mobile-first, i18n), fresh-auth-preflight, unlink-bekräftelse, provider-discovery via GET /auth/oauth/providers

    ✅ PL-F0206b
  • F02.06.09 Shipped

    Fresh re-auth-krav (≤5 min) vid linking — is_auth_time_fresh() med FRESH_AUTH_WINDOW_SECONDS=300, auth_time-claim i JWT, POST /link/{app} returnerar 401 reauth_required vid stale session, GET /auth/me exponerar auth_time_fresh-flagga

    ✅ PL-F0206b
  • F02.06.10 Shipped

    HTTP-only cookie session på .petanque.life — pl_access_token/pl_refresh_token med Secure; HttpOnly; SameSite=Lax; Domain=.petanque.life; Path=/, pl_oauth_binding-cookie med SHA-256-hash binding, cookie-cleanup efter callback

    ✅ PL-F0206b
  • F02.06.11 Shipped

    Native exchange code-flöde för mobil — OAuthExchangeCode med 64-byte base64url, 60s TTL, POST /auth/oauth/exchange med find_one_and_delete (single-use), POST /auth/oauth/apple/native för iOS ASAuthorizationController, tokens aldrig i URL

    ✅ PL-F0206b
  • F02.06.12 Shipped

    Per-provider email verification policy — compute_email_verified() med Google (trust claim), Microsoft (work=trust, personal=false), GitHub (primary+verified from /user/emails), Apple (alltid true), konservativ default false

    ✅ PL-F0206b
  • F02.06.13 Shipped

    Per-tenant OAuth-provider-overrides — TenantConfig.oauth_providers: list[OAuthProviderConfig] (provider+enabled+applications-tuple), ?application=app

    admin|web-query på GET /auth/oauth/providers filtrerar listan mot aktiv tenant så en federation kan stänga av specifika providers per surface utan globalt avhopp | ✅ PL-T206
  • F02.06.14 Shipped

    Sign in with Apple på web — Service ID + private-key + JWKS-verifiering återanvänds från native-flödet; /.well-known/apple-developer-domain-association(.txt) serveras från Settings.APPLE_DOMAIN_ASSOCIATION så ägaren kan rotera filen utan deploy

    ✅ PL-T206
  • F02.06.15 Shipped

    OAuth-knappar i tenant-CMS-login (/account/login) — web/src/app/account/login/page.tsx + AccountLoginClient.tsx använder shared fetchOAuthProviders med application=web, renderar PROVIDER_META-färgade knappar inom tenantens BookingThemeStyle-brand, hanterar ?error= via auth.error.<code>-i18n med fallback till auth.error.generic

    ✅ PL-T206
  • F02.06.16 Shipped

    Apple på admin-konsolen — apple i ADMIN_PROVIDER_WHITELIST + ADMIN_PROVIDER_DEFAULTS; admin/src/lib/oauth.ts.fetchProviders() skickar ?application=admin så per-tenant-overrides respekteras

    ✅ PL-T206
  • F02.06.17 Shipped

    Multi-tenant user sessions / tenant-kontextbyte — en auth_identity är cross-tenant och kan byta aktiv tenant utan re-auth: GET /auth/me returnerar tillgängliga tenants + vald tenant_id, POST /auth/tenant/select { tenant_id } mintar en tenant-scopad access-token (sätter tenant_id-claim) efter verifierat medlemskap, auth_time bevaras, kontext överlever refresh (refresh_token_family.tenant_id), web-cookie/native-body (aldrig token i URL), icke-medlem → 403 tenant_forbidden; en tenant-scopad token bär bara kontext (AuthZ är F1-4)

    ✅ Go-backend 4848185c.m-4 (F1-3 m-4, t-3): api/internal/http/tenant.go + me-utökning i auth.go; medlemskaps-läsmodellen auth_identity_tenant är en dokumenterad D-USERS/F1-9-hook (list_available_tenants/has_tenant_membership fulltestade, källan seedad nu, F1-9-tenants/D-USERS-profiler senare); audit tenant_selected/tenant_select_forbidden; ingen tenants-tabell/hierarki byggd (F1-9)

Multi-factor Authentication & Advanced Security

F02.07
Shipped

> **Go-backend `4848185c.m-3` (F1-3 m-3) — MFA-orkestrering (ny ren Go-stack):**

How it works
  • F02.07.01 Shipped

    TOTP authenticator app (Google Authenticator, 1Password, Authy) — enrollment detection via GET /auth/mfa/methods

    ✅ PL-F0207a
  • F02.07.02 Shipped

    SMS-OTP via 46elks — POST /auth/mfa/sms/challenge, POST /auth/mfa/sms/verify, SHA-256-hashad kodlagring, PII-maskering av telefonnummer, single-use med 5-försökstak

    ✅ PL-F0207a
  • F02.07.03 Shipped

    Email-OTP fallback — POST /auth/mfa/email/challenge, POST /auth/mfa/email/verify, break-glass recovery för tappad SIM/authenticator

    ✅ PL-F0207a
  • F02.07.04 Shipped

    WebAuthn / Passkeys — enrollment detection via GET /auth/mfa/methods, WebAuthnCredential-count

    ✅ PL-F0207a
  • F02.07.05 Shipped

    MFA enforcement per roll — GET /auth/mfa/enforcement med policy-besked (required/enrolled/grace_period), GET /auth/mfa/policy, PUT /auth/mfa/policy admin-upsert, per-tenant konfigurerbara required_roles/allowed_methods/grace_period_days/fresh_window_seconds

    ✅ PL-F0207a
  • F02.07.06 Shipped

    Recovery codes — 10 engångskoder per användare, POST /auth/mfa/recovery-codes/generate, GET /auth/mfa/recovery-codes/count, POST /auth/mfa/recovery-codes/verify med token-upgrade, SHA-256-hashad lagring, regenerering invaliderar gamla koder

    ✅ PL-F0207b
  • F02.07.07 Shipped

    Trusted devices med 30-dagars skip — POST /auth/mfa/trusted-devices registrering efter MFA, GET /auth/mfa/trusted-devices/check fingerprint-kontroll, GET /auth/mfa/trusted-devices lista, DELETE /auth/mfa/trusted-devices/{id} revokering, SHA-256-hashade fingerprints

    ✅ PL-F0207b
  • F02.07.08 Shipped

    Session management — GET /auth/sessions lista aktiva sessioner via RefreshTokenFamily, DELETE /auth/sessions/{id} revokera enskild session, POST /auth/sessions/revoke-all revokera alla utom aktuell

    ✅ PL-F0207b
  • F02.07.09 Shipped

    Brute-force-skydd med exponentiell backoff — BruteForceRecord-modell, 1s→2s→4s→8s→16s→30s (cap) delay per konsekutivt misslyckande, automatisk reset vid lyckad inloggning, applicerat på recovery-code-verify

    ✅ PL-F0207b
  • F02.07.10 Shipped

    Säkerhetsmail vid nya inloggningar och MFA-ändringar — SecurityEvent-modell med append-only audit, SecurityNotificationService dispatchar email vid new_login, mfa_enrolled, mfa_disabled, recovery_code_used, trusted_device_added/removed, session_revoked, GET /auth/security-events lista

    ✅ PL-F0207b
  • F02.08.01 Shipped

    individual_context — IndividualContext datamodell med user_id (unikt), display_name, country, language, birth_year, preferred_position (pointer/shooter/middle), handicap_hint, privacy_public_profile, subscription_tier (FREE/PREMIUM), premium_valid_until, Stripe-fält; 30-sekunders signup via POST /public/signup/individual; profil via GET/PATCH /me/individual-context

    ✅ PL-T005
  • F02.08.02 Shipped

    casual_match_p2p — IndividualCasualMatch venue-lös match mellan två individual-kontext; POST /me/casual-matches med FREE-tier-gating (50/månad), POST /me/casual-matches/{id}/verify motpartsverifiering, GET /me/casual-matches historik

    ✅ PL-T005
  • F02.08.03 Shipped

    friend_graph — IndividualFriend med status PENDING/ACCEPTED/BLOCKED; POST /me/friends/request, POST /me/friends/{id}/accept, POST /me/friends/{id}/block, GET /me/friends filtrering

    ✅ PL-T005
  • F02.08.04 Shipped

    subscription_tier_gate — individual_tier.py service med is_premium_active() (inkl. grace-period), check_casual_match_limit() (50/månad FREE), require_premium(), 402 Payment Required med error_code="premium_required"; upgrade-to-premium dev-stub, downgrade med bevarad premium_valid_until

    ✅ PL-T005
  • F02.09.01 Shipped

    chain_tenant — TenantType.CHAIN med ChainMembership (chain↔venue-länk), super-admin-onboarding via POST /admin/tenants/chain, venue-länkning via POST /chain/{id}/add-venue + POST /venue/{id}/accept-chain/{id}, bryt-länk via DELETE /chain/{id}/venues/{id} (soft-deactivate)

    ✅ PL-T016
  • F02.09.02 Shipped

    cross_venue_league — ChainLeague + ChainLeagueEntry; POST /chain/{id}/leagues med venue-multiselect, GET /chain/{id}/leagues/{id}/leaderboard aggregerat cross-venue-rankning, season_start/end-validering

    ✅ PL-T016
  • F02.09.03 Shipped

    chain_passport — ChainPassport loyalty-tracker med BRONZE (5+)/SILVER (20+)/GOLD (50+) tier-progression baserat på rolling visits; GET /chain/{id}/passport/{user_id}, POST /chain/{id}/passport/{user_id}/visit QR-check-in

    ✅ PL-T016
  • F02.09.04 Shipped

    corporate_event_module — CorporateEvent med INQUIRY→QUOTED→BOOKED→COMPLETED→CANCELLED pipeline; POST/GET/PATCH /chain/{id}/corporate-events, host_venue_tenant_id validerad mot aktiv ChainMembership

    ✅ PL-T016
  • F02.09.05 Shipped

    white_label_app — ChainWhiteLabelConfig med app_name, colors, logos, custom_domain, hide_petanque_life_branding; PUT/GET /chain/{id}/white-label, ChainBrandingProvider i app för dynamisk re-branding

    ✅ PL-T016
  • F02.09.06 Shipped

    multi_currency_chain_invoice — ChainInvoiceBatch med per-country ChainInvoiceLineItem (local_currency, ecb_rate_used, ecb_rate_date); kvartalsvis generering via tools/generate_chain_invoice_batch.py med ECB-fixing

    ✅ PL-T016
  • F02.09.07 Shipped

    chain_passport_v2 — ChainPassport19 med points-baserad tier-progression (REGULAR/GOLD/PLATINUM), passport_id format {CHAIN_CODE}-PASS-NNNNNN, unique index (tenant_id, auth_identity_id), ChainVenueVisit per-visit records; POST /chain/passports (create, 409 duplicate), GET /chain/passports/me, GET /chain/passports/{id}, PUT /chain/passports/{id} (profile + tier override + ban)

    ✅ PL-T019
  • F02.09.08 Shipped

    chain_checkin_points — POST /chain/passports/{id}/check-in (QR-scan, creates visit, increments total_games, 403 if banned), POST /chain/passports/{id}/award-points (manual points by venue staff, creates visit with points_earned, updates total_points)

    ✅ PL-T019
  • F02.09.09 Shipped

    chain_leaderboard — GET /chain/leaderboard med ?venue_id= (venue-filtrerat via aggregation), ?period=month

    year|alltime (tidsfiltrerat), top-50 paginering; alltime query direkt på total_points, period-baserat via an aggregation query på ChainVenueVisit | ✅ PL-T019
  • F02.09.10 Shipped

    chain_visit_history — GET /chain/passports/{id}/visits paginerad venue-besökshistorik sorterad senast-först; composite index (chain_passport_id, visited_at DESC) för performance

    ✅ PL-T019
  • F02.09.11 Shipped

    chain_passport_admin_ui — PassportSearch admin-vy med passport-ID-sökning, detail panel (namn, tier badge, status badge, stats, visit timeline), tier override (confirmation dialog), ban/unban actions; LeaderboardView med period tabs (All Time/This Year/This Month), venue filter dropdown, auto-refresh 60s, rank badges (#1 gold, #2 silver, #3 bronze)

    ✅ PL-T019
  • F02.10.01 Shipped

    tenant_tier_matrix — TENANT_TIER_MATRIX i services/tenant_tier.py mappar varje TenantType till included_domains + excluded_subsystems + all_access-flagga; FEDERATION/CONTINENTAL/FIPJP/SANDBOX = all_access, CLUB/AFFILIATE/VENUE/CHAIN/EVENT/INDIVIDUAL = matrix-baserat; byte-equivalent med www/src/data/tenant-tiers.ts (verifieras av services/tenant_tier_matrix_sync.py + test_pl225_tier_matrix_sync.py)

    ✅ PL-T225
  • F02.10.02 Shipped

    requires_subsystem — API-dependency i security/tenant_tier.py som gatear endpoints på subsystem-nivå (F<NN>.<MM>-format); andra gating-lagret efter capability-RBAC; 401 unauthenticated_tenant_context när tenant saknas, 403 tenant_type_not_eligible med subsystem + tenant_type i body när matrix/addon ej täcker

    ✅ PL-T225
  • F02.10.03 Shipped

    tenant_tier_resolver — TenantTierResolver med Redis 5-min cache (tenant_tier:{tenant_id}-nycklar), graceful fallback till per-request uppslag i datalagret när Redis saknas; is_subsystem_allowed() kollar all_access → matrix → aktiva addons; invalidate_cache(tenant_id) anropas av alla mutationer (grant/revoke/change_type)

    ✅ PL-T225
  • F02.10.04 Shipped

    tenant_tier_addon — TenantTierAddon datamodell med unique index (tenant_id, subsystem_id), optional expires_at (null = permanent), billing_reference, required reason (3–500 chars), granted_at/granted_by-audit; addons låser upp enskilda subsystems utan att flippa tenant_type

    ✅ PL-T225
  • F02.10.05 Shipped

    sys_tenant_tier_admin — routes/sys_tenant_tier.py med GET /sys/tenants/{id}/tier (read, support/finance/security), POST /sys/tenants/{id}/tier/addons (grant, finance/security), DELETE /sys/tenants/{id}/tier/addons/{addon_id} (revoke, finance/security), PATCH /sys/tenants/{id}/tier/type (security only, fresh-auth-required); alla emitterar tenant.tier.*-platform events + AuditLog-rad

    ✅ PL-T225
  • F02.10.06 Shipped

    me_tenant_tier — GET /me/tenant-tier returnerar tenant-snapshot (tenant_type, all_access, allowed_subsystems, addons-trim, included_domains, excluded_subsystems) för app/admin client-side hide; ["*"] när all_access så frontend slipper enumerera hela katalogen; useAllowedSubsystems()-hook i admin cachar 5 min med tenant-switch-bump

    ✅ PL-T225
  • F02.10.07 Shipped

    tier_aware_admin_ui — useAllowedSubsystems() filtrerar visibleSectionsFor() (sektioner med subsystem-mapping döljs i sidebar/CommandPalette när delsystemet ej är allowed); TenantTierUpgradeModal öppnas från modul-event-bus när någon endpoint returnerar 403 tenant_type_not_eligible; modal visar subsystem-id + tenant_type + kontaktuppgifter

    ✅ PL-T225
  • F02.10.08 Shipped

    sys_tenant_tier_view — sys/app/(dashboard)/tenants/[id]/tier.tsx är operator-arbetsytan: tier-summary-card (tenant_type-chip, included domains, excluded subsystems), addons-card (active addons med revoke), grant-addon-form (subsystem_id-regex, expiry, billing_reference, reason), change-tenant-type-card (chip-väljare för 9 TenantType-värden, fresh-auth-prompt)

    ✅ PL-T225
  • F02.10.09 Shipped

    tier_jobs — jobs/tenant_tier_jobs.py med tenant-tier-addon-expiry-tick (15 min, hård-raderar TenantTierAddon där expires_at <= now, behåller permanenta) + tenant-tier-matrix-audit-tick (1 h, jämför API- och www-matrix byte-by-byte, emitterar tenant.tier.matrix_drift på diff för operator-larm)

    ✅ PL-T225
  • F02.11.01 Shipped

    admin_session_devlogin — app-lokal session/token-provider (admin.auth.token) + AuthzClient-fabrik + inline "Dev login"-affordans på /login (dead-stripped i prod-bundle via NEXT_PUBLIC_ENVIRONMENT); auth-redirect till /login när session saknas

    ✅ f07177c7.m-6.t-2
  • F02.11.02 Shipped

    admin_roller_lista — roll-lista (GET /roles) + CRUD (POST/PATCH/DELETE /roles); systemmallar (is_system) visuellt åtskilda (ikon + text) utan redigera/radera-affordans

    ✅ f07177c7.m-6.t-2
  • F02.11.03 Shipped

    admin_capability_matris — grid roll × capability grupperad per namnrymd (kollapsbar progressive disclosure), GET/PUT /roles/{id}/capabilities (REPLACE), GET /capabilities; systemmall skrivskyddad

    ✅ f07177c7.m-6.t-2
  • F02.11.04 Shipped

    admin_rolltilldelning — tilldela roll till principal i scope (GET/POST/DELETE /role-assignments); tenant-scope fullt, org_node/resurs via degraderat scope_id-fält (F1-9-seam)

    ✅ f07177c7.m-6.t-2
  • F02.11.05 Shipped

    admin_rollrequests_inbox — läs + skapa role_requests (GET/POST /role-requests), status ikon + text; godkänn/avslå disabled (F1-6 äger statusmaskinen)

    ✅ f07177c7.m-6.t-2
  • F02.11.06 Shipped

    admin_ui_gating — all nav/åtgärd gatad mot /me/capabilities (fail-closed) via delad useCapabilities/<RequireCapability>; enhetlig RFC 9457-felpresentation (messageForProblem), 404-ej-403-semantik bevarad

    ✅ f07177c7.m-6.t-2
  • F02.12.01 Shipped

    sys_session_devlogin — app-lokal session/token-provider (sys.auth.token) + AuthzClient-fabrik + inline "Dev login"-affordans på /login (dead-stripped i prod via NEXT_PUBLIC_ENVIRONMENT); auth-redirect till /login när session saknas

    ✅ f07177c7.m-6.t-3
  • F02.12.02 Shipped

    sys_console_gating — konsol-nav (/roller, /audit) gatad fail-closed via oraklet capability-check (sys.console.access); tenant-token får ingen konsol; orakel-konsistent utan 403-benägna /me/capabilities

    ✅ f07177c7.m-6.t-3
  • F02.12.03 Shipped

    sys_roll_matris — rollkatalog (GET /sys/roles) + uppslag av identitets sys-roller (GET /sys/users/{email}/roles); ikon + text, ingen tenant-branding

    ✅ f07177c7.m-6.t-3
  • F02.12.04 Shipped

    sys_roll_grant_revoke — tilldela (POST /sys/users/{email}/roles) + återkalla (DELETE …/{role}) under fyra ögon; self-grant → 400 self_grant_blocked presenterat blockerande utan radändring; roller omläses från API efter mutation

    ✅ f07177c7.m-6.t-3
  • F02.12.05 Shipped

    sys_fresh_auth_seam — 401 reauth_required → uttrycklig sys-reauth-affordans (navigerar till /login), mutationen återupptas aldrig tyst; dokumenterat seam (m-5 saknar re-auth-endpoint, F1-3/F1-10)

    ✅ f07177c7.m-6.t-3
  • F02.12.06 Shipped

    sys_audit_seam — audit-ytan (/audit) som skal + seam: tomtillstånd "data levereras av F1-5", inga audit-row-* hävdas; live audit-läsning ägs av F1-5

    ✅ f07177c7.m-6.t-3
  • F02.13.01 Shipped

    web_session_devlogin — app-lokal session/token-provider (web.auth.token) + AuthzClient-fabrik + inline "Dev login"-affordans på /login (dead-stripped i prod via NEXT_PUBLIC_ENVIRONMENT); anonym besökare felar closed utan att krascha den publika ytan

    ✅ f07177c7.m-6.t-4
  • F02.13.02 Shipped

    web_action_gating — gatad genväg "Förbundsadministration" (web-gated-action) i SiteHeader gatad mot admin.roles.manage via <RequireCapability> (fail-closed); dold för anonym/utan-capability, klick ger aldrig 403; full white-label (endast design-tokens)

    ✅ f07177c7.m-6.t-4
  • F02.13.03 Shipped

    app_session_devlogin — app-lokal session/token-provider (app.auth.token) + AuthzClient-fabrik + dev-login-affordans på /login (dead-stripped i prod via EXPO_PUBLIC_ENVIRONMENT); Metro-monorepo-resolution av @petanque-life/shared

    ✅ f07177c7.m-6.t-4
  • F02.13.04 Shipped

    app_action_gating_per_tenant — gatad åtgärd "Anmäl lag" (app-action-register-team) mot competition:register; cross-tenant <TenantSwitcher> (app-tenant-switcher/app-tenant-option-<key>) som laddar om /me/capabilities per aktiv tenant (X-Tenant-Id) — ingen capability-läcka mellan tenants (AC-14); fail-closed under omladdning

    ✅ f07177c7.m-6.t-4