What was done
The "Oxid Edition" client-side 2FA authenticator was completely rebuilt from scratch:
- Complete template replacement: the template has been replaced with pure Vue 3 + Vite. The backend is no longer used—the app works 100% offline.
- Industrial-Metallic design system: Rust Orange
#E25822over Dark Charcoal#1A1B1E, glassmorphism, metallic edges,JetBrains Mono(for codes) andInter(for the UI). - Full 2FA token lifecycle: PIN generation → unlock → add via QR/manually → display with countdown → copy → delete → export/import → wipe.
- Real cryptography:
- The PIN is hashed using PBKDF2 + SHA-256, 25,000 iterations** for verification.
- Token storage in
localStorageis encrypted with AES-256 CBC using a key derived from the PIN using the same PBKDF2 (but with a different salt). - Real TOTP:
otpauthlibrary (RFC 6238), supports SHA-1/256/512, 6/7/8 digits, 30/60 sec periods. - Real QR scanner via
html5-qrcodewithotpauth://URI recognition and parsing of all parameters (issuer, label, algorithm, digits, period). - Categories with colored dots: Crypto, Social, Work, Finance, Email, Gaming, Other — with automatic detection by known providers (GitHub → Work, Coinbase → Crypto, etc.).
- Themes: dark (default) and light steel, saved in
localStorage. - Biometric unlock via WebAuthn (Touch ID / Face ID / Windows Hello).
WebAuthn can be used to "wrap" the encrypted key: the PIN remains as a backup, while in 95% of cases, the user unlocks the app with a single touch. - Auto-Lock on Inactivity.
For example, after 60 seconds of idle time — an automatic call toauth.lock(). A mandatory feature for corporate use cases. - Testing: Automatically ran 17 scenarios — all passed (100%).
Security Logic
- On first launch, the user creates a 4-digit PIN.
- The PIN is not stored in plaintext. Only the PBKDF2 hash (oxid:pin-hash) is stored.
- The PIN itself is stored in the Pinia store's RAM only during an unlocked session. After a page refresh or pressing "Lock," it is erased.
- All tokens are serialized as JSON and encrypted with AES-256 before being written to localStorage under the oxid:vault key. Without the correct PIN, they cannot be decrypted—even if an attacker gains access to local storage.
- The .oxid export file is the same encrypted payload; it can be safely sent to yourself via email or the cloud: without the PIN, it is useless.
TOTP Code Generation
- A timer on each card is triggered once per second: the code and remaining time are recalculated.
- The progress bar under the code gradually shrinks from 100% to 0%.
- Five seconds before the code expires, the bar turns red—a visual warning.
- Tap the code → copy to clipboard + metal "toast."
Features — Detailed List
PIN Screen
- 3 automatic modes: Create / Confirm / Enter (depending on the presence of 'oxid:pin-hash').
- Indicator dots fill with a rusty orange color with a slight glow.
- If the PIN doesn't match, a shake animation is played and the user is reset to Create mode.
- Background — low-contrast brushed steel texture (10–15% opacity).
Vault (main screen)
- Sticky-glass header: number of codes, Lock and Settings buttons.
- Search field — real-time filtering by issuer / label / category.
- Horizontal filter bar with 7 categories (colored dot + uppercase label).
- Token cards: issuer, label, algorithm badge (for SHA-256/512), 6-digit code with a dot separator in the middle, thin progress bar.
- FAB "+" — add token.
- Empty state with a 3D illustration and a "Forge First Token" button.
Add token
- QR scan: camera permission request → live video with reticle (angle brackets + ticker) → auto-parsing otpauth URI → auto-category → save.
- Manual: issuer / label / Base32 secret with validation (A-Z2-7 only), select categories (with issuer auto-suggestion), expandable advanced (algo / digits / period).
Settings
- Theme: Dark / Light switch (persistent).
- Export Vault: Download an encrypted .oxid file with a timestamp.
- Import Vault: Upload file → enter the PIN used for export → merge without duplicates.
- Lock Now: Return to the PIN screen.
- Wipe All Data: A modal requiring the word 'WIPE' for confirmation; completely destroys all 'oxid:*' keys.
Toasts
- Appears at the bottom center.
- Options: 'success' (rust) / 'error' (red).
- Auto-hide after 2.2 seconds with an easing animation.
Technical Decisions & Justification
| Tech / Feature | Decision & Justification |
|---|---|
| PBKDF2 (25k iterations) | Balance between "expensive for brute force" and "acceptable for mobile" |
| AES-256 CBC + PKCS7 | Industry standard, available in crypto-js with zero extra dependencies |
otpauth (instead of custom TOTP) |
Full RFC 6238 compliance; native compatibility with Google/GitHub/AWS QR codes |
html5-qrcode |
Works directly in <video> without WASM; mobile-first by default |
localStorage |
Specification rules out server sync; IndexedDB is overkill for this data volume |
| PIN in memory, not storage | If an attacker dumps the storage, they only get encrypted data |

