Process 1 build 0203261953
This commit is contained in:
@@ -12,7 +12,13 @@ from urllib.parse import urlparse, urlunparse
|
||||
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File, Request
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from ..auth import require_admin, get_current_user, require_admin_event_stream
|
||||
from ..auth import (
|
||||
require_admin,
|
||||
get_current_user,
|
||||
require_admin_event_stream,
|
||||
normalize_user_auth_provider,
|
||||
resolve_user_auth_provider,
|
||||
)
|
||||
from ..config import settings as env_settings
|
||||
from ..db import (
|
||||
delete_setting,
|
||||
@@ -40,7 +46,7 @@ from ..db import (
|
||||
set_user_profile_id,
|
||||
set_user_expires_at,
|
||||
set_user_password,
|
||||
set_jellyfin_auth_cache,
|
||||
sync_jellyfin_password_state,
|
||||
set_user_role,
|
||||
run_integrity_check,
|
||||
vacuum_db,
|
||||
@@ -85,6 +91,7 @@ from ..services.invite_email import (
|
||||
reset_invite_email_template,
|
||||
save_invite_email_template,
|
||||
send_test_email,
|
||||
smtp_email_delivery_warning,
|
||||
send_templated_email,
|
||||
smtp_email_config_ready,
|
||||
)
|
||||
@@ -1451,7 +1458,8 @@ async def update_user_password(username: str, payload: Dict[str, Any]) -> Dict[s
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
new_password_clean = new_password.strip()
|
||||
auth_provider = str(user.get("auth_provider") or "local").lower()
|
||||
user = normalize_user_auth_provider(user)
|
||||
auth_provider = resolve_user_auth_provider(user)
|
||||
if auth_provider == "local":
|
||||
set_user_password(username, new_password_clean)
|
||||
return {"status": "ok", "username": username, "provider": "local"}
|
||||
@@ -1468,7 +1476,7 @@ async def update_user_password(username: str, payload: Dict[str, Any]) -> Dict[s
|
||||
await client.set_user_password(user_id, new_password_clean)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=f"Jellyfin password update failed: {exc}") from exc
|
||||
set_jellyfin_auth_cache(username, new_password_clean)
|
||||
sync_jellyfin_password_state(username, new_password_clean)
|
||||
return {"status": "ok", "username": username, "provider": "jellyfin"}
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
@@ -1691,11 +1699,12 @@ async def update_invite_policy(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@router.get("/invites/email/templates")
|
||||
async def get_invite_email_template_settings() -> Dict[str, Any]:
|
||||
ready, detail = smtp_email_config_ready()
|
||||
warning = smtp_email_delivery_warning()
|
||||
return {
|
||||
"status": "ok",
|
||||
"email": {
|
||||
"configured": ready,
|
||||
"detail": detail,
|
||||
"detail": warning or detail,
|
||||
},
|
||||
"templates": list(get_invite_email_templates().values()),
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ from ..db import (
|
||||
get_user_by_username,
|
||||
get_users_by_username_ci,
|
||||
set_user_password,
|
||||
set_jellyfin_auth_cache,
|
||||
set_user_jellyseerr_id,
|
||||
set_user_auth_provider,
|
||||
get_signup_invite_by_code,
|
||||
@@ -35,13 +34,14 @@ from ..db import (
|
||||
get_global_request_leader,
|
||||
get_global_request_total,
|
||||
get_setting,
|
||||
sync_jellyfin_password_state,
|
||||
)
|
||||
from ..runtime import get_runtime_settings
|
||||
from ..clients.jellyfin import JellyfinClient
|
||||
from ..clients.jellyseerr import JellyseerrClient
|
||||
from ..security import create_access_token, verify_password
|
||||
from ..security import create_stream_token
|
||||
from ..auth import get_current_user
|
||||
from ..auth import get_current_user, normalize_user_auth_provider, resolve_user_auth_provider
|
||||
from ..config import settings
|
||||
from ..services.user_cache import (
|
||||
build_jellyseerr_candidate_map,
|
||||
@@ -599,7 +599,7 @@ async def jellyfin_login(request: Request, form_data: OAuth2PasswordRequestForm
|
||||
save_jellyfin_users_cache(users)
|
||||
except Exception:
|
||||
pass
|
||||
set_jellyfin_auth_cache(canonical_username, password)
|
||||
sync_jellyfin_password_state(canonical_username, password)
|
||||
if user and user.get("jellyseerr_user_id") is None and candidate_map:
|
||||
matched_id = match_jellyseerr_user_id(canonical_username, candidate_map)
|
||||
if matched_id is not None:
|
||||
@@ -781,7 +781,7 @@ async def signup(payload: dict) -> dict:
|
||||
if jellyfin_client.configured():
|
||||
logger.info("signup provisioning jellyfin username=%s", username)
|
||||
auth_provider = "jellyfin"
|
||||
local_password_value = "jellyfin-user"
|
||||
local_password_value = password_value
|
||||
try:
|
||||
await jellyfin_client.create_user_with_password(username, password_value)
|
||||
except httpx.HTTPStatusError as exc:
|
||||
@@ -838,7 +838,7 @@ async def signup(payload: dict) -> dict:
|
||||
increment_signup_invite_use(int(invite["id"]))
|
||||
created_user = get_user_by_username(username)
|
||||
if auth_provider == "jellyfin":
|
||||
set_jellyfin_auth_cache(username, password_value)
|
||||
sync_jellyfin_password_state(username, password_value)
|
||||
if (
|
||||
created_user
|
||||
and created_user.get("jellyseerr_user_id") is None
|
||||
@@ -1129,16 +1129,20 @@ async def change_password(payload: dict, current_user: dict = Depends(get_curren
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="Password must be at least 8 characters."
|
||||
)
|
||||
username = str(current_user.get("username") or "").strip()
|
||||
auth_provider = str(current_user.get("auth_provider") or "local").lower()
|
||||
if not username:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid user")
|
||||
new_password_clean = new_password.strip()
|
||||
stored_user = normalize_user_auth_provider(get_user_by_username(username))
|
||||
auth_provider = resolve_user_auth_provider(stored_user or current_user)
|
||||
logger.info("password change requested username=%s provider=%s", username, auth_provider)
|
||||
|
||||
if auth_provider == "local":
|
||||
user = verify_user_password(username, current_password)
|
||||
if not user:
|
||||
logger.warning("password change rejected username=%s provider=local reason=invalid-current-password", username)
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Current password is incorrect")
|
||||
set_user_password(username, new_password_clean)
|
||||
logger.info("password change completed username=%s provider=local", username)
|
||||
return {"status": "ok", "provider": "local"}
|
||||
|
||||
if auth_provider == "jellyfin":
|
||||
@@ -1152,6 +1156,7 @@ async def change_password(payload: dict, current_user: dict = Depends(get_curren
|
||||
try:
|
||||
auth_result = await client.authenticate_by_name(username, current_password)
|
||||
if not isinstance(auth_result, dict) or not auth_result.get("User"):
|
||||
logger.warning("password change rejected username=%s provider=jellyfin reason=invalid-current-password", username)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Current password is incorrect"
|
||||
)
|
||||
@@ -1159,6 +1164,7 @@ async def change_password(payload: dict, current_user: dict = Depends(get_curren
|
||||
raise
|
||||
except Exception as exc:
|
||||
detail = _extract_http_error_detail(exc)
|
||||
logger.warning("password change validation failed username=%s provider=jellyfin detail=%s", username, detail)
|
||||
if isinstance(exc, httpx.HTTPStatusError) and exc.response is not None and exc.response.status_code in {401, 403}:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Current password is incorrect"
|
||||
@@ -1176,13 +1182,15 @@ async def change_password(payload: dict, current_user: dict = Depends(get_curren
|
||||
await client.set_user_password(user_id, new_password_clean)
|
||||
except Exception as exc:
|
||||
detail = _extract_http_error_detail(exc)
|
||||
logger.warning("password change update failed username=%s provider=jellyfin detail=%s", username, detail)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail=f"Jellyfin password update failed: {detail}",
|
||||
) from exc
|
||||
|
||||
# Keep Magent's Jellyfin auth cache in sync for faster subsequent sign-ins.
|
||||
set_jellyfin_auth_cache(username, new_password_clean)
|
||||
# Keep Magent's password hash and Jellyfin auth cache aligned with Jellyfin.
|
||||
sync_jellyfin_password_state(username, new_password_clean)
|
||||
logger.info("password change completed username=%s provider=jellyfin", username)
|
||||
return {"status": "ok", "provider": "jellyfin"}
|
||||
|
||||
raise HTTPException(
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any, Dict
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from ..auth import get_current_user
|
||||
from ..build_info import BUILD_NUMBER, CHANGELOG
|
||||
from ..runtime import get_runtime_settings
|
||||
|
||||
router = APIRouter(prefix="/site", tags=["site"])
|
||||
@@ -17,7 +18,7 @@ def _build_site_info(include_changelog: bool) -> Dict[str, Any]:
|
||||
if tone not in _BANNER_TONES:
|
||||
tone = "info"
|
||||
info = {
|
||||
"buildNumber": (runtime.site_build_number or "").strip(),
|
||||
"buildNumber": (runtime.site_build_number or BUILD_NUMBER or "").strip(),
|
||||
"banner": {
|
||||
"enabled": bool(runtime.site_banner_enabled and banner_message),
|
||||
"message": banner_message,
|
||||
@@ -25,7 +26,7 @@ def _build_site_info(include_changelog: bool) -> Dict[str, Any]:
|
||||
},
|
||||
}
|
||||
if include_changelog:
|
||||
info["changelog"] = (runtime.site_changelog or "").strip()
|
||||
info["changelog"] = (CHANGELOG or "").strip()
|
||||
return info
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user