Merge pull request #871 from Nezreka/dev
#870: Deezer ARL 'resets itself' — test the SAVED token, not the reda…
This commit is contained in:
commit
2953c2c5d2
4 changed files with 66 additions and 8 deletions
|
|
@ -876,6 +876,20 @@ class ConfigManager:
|
|||
config[keys[-1]] = value
|
||||
self._save_config()
|
||||
|
||||
def resolve_secret(self, key: str, posted: Any) -> str:
|
||||
"""Resolve a secret value coming back from the settings UI.
|
||||
|
||||
The UI renders a saved-but-untouched secret as the REDACTED_SENTINEL (shown
|
||||
masked); empty or that sentinel means "use the stored value", while a real
|
||||
string is a genuine new secret. A connection-test endpoint should test the
|
||||
EFFECTIVE secret, not the mask — otherwise testing a saved-but-untouched
|
||||
token sends the sentinel and the source rejects it (#870)."""
|
||||
if isinstance(posted, str):
|
||||
posted = posted.strip()
|
||||
if not posted or posted == self.REDACTED_SENTINEL:
|
||||
return self.get(key, '') or ''
|
||||
return posted
|
||||
|
||||
def get_spotify_config(self) -> Dict[str, str]:
|
||||
return self.get('spotify', {})
|
||||
|
||||
|
|
|
|||
43
tests/test_resolve_secret.py
Normal file
43
tests/test_resolve_secret.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"""ConfigManager.resolve_secret — test the EFFECTIVE secret, not the mask (#870).
|
||||
|
||||
The settings UI renders a saved-but-untouched secret as the redaction sentinel
|
||||
(shown as dots). The Deezer ARL connection test was sending that sentinel as the
|
||||
token, so Deezer rejected it ("Invalid ARL token — USER_ID=0") and it looked like
|
||||
the ARL kept resetting — even though the saved value was fine. resolve_secret maps
|
||||
an empty/sentinel posted value back to the stored token.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from config.settings import ConfigManager
|
||||
|
||||
S = ConfigManager.REDACTED_SENTINEL
|
||||
|
||||
|
||||
def _cm(config_data):
|
||||
cm = object.__new__(ConfigManager) # bypass __init__/DB — get() reads config_data
|
||||
cm.config_data = config_data
|
||||
return cm
|
||||
|
||||
|
||||
def test_sentinel_resolves_to_stored():
|
||||
cm = _cm({'deezer_download': {'arl': 'real_arl_123'}})
|
||||
assert cm.resolve_secret('deezer_download.arl', S) == 'real_arl_123'
|
||||
|
||||
|
||||
def test_empty_or_none_resolves_to_stored():
|
||||
cm = _cm({'deezer_download': {'arl': 'real_arl_123'}})
|
||||
assert cm.resolve_secret('deezer_download.arl', '') == 'real_arl_123'
|
||||
assert cm.resolve_secret('deezer_download.arl', ' ') == 'real_arl_123'
|
||||
assert cm.resolve_secret('deezer_download.arl', None) == 'real_arl_123'
|
||||
|
||||
|
||||
def test_real_value_passes_through_trimmed():
|
||||
cm = _cm({'deezer_download': {'arl': 'old'}})
|
||||
assert cm.resolve_secret('deezer_download.arl', 'new_token') == 'new_token'
|
||||
assert cm.resolve_secret('deezer_download.arl', ' spaced ') == 'spaced'
|
||||
|
||||
|
||||
def test_sentinel_with_nothing_stored_returns_empty():
|
||||
cm = _cm({})
|
||||
assert cm.resolve_secret('deezer_download.arl', S) == ''
|
||||
|
|
@ -21578,7 +21578,10 @@ def deezer_download_test():
|
|||
"""Test Deezer ARL token authentication."""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
arl = data.get('arl', '')
|
||||
# An empty/redaction-sentinel value means "test the SAVED token" — the
|
||||
# settings field round-trips a mask for a saved-but-untouched secret, so
|
||||
# testing it must use the stored ARL, not the mask (#870).
|
||||
arl = config_manager.resolve_secret('deezer_download.arl', data.get('arl'))
|
||||
if not arl:
|
||||
return jsonify({'success': False, 'error': 'No ARL token provided'})
|
||||
|
||||
|
|
|
|||
|
|
@ -4305,13 +4305,11 @@ async function testDeezerDownloadConnection() {
|
|||
statusEl.textContent = 'Checking...';
|
||||
statusEl.style.color = '#aaa';
|
||||
try {
|
||||
// Save the ARL first so the backend can use it
|
||||
const arl = document.getElementById('deezer-download-arl')?.value || '';
|
||||
if (!arl) {
|
||||
statusEl.textContent = 'No ARL token provided';
|
||||
statusEl.style.color = '#ff9800';
|
||||
return;
|
||||
}
|
||||
let arl = document.getElementById('deezer-download-arl')?.value || '';
|
||||
// An untouched field holds the redaction sentinel (a token IS saved) —
|
||||
// send empty so the backend tests the SAVED token instead of the mask,
|
||||
// which the source would reject (#870).
|
||||
if (arl === REDACTED_SECRET_SENTINEL) arl = '';
|
||||
const resp = await fetch('/api/deezer-download/test', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
|
|
|||
Loading…
Reference in a new issue