52 lines
2.3 KiB
Python
52 lines
2.3 KiB
Python
"""Stable Jellyfin identities for private, user-scoped integrations."""
|
|
|
|
import hashlib
|
|
from contextlib import closing
|
|
|
|
from .. import db
|
|
|
|
|
|
def source_key(base_url: str | None) -> str:
|
|
return hashlib.sha256(str(base_url or "").strip().rstrip("/").encode()).hexdigest()
|
|
|
|
|
|
def linked_user_id(username: str, base_url: str | None) -> str | None:
|
|
user = db.get_user_by_username(username)
|
|
if not user or not base_url:
|
|
return None
|
|
with closing(db._connect()) as conn, conn:
|
|
row = conn.execute(
|
|
"SELECT jellyfin_user_id FROM jellyfin_user_links WHERE source = ? AND local_user_id = ?",
|
|
(source_key(base_url), user["id"]),
|
|
).fetchone()
|
|
return row[0] if row else None
|
|
|
|
|
|
def link_user(username: str, jellyfin_user_id: str, base_url: str | None) -> None:
|
|
"""Use only verified login or canonical Jellyfin user sync, never playback names."""
|
|
user = db.get_user_by_username(username)
|
|
if not user or not jellyfin_user_id or not base_url:
|
|
return
|
|
with closing(db._connect()) as conn, conn:
|
|
if conn.execute("SELECT 1 FROM user_identity_confirmations WHERE local_user_id = ?", (user["id"],)).fetchone():
|
|
# Reviewed identities are updated only through the admin confirmation workflow.
|
|
return
|
|
# A renamed or re-created account must not silently take over an existing identity.
|
|
conn.execute(
|
|
"INSERT OR IGNORE INTO jellyfin_user_links (source, local_user_id, jellyfin_user_id) VALUES (?, ?, ?)",
|
|
(source_key(base_url), user["id"], str(jellyfin_user_id)),
|
|
)
|
|
|
|
|
|
def user_for_identity(jellyfin_user_id: str, base_url: str | None):
|
|
"""Resolve a verified upstream login to its existing local account."""
|
|
if not jellyfin_user_id or not base_url:
|
|
return None
|
|
with closing(db._connect()) as conn:
|
|
rows = conn.execute("SELECT local_user_id FROM jellyfin_user_links WHERE source=? AND lower(replace(jellyfin_user_id,'-',''))=?",
|
|
(source_key(base_url), str(jellyfin_user_id).replace('-', '').lower())).fetchall()
|
|
if len(rows) > 1:
|
|
from fastapi import HTTPException
|
|
raise HTTPException(409, 'Multiple accounts claim this Jellyfin ID. Ask an administrator to repair the links.')
|
|
return db.get_user_by_id(rows[0][0]) if rows else None
|