Fix Books tab: auto-generate encryption key, remove password prompt
All checks were successful
Build and push Docker image / build (push) Successful in 11s
Test / test (push) Successful in 16s

- getOrCreateEncKey() now generates a random AES-GCM-256 key if none
  found in localStorage, instead of throwing an error
- Removed enc-key-prompt div from player.html entirely
- Simplified initBookDropZone() — removed prompt show/hide logic
- book-upload-area always visible, no longer hidden behind prompt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
marwin 2026-03-19 22:16:22 +01:00
parent 500b3fa780
commit 2fad4a726c
2 changed files with 9 additions and 21 deletions

View file

@ -1813,16 +1813,20 @@ function hexToBytes(hex) {
async function getOrCreateEncKey() { async function getOrCreateEncKey() {
if (_encKey) return _encKey; if (_encKey) return _encKey;
const storageKey = `diora_enc_key_${window.USER_ID || 'anon'}`; const storageKey = `diora_enc_key_${window.USER_ID || 0}`;
const stored = localStorage.getItem(storageKey); const stored = localStorage.getItem(storageKey);
if (stored) { if (stored) {
try { try {
const raw = base64ToBytes(stored); const raw = base64ToBytes(stored);
_encKey = await crypto.subtle.importKey('raw', raw, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt']); _encKey = await crypto.subtle.importKey('raw', raw, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt']);
return _encKey; return _encKey;
} catch (e) { /* fall through */ } } catch (e) { /* fall through, generate new */ }
} }
throw new Error('Encryption key not found — please log out and log in again.'); // No key found — generate one and store it
_encKey = await crypto.subtle.generateKey({name: 'AES-GCM', length: 256}, true, ['encrypt', 'decrypt']);
const raw = await crypto.subtle.exportKey('raw', _encKey);
localStorage.setItem(storageKey, bytesToBase64(new Uint8Array(raw)));
return _encKey;
} }
async function encryptBytes(key, plainBytes) { async function encryptBytes(key, plainBytes) {
@ -2205,14 +2209,6 @@ function initBookDropZone() {
if (!zone || !zone.contains(e.target)) e.preventDefault(); if (!zone || !zone.contains(e.target)) e.preventDefault();
}); });
const storageKey = `diora_enc_key_${window.USER_ID || 'anon'}`;
const hasKey = !!localStorage.getItem(storageKey);
const prompt = $('enc-key-prompt');
const uploadArea = $('book-upload-area');
if (prompt) prompt.style.display = hasKey ? 'none' : '';
if (uploadArea) uploadArea.style.display = hasKey ? '' : 'none';
const zone = $('book-drop-zone'); const zone = $('book-drop-zone');
if (!zone) return; if (!zone) return;
@ -2245,7 +2241,7 @@ async function deriveAndStoreKey() {
mat, {name: 'AES-GCM', length: 256}, true, ['encrypt', 'decrypt'] mat, {name: 'AES-GCM', length: 256}, true, ['encrypt', 'decrypt']
); );
const raw = await crypto.subtle.exportKey('raw', key); const raw = await crypto.subtle.exportKey('raw', key);
const storageKey = `diora_enc_key_${window.USER_ID || 'anon'}`; const storageKey = `diora_enc_key_${window.USER_ID || 0}`;
localStorage.setItem(storageKey, bytesToBase64(new Uint8Array(raw))); localStorage.setItem(storageKey, bytesToBase64(new Uint8Array(raw)));
_encKey = null; // reset cached key _encKey = null; // reset cached key
if (statusEl) statusEl.textContent = '✓ Unlocked'; if (statusEl) statusEl.textContent = '✓ Unlocked';

View file

@ -278,15 +278,7 @@
<!-- ===== BOOKS TAB ===== --> <!-- ===== BOOKS TAB ===== -->
<section class="tab-panel" id="tab-books" style="display:none;"> <section class="tab-panel" id="tab-books" style="display:none;">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<div id="enc-key-prompt" class="enc-key-prompt"> <div id="book-upload-area">
<p class="muted">Enter your password to unlock encrypted storage on this device.</p>
<div class="search-bar">
<input type="password" id="enc-key-password" class="search-input" placeholder="Your password…">
<button class="btn" onclick="deriveAndStoreKey()">Unlock</button>
</div>
<span id="enc-key-status" class="muted"></span>
</div>
<div id="book-upload-area" style="display:none;">
<div class="book-drop-zone" id="book-drop-zone"> <div class="book-drop-zone" id="book-drop-zone">
<span>Drop .epub or .pdf here or <label for="book-file-input" style="cursor:pointer;text-decoration:underline;">browse</label></span> <span>Drop .epub or .pdf here or <label for="book-file-input" style="cursor:pointer;text-decoration:underline;">browse</label></span>
<input type="file" id="book-file-input" accept=".epub,.pdf" style="display:none;" onchange="bookFileSelected(this)"> <input type="file" id="book-file-input" accept=".epub,.pdf" style="display:none;" onchange="bookFileSelected(this)">