From b4de3e78d7a47007b735997e30c8243e27c17058 Mon Sep 17 00:00:00 2001 From: Nguyen Ngo Date: Sat, 9 May 2026 15:01:33 -0400 Subject: [PATCH] 05/09 Remove conflicted files --- ...E.sync-conflict-20260507-154033-FEZULOS.md | 516 ------------------ ...E.sync-conflict-20260507-154213-FEZULOS.md | 288 ---------- 2 files changed, 804 deletions(-) delete mode 100644 CLAUDE.sync-conflict-20260507-154033-FEZULOS.md delete mode 100644 README.sync-conflict-20260507-154213-FEZULOS.md diff --git a/CLAUDE.sync-conflict-20260507-154033-FEZULOS.md b/CLAUDE.sync-conflict-20260507-154033-FEZULOS.md deleted file mode 100644 index 33673cb..0000000 --- a/CLAUDE.sync-conflict-20260507-154033-FEZULOS.md +++ /dev/null @@ -1,516 +0,0 @@ -# PassKeeper — Password Manager Web App & Browser Extension - -## Project Overview - -A full-featured password manager web app and browser extension modelled after LastPass. Users can store, organise, and autofill credentials securely with a zero-knowledge architecture. - ---- - -## Tech Stack - -### Development (Windows) - -- **Backend:** Python 3.12, Flask 3.x -- **Database:** MySQL 8.x -- **Frontend:** Vanilla JS (Web Crypto API) + Jinja2 templates -- **Dev server:** `python run.py` - -### Production (Ubuntu) - -- **Web server:** Nginx (reverse proxy, TLS termination) -- **WSGI server:** Gunicorn -- **Process manager:** systemd -- **Database:** MySQL 8.x -- **TLS:** Let's Encrypt / Certbot - ---- - -## Architecture - -``` -Browser Extension <──────────────────────────────────────┐ -Web App <──> Nginx ──> Gunicorn ──> Flask App │ - │ │ - MySQL DB │ - │ - REST API (JSON, HTTPS) ───────┘ -``` - -### Project File Structure - -``` -passkeeper/ -├── app/ -│ ├── __init__.py # App factory, blueprints, CSRF exemptions, security headers -│ ├── config.py # DevelopmentConfig / ProductionConfig -│ ├── models/ -│ │ ├── user.py # Argon2id, TOTP encrypted, ECDH keys, recovery -│ │ ├── vault_item.py # enc_data, iv, enc_name, iv_name -│ │ ├── folder.py -│ │ ├── token_blacklist.py # JWT revocation (jti + expires_at) -│ │ ├── shared_item.py # ECDH-encrypted cross-user shares -│ │ ├── emergency_access.py # State machine -│ │ └── audit_log.py -│ ├── routes/ -│ │ ├── auth.py # Register, login, MFA, logout, refresh, change-password, recovery -│ │ ├── vault.py # CRUD + GET /export + POST /import -│ │ ├── folders.py -│ │ ├── sharing.py -│ │ └── emergency.py -│ ├── services/ -│ │ └── auth_service.py # Argon2id, JWT, blacklist, @require_jwt, TOTP encrypt/decrypt -│ ├── static/ -│ │ ├── css/app.css # Full responsive stylesheet; collapsible group styles; tag badge styles -│ │ └── js/ -│ │ ├── crypto.js # deriveAuthHash, deriveVaultKey, encryptItem, decryptItem, -│ │ │ # encryptName, decryptName, generateSalt -│ │ ├── auth.js # Login/register + TOTP MFA + VaultSession -│ │ ├── recover.js -│ │ ├── sharing.js # ECDH P-256 -│ │ └── vault.js # Full vault UI — see "Key Features" below -│ └── templates/vault/ -│ └── index.html # Tags field, Auto-Lock setting, Import/Export view + sidebar -├── extension/ -│ ├── manifest.json # MV3 (Chrome/Edge); Ctrl+Shift+L keyboard shortcut -│ ├── manifest.firefox.json # MV2 (Firefox) -│ ├── background.js # Chrome SW: badges, pending-save "!" badge, CLEAR_SAVE_BADGE -│ ├── background.firefox.js # Firefox: in-memory session shim, setTimeout idle lock -│ ├── shared/ -│ │ ├── crypto.js # PBKDF2+AES-GCM; encryptName/decryptName; extractable key -│ │ └── browser-polyfill.js # chrome=browser alias for Firefox content scripts -│ ├── popup/ -│ │ ├── popup.html # Tabs: All relevant / All items / Favorites / Recents -│ │ ├── popup.css # .pk-group-* collapsible styles; .pk-tag badge; .pk-flyout menu -│ │ └── popup.js # Vault+folder fetch; collapsible groups; Favorites tab; -│ │ # tag display; clipboard auto-clear; CSPRNG generator; -│ │ # save badge clear; three-dot flyout; copy-username button -│ ├── content/ -│ │ └── content.js # _isLikelyUsernameField; _normaliseUrl; debounced input (150ms); -│ │ # MutationObserver guard; vault_items_cs from chrome.storage.session -│ ├── bridge/ -│ │ └── bridge.js # SSO bridge (vault domain only) -│ └── icons/ -├── migrations/versions/ -│ ├── 71d7158dd3b9_add_audit_log_tables_fix_sharing_public_.py -│ ├── a1b2c3d4e5f6_encrypt_totp_secret_at_rest.py -│ ├── b2c3d4e5f6a7_add_account_recovery_columns.py -│ └── c3d4e5f6a7b8_encrypt_vault_item_name.py # adds enc_name + iv_name -├── scripts/ -│ ├── reencrypt_totp_secrets.py -│ ├── backup_db.sh / backup.cron / passkeeper-logrotate -│ ├── passkeeper-nginx.conf / passkeeper.service -├── reset_db.py -├── requirements.txt -├── wsgi.py / run.py -└── CLAUDE.md -``` - ---- - -## Database Schema (MySQL) - -```sql --- Users -CREATE TABLE users ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - email VARCHAR(255) UNIQUE NOT NULL, - master_hash VARCHAR(255) NOT NULL, -- Argon2id(authHash) - enc_key_salt VARCHAR(64) NOT NULL, - created_at / last_login DATETIME, - totp_secret VARCHAR(255), -- AES-256-GCM ciphertext - totp_iv VARCHAR(64), - totp_enabled TINYINT(1) DEFAULT 0, - sharing_public_key VARCHAR(128), - sharing_private_key_enc TEXT, - sharing_private_key_iv VARCHAR(64), - recovery_enc_salt VARCHAR(128), - recovery_iv VARCHAR(64) -); - --- Vault Items -CREATE TABLE vault_items ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - user_id INT UNSIGNED NOT NULL, - folder_id INT UNSIGNED, - item_type VARCHAR(20) NOT NULL DEFAULT 'password', - name VARCHAR(255) NOT NULL, -- server label only (item type string) - enc_data TEXT NOT NULL, -- AES-256-GCM ciphertext of payload + tags - iv VARCHAR(64) NOT NULL, - enc_name TEXT, -- AES-256-GCM ciphertext of item name (nullable) - iv_name VARCHAR(64), - created_at DATETIME DEFAULT NOW(), - updated_at DATETIME DEFAULT NOW() ON UPDATE NOW(), - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (folder_id) REFERENCES folders(id) ON DELETE SET NULL -); --- (folders, token_blacklist, shared_items, emergency_access, audit_logs — unchanged) -``` - ---- - -## Migration History - -| Revision | Description | -| -------------- | ------------------------------------------- | -| `71d7158dd3b9` | Add audit_logs; fix sharing_public_key type | -| `a1b2c3d4e5f6` | Encrypt TOTP secret at rest | -| `b2c3d4e5f6a7` | Add account recovery columns | -| `c3d4e5f6a7b8` | Add enc_name + iv_name to vault_items | - ---- - -## Security Model - -- **Zero-knowledge:** master password never sent to server - - `authHash = PBKDF2(masterPassword, email, 100k iter)` → auth only - - `vaultKey = PBKDF2(masterPassword, enc_key_salt, 600k iter)` → browser memory only - - All vault data (payload + name + tags + advanced settings) encrypted client-side (AES-256-GCM) -- **Item name:** `enc_name`/`iv_name`; server `name` column = item type only -- **Tags:** `plain.tags: string[]` inside `enc_data`; server never sees them -- **Advanced Settings:** `plain.autofill`, `plain.autologin`, `plain.reprompt` — boolean fields inside `enc_data`; zero-knowledge like tags -- **Argon2id:** double-hashes `authHash` server-side -- **JWT:** HS256, 15 min access / 7 day refresh, JTI blacklisted on logout -- **MFA:** TOTP secret AES-256-GCM encrypted at rest -- **Sharing:** ECDH P-256 zero-knowledge re-encryption -- **Password generator:** fully CSPRNG (`_cryptoRandInt` rejection-sampling) -- **Decrypted vault data:** `chrome.storage.session` only — never to disk -- **Clipboard auto-clear:** 30 s after any password/username copy (web + extension) -- **Breach detection:** HIBP k-anonymity — only 5-char SHA-1 prefix transmitted - ---- - -## Extension Storage Architecture - -| Data | Storage | Reason | -| -------------------------------------------------------------------------------------- | ----------------------------------- | ------------------------------------- | -| `access_token`, `vault_key_jwk`, `vault_items`, `vault_items_cs` | `chrome.storage.session` | Memory-only, cleared on browser close | -| `refresh_token`, `enc_key_salt`, `pending_save`, `save_blocklist`, `idle_lock_seconds` | `chrome.storage.local` | Persists across restarts | -| Web-app session timeout | `localStorage` (`web_idle_minutes`) | Per-browser preference | - -### Critical: `vault_items_cs` is in `session`, NOT `local` - -Requires Chrome 111+. `content.js` `onChanged` listener watches `area === 'session'`. Do not revert — reverting persists decrypted passwords to disk. - -### Pending-save badge - -`background.js` sets a red `"!"` badge on `SAVE_CREDENTIALS`. Cleared via `CLEAR_SAVE_BADGE` when user acts on the save prompt in the popup. - ---- - -## Key Implementation Details - -### Vault item tags - -- Stored as `plain.tags: string[]` inside `enc_data`. No schema change ever needed. -- **Web app:** `_parseTags(str)` → comma-split, lowercase, dedup, sort. `_allTags()` collects across all items. `renderTagList()` builds sidebar. Tags field in modal has live badge preview. -- **Extension:** `.pk-tag` badges below item subtitle. Favorites tab = items where `tags.includes('favorite')`. Favorited items show gold ★ in site label. -- To favourite an item: add tag `favorite` in the web app edit modal. - -### Collapsible folder groups - -- **Web app:** `_collapsedGroups` Set persists state across re-renders. Header shows name + count badge + chevron (▼/▶). Click toggles and re-renders. -- **Extension:** only on "All items" tab. `_folders` fetched in parallel with vault. `_collapsedFolders` Set. `folderName(id)` resolves to display name with `(none)` fallback. - -### Import / Export - -- **Encrypted JSON export:** `GET /api/vault` → versioned envelope → download. Zero-knowledge. -- **CSV export:** decrypt client-side → `name, url, username, password, notes`. -- **JSON import:** sends encrypted blobs to `POST /api/vault/import` directly. -- **CSV import:** parses Chrome / Bitwarden / 1Password formats; encrypts client-side before POSTing. -- Both endpoints write audit logs: `vault_item.export`, `vault_item.import`. - -### Web-app session timeout - -- `_startWebIdleTracking()` called at end of `init()`. Listens to `mousemove`, `mousedown`, `keydown`, `touchstart`, `scroll`. -- On timeout: `VaultSession.clear()` → unlock overlay → toast. -- Stored in `localStorage` as `web_idle_minutes`. Default 15 min. Options: Never/5/10/15/30/60. -- Exposed in Account Settings → Auto-Lock (`