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/upload/', views.upload_background, name='upload_background'),
|
||||||
path('background/delete/', views.delete_background, name='delete_background'),
|
path('background/delete/', views.delete_background, name='delete_background'),
|
||||||
path('focus-station/', views.save_focus_station, name='save_focus_station'),
|
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'),
|
path('change-password/', views.change_password, name='change_password'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,19 @@ def save_focus_station(request):
|
||||||
return JsonResponse({'ok': True})
|
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
|
@login_required
|
||||||
@require_http_methods(['POST'])
|
@require_http_methods(['POST'])
|
||||||
def change_password(request):
|
def change_password(request):
|
||||||
|
|
|
||||||
|
|
@ -175,26 +175,31 @@ document.getElementById('pw-change-form')?.addEventListener('submit', async func
|
||||||
const username = '{{ request.user.username }}';
|
const username = '{{ request.user.username }}';
|
||||||
const storageKey = `diora_enc_key_${userId}`;
|
const storageKey = `diora_enc_key_${userId}`;
|
||||||
|
|
||||||
statusEl.textContent = 'Schlüssel ableiten…';
|
statusEl.textContent = 'Passwort prüfen…';
|
||||||
try {
|
try {
|
||||||
const oldKey = await _pbkdf2Key(oldPw, username);
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
// Verify old key matches localStorage
|
// Use the key currently in localStorage as the old key (source of truth for decryption)
|
||||||
const stored = localStorage.getItem(storageKey);
|
const stored = localStorage.getItem(storageKey);
|
||||||
|
let oldKey;
|
||||||
if (stored) {
|
if (stored) {
|
||||||
const storedBytes = _base64ToBytes(stored);
|
oldKey = await crypto.subtle.importKey('raw', _base64ToBytes(stored), {name: 'AES-GCM'}, false, ['decrypt']);
|
||||||
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.';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newKey = await _pbkdf2Key(newPw, username);
|
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…';
|
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++) {
|
for (let i = 0; i < books.length; i++) {
|
||||||
const book = books[i];
|
const book = books[i];
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue