Accounts: fix password change key migration
The old key comparison was wrong — the localStorage key may have been randomly generated rather than PBKDF2-derived. Fix: - Add /accounts/check-password/ to validate the old password server-side before touching any keys - Use the localStorage key directly as the old decryption key (it is always the correct source of truth, regardless of how it was generated) - Derive the new key from the new password via PBKDF2 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1426c69a73
commit
817323ad19
3 changed files with 30 additions and 11 deletions
|
|
@ -13,5 +13,6 @@ urlpatterns = [
|
|||
path('background/upload/', views.upload_background, name='upload_background'),
|
||||
path('background/delete/', views.delete_background, name='delete_background'),
|
||||
path('focus-station/', views.save_focus_station, name='save_focus_station'),
|
||||
path('check-password/', views.check_password, name='check_password'),
|
||||
path('change-password/', views.change_password, name='change_password'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -182,6 +182,19 @@ def save_focus_station(request):
|
|||
return JsonResponse({'ok': True})
|
||||
|
||||
|
||||
@login_required
|
||||
@csrf_exempt
|
||||
@require_http_methods(['POST'])
|
||||
def check_password(request):
|
||||
try:
|
||||
body = json.loads(request.body)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
return JsonResponse({'ok': False}, status=400)
|
||||
pw = body.get('password', '')
|
||||
user = authenticate(request, username=request.user.username, password=pw)
|
||||
return JsonResponse({'ok': user is not None})
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['POST'])
|
||||
def change_password(request):
|
||||
|
|
|
|||
|
|
@ -175,26 +175,31 @@ document.getElementById('pw-change-form')?.addEventListener('submit', async func
|
|||
const username = '{{ request.user.username }}';
|
||||
const storageKey = `diora_enc_key_${userId}`;
|
||||
|
||||
statusEl.textContent = 'Schlüssel ableiten…';
|
||||
statusEl.textContent = 'Passwort prüfen…';
|
||||
try {
|
||||
const oldKey = await _pbkdf2Key(oldPw, username);
|
||||
|
||||
// Verify old key matches localStorage
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
if (stored) {
|
||||
const storedBytes = _base64ToBytes(stored);
|
||||
const derivedBytes = new Uint8Array(await crypto.subtle.exportKey('raw', oldKey));
|
||||
if (!storedBytes.every((b, i) => b === derivedBytes[i])) {
|
||||
statusEl.textContent = 'Fehler: Das alte Passwort stimmt nicht mit dem gespeicherten Schlüssel überein. Bitte altes Passwort prüfen.';
|
||||
// Validate old password server-side before touching any keys
|
||||
const checkResp = await fetch('/accounts/check-password/', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken()},
|
||||
body: JSON.stringify({password: oldPw}),
|
||||
}).then(r => r.json());
|
||||
if (!checkResp.ok) {
|
||||
statusEl.textContent = 'Fehler: Altes Passwort ist falsch.';
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the key currently in localStorage as the old key (source of truth for decryption)
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
let oldKey;
|
||||
if (stored) {
|
||||
oldKey = await crypto.subtle.importKey('raw', _base64ToBytes(stored), {name: 'AES-GCM'}, false, ['decrypt']);
|
||||
}
|
||||
|
||||
const newKey = await _pbkdf2Key(newPw, username);
|
||||
|
||||
// Re-encrypt all books
|
||||
// Re-encrypt all books (only if we have a key to decrypt with)
|
||||
statusEl.textContent = 'Bücherliste laden…';
|
||||
const books = await fetch('/books/').then(r => r.json());
|
||||
const books = oldKey ? await fetch('/books/').then(r => r.json()) : [];
|
||||
|
||||
for (let i = 0; i < books.length; i++) {
|
||||
const book = books[i];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue