Safari (iPadOS/iOS) blocks SameSite=Strict cookies on the initial
top-level navigation when it considers the request cross-site (links
from messengers, email, QR codes). The CSRF cookie was therefore never
set on first visit, and the subsequent login POST failed with 403
"CSRF failed".
Switch the CSRF cookie to SameSite=Lax — this is the OWASP recommended
default and matches industry practice. The auth (session) cookie keeps
SameSite=Strict, since it is only issued after a successful first-party
login POST and needs the stricter binding.
- admin_page: slot shown as occupied based on ACTIVE status only (no time cutoff)
- go_service: busy slots checked by ACTIVE status (no cutoff) — cleanup_loop handles expiry
- startup_event: cleanup_loop starts regardless of ENABLE_STARTUP_MAINTENANCE flag;
pool/container init guarded by the flag separately
- cleanup_loop: RDPSLOT sessions expire correctly and trigger container restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New RdpSlot model (rdp_slots table): service_id, rdp_username,
rdp_password, container_name
- Each slot gets a dedicated portal-rdpslot-<slug>-<id> container with
Traefik route /rdp/<slot_id>/ and restart_policy=unless-stopped
- go_service: RDP services with slots use pool allocation — finds first
free slot (not occupied by active session), returns 503 if all busy
- session_status + session_view: handle RDPSLOT: container_id prefix
- terminate_session_record: restarts slot container in background on close
- session_redirect_url: RDPSLOT sessions redirect to /s/<id>/view
- startup_event: starts containers for all configured slots on boot
- Admin: POST /api/admin/services/{id}/rdp-slots, DELETE /api/admin/rdp-slots/{id}
- admin.html: slot management UI (list, add, delete); removed ACL exclusivity
- set_acl: removed RDP 1-user exclusivity — RDP services now assignable to many
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- #mobile-wall uses width:100vw/height:100vh/overflow:hidden to prevent
text overflow on narrow screens
- All font sizes via clamp(), word-break:break-word on text elements
- MONT logo pinned to top (position:absolute), height clamp(4rem,16vw,6rem)
- Made by Galyaviev footer pinned to bottom
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dashboard.html: overlay div moved before <script> so getElementById works;
double rAF ensures browser paints spinner before navigation
- main.py: pooled_rdp route fix — session_status now returns /svc/<slug>/
route and redirect_url for POOL: RDP sessions (was always ready instantly)
- docker-compose.yml: parametrise env vars via .env for easier tuning
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>