{"id":"c0026","filename":"c0026_faza2_multitenant_middleware.dok.json","weise3_id":"","tip":"plan_faza","naziv":"c0026 — FAZA 2: Multi-tenant SudacMiddleware prosirenje","kreator":"CC","datum":"2026-05-03T13:44:22.604125+00:00","snippet":"","status":"","prev_weise3":"","bunker_l":"#00d4ff","full":{"tip":"plan_faza","kreator":"CC","nastao":"2026-05-03T13:44:22.604125+00:00","naziv":"c0026 — FAZA 2: Multi-tenant SudacMiddleware prosirenje","prev_chain":"c0025","parent_master":"c0024","cilj":"Sigurnosna granica po tenantu — komitent A ne vidi komitenta B; Limit-admin vidi sve klijente Limita; Konjik root vidi sve.","koraci":{"2.1":{"naziv":"Sesija dobija tenant_scope[]","akcije":["POST /genesis/auth/verify dodaje tenant_scope: [{\"tenant_id\": \"limit\", \"uloge\": [\"knjigovodja\"]}]","tenant_scope se izvodi iz osoba_wid (tko je login-nuo) preko get_user_tenants()","Pohranjeno u Redis session (tier vec je tu, sad i tenant_scope)"],"fajlovi":["api/genesis_auth.py"]},"2.2":{"naziv":"SudacMiddleware tenant check","akcije":["Novi helper _provjeri_tenant(request, path, tenant_required) -> JSONResponse | None","Endpointi koji su tenant-scoped (svi /api/v1/firme/, /osobe/, /partneri/, /propter/sobe/) MORAJU extractovat path-tenant_id i provjerit je li session.tenant_scope dozvoljava read/write","403 + savjet ako ne dozvoljava (slicno c0021 tier check)"],"fajlovi":["services/sudac_middleware.py"]},"2.3":{"naziv":"Cross-tenant transfer — eksplicitni write","akcije":["POST /api/v1/transfers/ — npr. transfer skladiste-podruznica (Optika Zagreb -> Optika Split)","Zahtijeva: tier=native + osoba u tenant_scope sa role=voditelj_skladista ILI konjik_root_admin","Sealed dokarh tip=transfer.cross_tenant"],"fajlovi":["api/transfers.py"]},"2.4":{"naziv":"Tenant impersonacija (Limit-admin glumi komitenta)","akcije":["POST /api/v1/tenants/{tenant_id}/impersonate — Limit-admin moze otvoriti sesiju kao komitent A","Audit: svaki impersonate event = dokarh sealed (zna se tko je radio u cijem ime)","Session \"act_as\": tenant_id=\"komitent_a\", original_session: <wid>","UI badge: \"Radis u ime Komitent A — vrati se na svoj racun\""],"fajlovi":["api/tenants.py","sucelje/web/components/TenantImpersonateBar.tsx"]}},"acceptance_kriteriji":["Login kao komitent_a, GET /partneri/ -> samo svoje partnere","Login kao komitent_a, GET /firme/by-slug/komitent_b -> 403 + tenant_nedovoljan","Login kao limit_admin, GET /firme/by-slug/komitent_b -> 200 (jer Limit ima manager scope)","Login kao konjik_root, GET bilo koji firma -> 200","POST /tenants/komitent_a/impersonate kao limit_admin -> nova sesija + audit dokarh","Playwright: 4 cross-tenant pokusaja, sve 403 osim za root/manager"]}}