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

Identity & Access Management

110 features · 7 subsystems

User identity, authentication, authorization, and role management across the entire federation hierarchy. Leverages Craft-Easy's comprehensive auth stack and capability-based access control.

User Registration & Profile

F02.01
Platform+
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)
  • 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)
  • 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)

Authentication

F02.02
Platform
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)
  • 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 craft-easy require_auth-buggen)
  • F02.02.04 Shipped

    WebAuthn passkeys

    ✅ PL-F0202a (/webauthn/register/options, /register, /credentials list+delete, petanque-override av craft-easy's 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)
  • 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)
  • 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)
  • 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
Platform+

### Predefined Role Templates

How it works
  • F02.03.01 Shipped

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

    ✅ PL-F0203a
  • F02.03.02 Shipped

    Role definition with capability sets

    ✅ PL-F0203a
  • 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
  • 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)
  • 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
Platform+

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 rå Beanie-query, 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)
  • 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
Platform+
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)

    ✅ 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.

    ✅ 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
Platform+
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
  • 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
  • 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

Multi-factor Authentication & Advanced Security

F02.07
Platform+

### F02.08 — Individual Context (PL-T005)

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 Beanie-dokument 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 MongoDB aggregation pipeline 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 — FastAPI-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 Mongo-lookup 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 Beanie-modell 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