04/16 Upload codebase

This commit is contained in:
2026-04-16 08:27:07 -04:00
parent 91be5c1f5c
commit 457016b8d4
43 changed files with 6241 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
{% extends "base.html" %}
{% block title %}Sign In — PassKeeper{% endblock %}
{% block body_class %}auth-page{% endblock %}
{% block body %}
<div class="auth-container">
<div class="auth-card">
<div class="auth-logo">
<span class="logo-icon">🔒</span>
<span class="logo-text">PassKeeper</span>
</div>
<p id="register-notice" class="notice-success hidden">
Account created! Please sign in.
</p>
<!-- Step 1: Email + Master Password -->
<div id="login-step-1">
<h1 class="auth-title">Sign in</h1>
<form id="login-form" novalidate>
<div class="form-group">
<label for="email">Email address</label>
<input type="email" id="email" name="email" required
autocomplete="email" placeholder="you@example.com">
</div>
<div class="form-group">
<label for="password">Master password</label>
<div class="input-with-toggle">
<input type="password" id="password" name="password" required
autocomplete="current-password" placeholder="Enter master password">
<button type="button" class="btn-show-pass" id="toggle-login-pass"
aria-label="Toggle password visibility">👁</button>
</div>
</div>
<button type="submit" class="btn-primary btn-full" data-loading-text="Verifying…">
Sign in
</button>
</form>
<p class="auth-footer">
Don't have an account? <a href="/register">Create one</a>
</p>
</div>
<!-- Step 2: TOTP verification (hidden until MFA required) -->
<div id="login-step-2" class="hidden">
<h1 class="auth-title">Two-factor authentication</h1>
<p class="auth-step-hint">Enter the 6-digit code from your authenticator app.</p>
<form id="mfa-form" novalidate>
<p id="mfa-error" class="form-error hidden">Invalid or expired code. Try again.</p>
<div class="form-group">
<label for="mfa-code">Authenticator code</label>
<input type="text" id="mfa-code" name="mfa_code"
inputmode="numeric" pattern="[0-9]{6}" maxlength="6"
autocomplete="one-time-code" placeholder="000000">
</div>
<button type="submit" class="btn-primary btn-full" data-loading-text="Verifying…">
Verify
</button>
</form>
<div class="auth-footer">
<button class="btn-text" id="btn-back-to-password">← Back to sign in</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/crypto.js') }}"></script>
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
{% endblock %}
+55
View File
@@ -0,0 +1,55 @@
{% extends "base.html" %}
{% block title %}Create Account — PassKeeper{% endblock %}
{% block body_class %}auth-page{% endblock %}
{% block body %}
<div class="auth-container">
<div class="auth-card">
<div class="auth-logo">
<span class="logo-icon">🔒</span>
<span class="logo-text">PassKeeper</span>
</div>
<h1 class="auth-title">Create account</h1>
<div class="notice-info">
<strong>Important:</strong> Your master password encrypts all your data.
It is <em>never sent to our servers</em>. If you forget it, your vault cannot be recovered.
</div>
<form id="register-form" novalidate>
<div class="form-group">
<label for="email">Email address</label>
<input type="email" id="email" name="email" required
autocomplete="email" placeholder="you@example.com">
</div>
<div class="form-group">
<label for="password">Master password <span class="hint">(min. 12 characters)</span></label>
<div class="input-with-toggle">
<input type="password" id="password" name="password" required
autocomplete="new-password" minlength="12" placeholder="Create a strong master password">
<button type="button" class="btn-show-pass" id="toggle-reg-pass" aria-label="Toggle password visibility">👁</button>
</div>
<div class="password-strength" id="strength-bar"></div>
</div>
<div class="form-group">
<label for="confirm_password">Confirm master password</label>
<input type="password" id="confirm_password" name="confirm_password" required
autocomplete="new-password" placeholder="Repeat master password">
</div>
<button type="submit" class="btn-primary btn-full" data-loading-text="Creating account…">
Create account
</button>
</form>
<p class="auth-footer">
Already have an account? <a href="/login">Sign in</a>
</p>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/crypto.js') }}"></script>
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
{% endblock %}
+20
View File
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self';">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{% block title %}PassKeeper{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">
{% block head_extra %}{% endblock %}
</head>
<body class="{% block body_class %}{% endblock %}">
{% block body %}{% endblock %}
<div id="toast" class="toast" aria-live="polite"></div>
{% block scripts %}{% endblock %}
</body>
</html>
+419
View File
@@ -0,0 +1,419 @@
{% extends "base.html" %}
{% block title %}My Vault — PassKeeper{% endblock %}
{% block body_class %}vault-page{% endblock %}
{% block body %}
<div class="app-layout">
<!-- ── Sidebar ─────────────────────────────────────────────────── -->
<aside class="sidebar">
<div class="sidebar-header">
<span class="logo-icon">🔒</span>
<span class="logo-text">PassKeeper</span>
</div>
<nav class="sidebar-nav">
<ul>
<li class="sidebar-item active" id="sidebar-all" data-view="vault">
<span class="sidebar-icon">🏠</span> <span class="sidebar-label">All Items</span>
</li>
<li class="sidebar-section-header">Item Types</li>
<li class="sidebar-item" data-type-filter="password">
<span class="sidebar-icon">🔑</span> <span class="sidebar-label">Passwords</span>
</li>
<li class="sidebar-item" data-type-filter="note">
<span class="sidebar-icon">📝</span> <span class="sidebar-label">Secure Notes</span>
</li>
<li class="sidebar-item" data-type-filter="card">
<span class="sidebar-icon">💳</span> <span class="sidebar-label">Payment Cards</span>
</li>
<li class="sidebar-item" data-type-filter="bank">
<span class="sidebar-icon">🏦</span> <span class="sidebar-label">Bank Accounts</span>
</li>
<li class="sidebar-item" data-type-filter="address">
<span class="sidebar-icon">🏠</span> <span class="sidebar-label">Addresses</span>
</li>
<li class="sidebar-item" data-type-filter="ssn">
<span class="sidebar-icon">🪪</span> <span class="sidebar-label">Identities</span>
</li>
<li class="sidebar-section-header">Tools</li>
<li class="sidebar-item" id="sidebar-security" data-view="security">
<span class="sidebar-icon">🛡️</span> <span class="sidebar-label">Security</span>
</li>
<li class="sidebar-item" id="sidebar-sharing" data-view="sharing">
<span class="sidebar-icon">🔗</span> <span class="sidebar-label">Sharing</span>
</li>
<li class="sidebar-item" id="sidebar-emergency" data-view="emergency">
<span class="sidebar-icon">🚨</span> <span class="sidebar-label">Emergency Access</span>
</li>
<li class="sidebar-section-header">
Folders
<button class="btn-new-folder" id="btn-new-folder" title="New folder">+</button>
</li>
<li id="new-folder-row" class="new-folder-row hidden">
<form id="new-folder-form">
<input type="text" id="new-folder-input" placeholder="Folder name" maxlength="128">
<button type="submit" class="btn-icon" title="Save"></button>
<button type="button" class="btn-icon" id="btn-cancel-folder" title="Cancel"></button>
</form>
</li>
<ul id="sidebar-folders"></ul>
</ul>
</nav>
<div class="sidebar-footer">
<button class="sidebar-item btn-text" id="btn-settings">
<span class="sidebar-icon">⚙️</span> <span class="sidebar-label">Settings</span>
</button>
<button class="sidebar-item btn-text" id="btn-logout">
<span class="sidebar-icon">🚪</span> <span class="sidebar-label">Sign out</span>
</button>
</div>
</aside>
<!-- ── Main ───────────────────────────────────────────────────── -->
<main class="vault-main">
<!-- Vault view -->
<div id="view-vault">
<header class="vault-header">
<h2 class="vault-title" id="vault-title">All Items</h2>
<div class="vault-toolbar">
<div class="search-wrapper">
<span class="search-icon">🔍</span>
<input type="search" id="search-input" placeholder="Search vault…" autocomplete="off">
</div>
<div class="sort-wrapper">
<select id="sort-select" title="Sort by">
<option value="name-asc">Name AZ</option>
<option value="name-desc">Name ZA</option>
<option value="date-desc">Newest first</option>
<option value="date-asc">Oldest first</option>
<option value="folder">By folder</option>
</select>
</div>
<button class="btn-fab" id="btn-add-item" title="Add item">+</button>
</div>
</header>
<div id="vault-spinner" class="spinner-overlay hidden"><div class="spinner"></div></div>
<ul id="vault-list" class="vault-list"></ul>
</div>
<!-- Security dashboard view -->
<div id="view-security" class="hidden">
<header class="vault-header"><h2 class="vault-title">Security Dashboard</h2></header>
<div class="security-dashboard">
<div class="security-summary" id="security-summary"></div>
<div class="security-sections" id="security-sections"></div>
</div>
</div>
<!-- Sharing view -->
<div id="view-sharing" class="hidden">
<header class="vault-header">
<h2 class="vault-title">Sharing</h2>
<div class="vault-toolbar">
<button class="btn-primary btn-sm" id="btn-share-item">+ Share an item</button>
</div>
</header>
<div class="panel-body">
<div id="sharing-keys-notice" class="notice-info hidden">
You need to set up sharing keys before you can share items.
<button class="btn-primary btn-sm" id="btn-setup-keys-from-sharing">Set up keys</button>
</div>
<div class="panel-tabs">
<button class="tab-btn active" data-tab="shared-out">Shared by me</button>
<button class="tab-btn" data-tab="shared-in">Shared with me</button>
</div>
<div id="tab-shared-out" class="tab-pane">
<ul id="shared-out-list" class="share-list"></ul>
</div>
<div id="tab-shared-in" class="tab-pane hidden">
<ul id="shared-in-list" class="share-list"></ul>
</div>
</div>
</div>
<!-- Emergency Access view -->
<div id="view-emergency" class="hidden">
<header class="vault-header">
<h2 class="vault-title">Emergency Access</h2>
<div class="vault-toolbar">
<button class="btn-primary btn-sm" id="btn-add-emergency">+ Add trusted contact</button>
</div>
</header>
<div class="panel-body">
<div class="panel-tabs">
<button class="tab-btn active" data-tab="em-grants">My trusted contacts</button>
<button class="tab-btn" data-tab="em-access">I'm a trusted contact</button>
</div>
<div id="tab-em-grants" class="tab-pane">
<ul id="em-grants-list" class="share-list"></ul>
</div>
<div id="tab-em-access" class="tab-pane hidden">
<ul id="em-access-list" class="share-list"></ul>
<div id="em-vault-panel" class="em-vault-panel hidden">
<div class="em-vault-header">
<h4>Emergency Vault — Decrypted Items</h4>
<button class="btn-secondary btn-sm" id="btn-em-vault-back">← Back</button>
</div>
<ul id="em-vault-list" class="em-vault-list"></ul>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- ── Unlock overlay ──────────────────────────────────────────────── -->
<div id="unlock-overlay" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="unlock-title">
<div class="modal modal-unlock">
<div class="auth-logo unlock-logo"><span class="logo-icon">🔒</span><span class="logo-text">PassKeeper</span></div>
<h3 id="unlock-title" class="unlock-title">Unlock your vault</h3>
<p class="unlock-subtitle">Re-enter your master password to continue.</p>
<p id="unlock-error" class="form-error hidden">Incorrect master password.</p>
<form id="unlock-form" novalidate>
<div class="form-group">
<label for="unlock-password">Master password</label>
<input type="password" id="unlock-password" autocomplete="current-password" placeholder="Enter master password">
</div>
<div class="modal-actions">
<a href="/login" class="btn-secondary btn-link">Sign out</a>
<button type="submit" class="btn-primary">Unlock</button>
</div>
</form>
</div>
</div>
<!-- ── Add / Edit Item Modal ─────────────────────────────────────────── -->
<div id="item-modal" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="modal-title">
<div class="modal modal-item">
<div class="modal-header">
<h3 id="modal-title">Add Item</h3>
<button class="btn-icon btn-close" id="btn-close-modal" aria-label="Close"></button>
</div>
<form id="item-form" novalidate>
<div class="form-group" id="type-selector-group">
<label for="field-type">Item Type</label>
<select id="field-type" name="item_type">
<option value="password">🔑 Password</option>
<option value="note">📝 Secure Note</option>
<option value="card">💳 Payment Card</option>
<option value="bank">🏦 Bank Account</option>
<option value="address">🏠 Address</option>
<option value="ssn">🪪 Identity (SSN)</option>
</select>
</div>
<div class="form-group">
<label for="field-name">Name <span class="required">*</span></label>
<input type="text" id="field-name" name="name" required placeholder="e.g. Gmail">
</div>
<div class="type-fields" data-for-types="password">
<div class="form-group"><label for="field-url">Website URL</label><input type="url" id="field-url" placeholder="https://example.com"></div>
<div class="form-group"><label for="field-username">Username / Email</label><input type="text" id="field-username" autocomplete="off" placeholder="username or email"></div>
<div class="form-group">
<label for="field-password">Password</label>
<div class="input-with-toggle">
<input type="password" id="field-password" autocomplete="new-password" placeholder="Enter password">
<button type="button" class="btn-show-pass" id="btn-toggle-pass" aria-label="Toggle">👁</button>
</div>
</div>
</div>
<div class="type-fields hidden" data-for-types="note">
<div class="form-group"><label for="field-note-body">Note</label><textarea id="field-note-body" rows="6" placeholder="Enter your secure note…"></textarea></div>
</div>
<div class="type-fields hidden" data-for-types="card">
<div class="form-group"><label for="field-cardholder">Cardholder Name</label><input type="text" id="field-cardholder" placeholder="Name on card"></div>
<div class="form-group"><label for="field-card-number">Card Number</label><input type="text" id="field-card-number" autocomplete="off" placeholder="•••• •••• •••• ••••" maxlength="19"></div>
<div class="form-row">
<div class="form-group"><label for="field-expiry-month">Expiry Month</label>
<select id="field-expiry-month"><option value="">MM</option>
<option value="01">01</option><option value="02">02</option><option value="03">03</option><option value="04">04</option>
<option value="05">05</option><option value="06">06</option><option value="07">07</option><option value="08">08</option>
<option value="09">09</option><option value="10">10</option><option value="11">11</option><option value="12">12</option>
</select>
</div>
<div class="form-group"><label for="field-expiry-year">Year</label><input type="text" id="field-expiry-year" placeholder="YYYY" maxlength="4"></div>
<div class="form-group"><label for="field-cvv">CVV</label><input type="text" id="field-cvv" autocomplete="off" placeholder="•••" maxlength="4"></div>
</div>
</div>
<div class="type-fields hidden" data-for-types="bank">
<div class="form-group"><label for="field-bank-name">Bank Name</label><input type="text" id="field-bank-name" placeholder="e.g. Chase"></div>
<div class="form-group"><label for="field-account-type">Account Type</label>
<select id="field-account-type"><option value="">— Select —</option><option value="checking">Checking</option><option value="savings">Savings</option><option value="money_market">Money Market</option><option value="other">Other</option></select>
</div>
<div class="form-group"><label for="field-routing">Routing Number</label><input type="text" id="field-routing" autocomplete="off" placeholder="9-digit routing number" maxlength="9"></div>
<div class="form-group"><label for="field-account-number">Account Number</label><input type="text" id="field-account-number" autocomplete="off" placeholder="Account number"></div>
</div>
<div class="type-fields hidden" data-for-types="address">
<div class="form-row">
<div class="form-group"><label for="field-first-name">First Name</label><input type="text" id="field-first-name" placeholder="First name"></div>
<div class="form-group"><label for="field-last-name">Last Name</label><input type="text" id="field-last-name" placeholder="Last name"></div>
</div>
<div class="form-group"><label for="field-company">Company</label><input type="text" id="field-company" placeholder="Company (optional)"></div>
<div class="form-group"><label for="field-address-line">Street Address</label><input type="text" id="field-address-line" placeholder="123 Main St"></div>
<div class="form-row">
<div class="form-group"><label for="field-city">City</label><input type="text" id="field-city" placeholder="City"></div>
<div class="form-group"><label for="field-state">State</label><input type="text" id="field-state" placeholder="State"></div>
</div>
<div class="form-row">
<div class="form-group"><label for="field-zip">ZIP</label><input type="text" id="field-zip" placeholder="ZIP"></div>
<div class="form-group"><label for="field-country">Country</label><input type="text" id="field-country" placeholder="Country"></div>
</div>
<div class="form-row">
<div class="form-group"><label for="field-phone">Phone</label><input type="tel" id="field-phone" placeholder="+1 555-000-0000"></div>
<div class="form-group"><label for="field-email">Email</label><input type="email" id="field-email" placeholder="email@example.com"></div>
</div>
</div>
<div class="type-fields hidden" data-for-types="ssn">
<div class="form-group"><label for="field-ssn-number">Social Security Number</label>
<div class="input-with-toggle">
<input type="password" id="field-ssn-number" autocomplete="off" placeholder="•••-••-••••" maxlength="11">
<button type="button" class="btn-show-pass" id="btn-toggle-ssn" aria-label="Toggle">👁</button>
</div>
</div>
</div>
<div class="form-group">
<label for="field-folder">Folder</label>
<select id="field-folder"><option value="">— No folder —</option></select>
</div>
<div class="type-fields" data-for-types="password card bank address ssn">
<div class="form-group"><label for="field-notes">Notes</label><textarea id="field-notes" rows="3" placeholder="Optional notes…"></textarea></div>
</div>
<div class="modal-actions">
<button type="button" class="btn-secondary" id="btn-cancel-modal">Cancel</button>
<button type="submit" class="btn-primary">Save</button>
</div>
</form>
</div>
</div>
<!-- ── Account Settings Modal ────────────────────────────────────────── -->
<div id="settings-modal" class="modal-overlay" role="dialog" aria-modal="true">
<div class="modal modal-settings">
<div class="modal-header">
<h3>Account Settings</h3>
<button class="btn-icon btn-close" id="btn-close-settings" aria-label="Close"></button>
</div>
<!-- MFA Section -->
<div class="settings-section">
<h4 class="settings-section-title">🔐 Two-Factor Authentication</h4>
<div id="mfa-status-area">
<p id="mfa-status-text" class="settings-desc"></p>
<div id="mfa-setup-area" class="hidden">
<img id="mfa-qr-img" src="" alt="QR Code" class="mfa-qr">
<p class="settings-desc">Scan with Google Authenticator or Authy, then enter the 6-digit code below.</p>
<p class="settings-desc mfa-secret-text">Manual key: <code id="mfa-secret-display"></code></p>
<div class="form-group">
<label for="mfa-verify-code">Verification code</label>
<input type="text" id="mfa-verify-code" inputmode="numeric" maxlength="6" placeholder="000000">
</div>
<button class="btn-primary" id="btn-mfa-confirm">Enable MFA</button>
<button class="btn-secondary" id="btn-mfa-cancel-setup">Cancel</button>
</div>
<div id="mfa-disable-area" class="hidden">
<div class="form-group">
<label for="mfa-disable-code">Enter your current code to disable MFA</label>
<input type="text" id="mfa-disable-code" inputmode="numeric" maxlength="6" placeholder="000000">
</div>
<button class="btn-primary" id="btn-mfa-disable-confirm">Disable MFA</button>
<button class="btn-secondary" id="btn-mfa-disable-cancel">Cancel</button>
</div>
<div id="mfa-actions"></div>
</div>
</div>
<!-- Sharing Keys Section -->
<div class="settings-section">
<h4 class="settings-section-title">🔗 Sharing &amp; Emergency Access Keys</h4>
<p id="sharing-keys-status" class="settings-desc"></p>
<div id="sharing-keys-actions"></div>
</div>
</div>
</div>
<!-- ── Share Item Modal ───────────────────────────────────────────────── -->
<div id="share-modal" class="modal-overlay" role="dialog" aria-modal="true">
<div class="modal">
<div class="modal-header">
<h3>Share an Item</h3>
<button class="btn-icon btn-close" id="btn-close-share-modal" aria-label="Close"></button>
</div>
<form id="share-form" novalidate>
<p id="share-error" class="form-error hidden"></p>
<div class="form-group">
<label for="share-item-select">Item to share</label>
<select id="share-item-select"></select>
</div>
<div class="form-group">
<label for="share-recipient-email">Recipient's email</label>
<input type="email" id="share-recipient-email" placeholder="recipient@example.com">
</div>
<div class="modal-actions">
<button type="button" class="btn-secondary" id="btn-cancel-share">Cancel</button>
<button type="submit" class="btn-primary">Share</button>
</div>
</form>
</div>
</div>
<!-- ── Emergency Access Setup Modal ──────────────────────────────────── -->
<div id="emergency-modal" class="modal-overlay" role="dialog" aria-modal="true">
<div class="modal">
<div class="modal-header">
<h3>Add Emergency Contact</h3>
<button class="btn-icon btn-close" id="btn-close-emergency-modal" aria-label="Close"></button>
</div>
<form id="emergency-form" novalidate>
<p id="emergency-error" class="form-error hidden"></p>
<p class="settings-desc">
Your trusted contact will be able to request access to your vault after a waiting period
if you are unable to access your account.
</p>
<div class="form-group">
<label for="emergency-email">Trusted contact's email</label>
<input type="email" id="emergency-email" placeholder="contact@example.com">
</div>
<div class="form-group">
<label for="emergency-wait-days">Wait period (days)</label>
<select id="emergency-wait-days">
<option value="1">1 day</option>
<option value="3">3 days</option>
<option value="7" selected>7 days</option>
<option value="14">14 days</option>
<option value="30">30 days</option>
</select>
</div>
<div class="modal-actions">
<button type="button" class="btn-secondary" id="btn-cancel-emergency">Cancel</button>
<button type="submit" class="btn-primary">Send Invitation</button>
</div>
</form>
</div>
</div>
<!-- ── Item Detail Modal (read-only: shared items) ────────────────────── -->
<div id="detail-modal" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="detail-modal-title">
<div class="modal modal-detail">
<div class="modal-header">
<h3 id="detail-modal-title">Item Details</h3>
<button class="btn-icon btn-close" id="btn-close-detail-modal" aria-label="Close"></button>
</div>
<div id="detail-modal-body" class="detail-body"></div>
<div class="modal-actions">
<button type="button" class="btn-secondary" id="btn-detail-done">Close</button>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/crypto.js') }}"></script>
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
<script src="{{ url_for('static', filename='js/sharing.js') }}"></script>
<script src="{{ url_for('static', filename='js/vault.js') }}"></script>
{% endblock %}