v1.3.0 — Native client support, device pairing, security hardening
What's new
Native client support (Swiftfin, Findroid, etc.)
For users WITH a Jellyfin password — app passwords:
Go to `/TwoFactorAuth/Setup` → App Passwords → Generate. Enter the generated password in Swiftfin's password field. Each app password is independent, labeled, and revocable. Hashed with PBKDF2-SHA256 (200k iterations).
For passwordless users — device pairing:
- Open Swiftfin, tap Sign In → login fails (2FA required).
- Open Jellyfin web on a verified browser → `/TwoFactorAuth/Setup` → Devices Waiting for Approval shows your Swiftfin device.
- Click Trust → done. Go back to Swiftfin and sign in again — it works.
Redesigned Setup page
Unified dashboard: status + TOTP enrollment + recovery codes + email backup + pending device approvals + paired devices + app passwords + trusted browsers + active sessions. All in one page, dark theme.
Sidebar injection
"Two-Factor Auth" now appears in Jellyfin's nav drawer and settings page, matching your theme's styling via the AchievementBadges `.navMenuOption` pattern.
Admin fixes
- Users tab fixed — was 404'ing because it used the wrong config endpoint (`System/Configuration` instead of `getPluginConfiguration`).
- Test SMTP button and custom TOTP issuer name (from v1.2.x) preserved.
Security audit fixes
- Atomic user data updates — `MutateAsync` prevents lost writes under concurrent requests.
- Rate-limited app-password verification — 10 attempts/min per IP in the middleware.
- Bounded pending dict — 5000 global cap, 50 per user, 30-min TTL.
- Chunked-encoding support — middleware can now extract the password from chunked request bodies.
- Fail-closed — middleware returns 503 (not the raw auth token) if challenge issuance throws.
- Pending cleanup on disable — orphaned pending pairings are removed when 2FA is disabled.
Limitations
- App passwords only work for users who have a Jellyfin password. For passwordless users, use device pairing instead.
- QR pairing (scan a code to pair a device) deferred to v1.3.1. The auto-pending flow covers the same use case.
- Native clients see "login failed" on first attempt — they have to retry after approval. There's no way to make them show a "pending approval" screen without client-side changes.
Upgrade
Install over v1.2.x. Restart Jellyfin. Hard-refresh all browsers (Ctrl+Shift+R).