Skip to content

Commit a43ded9

Browse files
feat: add option to delete account
1 parent fa61711 commit a43ded9

File tree

5 files changed

+130
-0
lines changed

5 files changed

+130
-0
lines changed

app/api/endpoints/tokens.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,45 @@ async def create_token(payload: TokenRequest, request: Request) -> TokenResponse
159159
manifestUrl=manifest_url,
160160
expiresInSeconds=expires_in,
161161
)
162+
163+
164+
@router.post("/delete", status_code=200)
165+
async def delete_token(payload: TokenRequest):
166+
"""Delete a token based on provided credentials."""
167+
username = payload.username.strip() if payload.username else None
168+
password = payload.password
169+
auth_key = payload.authKey.strip() if payload.authKey else None
170+
171+
if auth_key and auth_key.startswith('"') and auth_key.endswith('"'):
172+
auth_key = auth_key[1:-1].strip()
173+
174+
if not auth_key and not (username and password):
175+
raise HTTPException(
176+
status_code=400,
177+
detail="Provide either a Stremio auth key or both username and password to delete account.",
178+
)
179+
180+
payload_to_derive = {
181+
"username": username,
182+
"password": password,
183+
"authKey": auth_key,
184+
}
185+
186+
try:
187+
# We don't verify credentials with Stremio here, we just check if we have a token for them.
188+
# If the user provides wrong credentials, we'll derive a wrong token, which won't exist in Redis.
189+
# That's fine, we can just say "deleted" or "not found".
190+
# However, to be nice, we might want to say "Settings deleted" even if they didn't exist.
191+
# But if we want to be strict, we could check existence.
192+
# Let's just try to delete.
193+
194+
token = token_store.derive_token(payload_to_derive)
195+
await token_store.delete_token(token)
196+
logger.info(f"[{redact_token(token)}] Token deleted (if existed)")
197+
return {"detail": "Settings deleted successfully"}
198+
except (redis_exceptions.RedisError, OSError) as exc:
199+
logger.error("Token deletion failed: {}", exc)
200+
raise HTTPException(
201+
status_code=503,
202+
detail="Token deletion is temporarily unavailable. Please try again once Redis is reachable.",
203+
) from exc

app/services/token_store.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ def _derive_token_value(self, payload: dict[str, Any]) -> str:
8080
secret = settings.TOKEN_SALT.encode("utf-8")
8181
return hmac.new(secret, serialized.encode("utf-8"), hashlib.sha256).hexdigest()
8282

83+
def derive_token(self, payload: dict[str, Any]) -> str:
84+
"""Public wrapper to derive token from payload."""
85+
normalized = self._normalize_payload(payload)
86+
return self._derive_token_value(normalized)
87+
8388
async def store_payload(self, payload: dict[str, Any]) -> tuple[str, bool]:
8489
self._ensure_secure_salt()
8590
normalized = self._normalize_payload(payload)

scripts/generate_release_notes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import subprocess
44
import sys
55
from pathlib import Path
6+
from traceback import print_exc
67

78
from openai import OpenAI
89
from pydantic import BaseModel
@@ -236,6 +237,7 @@ def generate_release_notes(commits, last_release_tag):
236237
return release_notes, version_name
237238
except Exception as e:
238239
print(f"Error generating release notes with LLM: {e}")
240+
print_exc()
239241

240242
try:
241243
commit_list = commits.split("\n")

static/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,19 @@
268268
</div>
269269
</button>
270270

271+
<div class="text-center mt-6">
272+
<button type="button" id="deleteAccountBtn"
273+
class="w-full bg-red-500/10 hover:bg-red-500/20 text-red-400 font-medium py-3.5 rounded-xl transition border border-red-500/20 flex items-center justify-center gap-2 group">
274+
<svg class="w-5 h-5 opacity-70 group-hover:opacity-100 transition-opacity" fill="none" stroke="currentColor"
275+
viewBox="0 0 24 24">
276+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
277+
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16">
278+
</path>
279+
</svg>
280+
Delete my settings/account
281+
</button>
282+
</div>
283+
271284
</form>
272285

273286
<!-- Success View -->

static/script.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,74 @@ function initializeSuccessActions() {
532532
}
533533
});
534534
}
535+
536+
const deleteAccountBtn = document.getElementById('deleteAccountBtn');
537+
if (deleteAccountBtn) {
538+
deleteAccountBtn.addEventListener('click', async () => {
539+
if (!confirm('Are you sure you want to delete your settings? This will remove your credentials from the server and stop your addons from working.')) {
540+
return;
541+
}
542+
543+
const authMethodValue = authMethod.value;
544+
const username = document.getElementById('username')?.value.trim();
545+
const password = document.getElementById('password')?.value;
546+
const authKey = document.getElementById('authKey')?.value.trim();
547+
548+
// Validation
549+
if (authMethodValue === 'credentials') {
550+
if (!username || !password) {
551+
showError('Please provide both email and password to delete your account.');
552+
return;
553+
}
554+
} else {
555+
if (!authKey) {
556+
showError('Please provide your Stremio auth key to delete your account.');
557+
return;
558+
}
559+
}
560+
561+
const payload = {};
562+
if (authMethodValue === 'credentials') {
563+
payload.username = username;
564+
payload.password = password;
565+
} else {
566+
payload.authKey = authKey;
567+
}
568+
569+
setLoading(true);
570+
hideError();
571+
572+
try {
573+
const response = await fetch('/tokens/delete', {
574+
method: 'POST',
575+
headers: {
576+
'Content-Type': 'application/json',
577+
},
578+
body: JSON.stringify(payload),
579+
});
580+
581+
const data = await response.json();
582+
583+
if (!response.ok) {
584+
throw new Error(data.detail || 'Failed to delete account');
585+
}
586+
587+
alert('Settings deleted successfully.');
588+
// Clear form
589+
configForm.reset();
590+
if (stremioLoginBtn.getAttribute('data-action') === 'logout') {
591+
setStremioLoggedOutState();
592+
}
593+
catalogs = [...defaultCatalogs];
594+
renderCatalogList();
595+
596+
} catch (err) {
597+
showError(err.message || 'Failed to delete account. Please try again.');
598+
} finally {
599+
setLoading(false);
600+
}
601+
});
602+
}
535603
}
536604

537605
// UI Helpers

0 commit comments

Comments
 (0)