mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-08 08:57:09 +08:00
Merge pull request #2700 from InfinityPacer/feature/api-token
This commit is contained in:
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, Header
|
|||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.core.plugin import PluginManager
|
from app.core.plugin import PluginManager
|
||||||
from app.core.security import verify_token
|
from app.core.security import verify_token, verify_apitoken
|
||||||
from app.db.systemconfig_oper import SystemConfigOper
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
from app.db.user_oper import get_current_active_superuser
|
from app.db.user_oper import get_current_active_superuser
|
||||||
from app.helper.plugin import PluginHelper
|
from app.helper.plugin import PluginHelper
|
||||||
@@ -23,6 +23,10 @@ def register_plugin_api(plugin_id: str = None):
|
|||||||
if r.path == api.get("path"):
|
if r.path == api.get("path"):
|
||||||
router.routes.remove(r)
|
router.routes.remove(r)
|
||||||
break
|
break
|
||||||
|
# 检查是否允许匿名访问,如果不允许匿名访问,则添加 API_TOKEN 验证
|
||||||
|
allow_anonymous = api.pop("allow_anonymous", False)
|
||||||
|
if not allow_anonymous:
|
||||||
|
api.setdefault("dependencies", []).append(Depends(verify_apitoken))
|
||||||
router.add_api_route(**api)
|
router.add_api_route(**api)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ def set_env_setting(env: dict,
|
|||||||
v = ''
|
v = ''
|
||||||
else:
|
else:
|
||||||
v = str(v)
|
v = str(v)
|
||||||
set_key(settings.CONFIG_PATH / "app.env", k, v)
|
set_key(SystemUtils.get_env_path(), k, v)
|
||||||
return schemas.Response(success=True)
|
return schemas.Response(success=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ def set_setting(key: str, value: Union[list, dict, bool, int, str] = None,
|
|||||||
value = ''
|
value = ''
|
||||||
else:
|
else:
|
||||||
value = str(value)
|
value = str(value)
|
||||||
set_key(settings.CONFIG_PATH / "app.env", key, value)
|
set_key(SystemUtils.get_env_path(), key, value)
|
||||||
else:
|
else:
|
||||||
SystemConfigOper().set(key, value)
|
SystemConfigOper().set(key, value)
|
||||||
return schemas.Response(success=True)
|
return schemas.Response(success=True)
|
||||||
|
|||||||
+17
-5
@@ -5,8 +5,10 @@ from pathlib import Path
|
|||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from dotenv import set_key
|
||||||
from pydantic import BaseSettings, validator
|
from pydantic import BaseSettings, validator
|
||||||
|
|
||||||
|
from app.log import logger
|
||||||
from app.utils.system import SystemUtils
|
from app.utils.system import SystemUtils
|
||||||
|
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ class Settings(BaseSettings):
|
|||||||
# 超级管理员
|
# 超级管理员
|
||||||
SUPERUSER: str = "admin"
|
SUPERUSER: str = "admin"
|
||||||
# API密钥,需要更换
|
# API密钥,需要更换
|
||||||
API_TOKEN: str = "moviepilot"
|
API_TOKEN: Optional[str] = None
|
||||||
# 网络代理 IP:PORT
|
# 网络代理 IP:PORT
|
||||||
PROXY_HOST: Optional[str] = None
|
PROXY_HOST: Optional[str] = None
|
||||||
# 登录页面电影海报,tmdb/bing
|
# 登录页面电影海报,tmdb/bing
|
||||||
@@ -198,6 +200,17 @@ class Settings(BaseSettings):
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
raise ValueError(f"{value} 格式错误,不是有效数字!")
|
raise ValueError(f"{value} 格式错误,不是有效数字!")
|
||||||
|
|
||||||
|
@validator("API_TOKEN", pre=True, always=True)
|
||||||
|
def validate_api_token(cls, v):
|
||||||
|
if not v:
|
||||||
|
new_token = secrets.token_urlsafe(16)
|
||||||
|
logger.info(f"API_TOKEN 未设置,已随机生成新的 API_TOKEN:{new_token}")
|
||||||
|
set_key(str(SystemUtils.get_env_path()), "API_TOKEN", new_token)
|
||||||
|
return new_token
|
||||||
|
elif len(v) < 16:
|
||||||
|
logger.warning("API_TOKEN 长度不足 16 个字符,存在安全隐患,建议尽快更换为更复杂的密钥!")
|
||||||
|
return v
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def INNER_CONFIG_PATH(self):
|
def INNER_CONFIG_PATH(self):
|
||||||
return self.ROOT_PATH / "config"
|
return self.ROOT_PATH / "config"
|
||||||
@@ -414,6 +427,8 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
case_sensitive = True
|
case_sensitive = True
|
||||||
|
env_file = SystemUtils.get_env_path()
|
||||||
|
env_file_encoding = "utf-8"
|
||||||
|
|
||||||
|
|
||||||
class GlobalVar(object):
|
class GlobalVar(object):
|
||||||
@@ -451,10 +466,7 @@ class GlobalVar(object):
|
|||||||
|
|
||||||
|
|
||||||
# 实例化配置
|
# 实例化配置
|
||||||
settings = Settings(
|
settings = Settings()
|
||||||
_env_file=Settings().CONFIG_PATH / "app.env",
|
|
||||||
_env_file_encoding="utf-8"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 全局标识
|
# 全局标识
|
||||||
global_vars = GlobalVar()
|
global_vars = GlobalVar()
|
||||||
|
|||||||
+2
-1
@@ -426,7 +426,8 @@ class PluginManager(metaclass=Singleton):
|
|||||||
"endpoint": self.xxx,
|
"endpoint": self.xxx,
|
||||||
"methods": ["GET", "POST"],
|
"methods": ["GET", "POST"],
|
||||||
"summary": "API名称",
|
"summary": "API名称",
|
||||||
"description": "API说明"
|
"description": "API说明",
|
||||||
|
"allow_anonymous": false
|
||||||
}]
|
}]
|
||||||
"""
|
"""
|
||||||
ret_apis = []
|
ret_apis = []
|
||||||
|
|||||||
+9
-3
@@ -2,7 +2,7 @@ import inspect
|
|||||||
import logging
|
import logging
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from pydantic import BaseSettings
|
from pydantic import BaseSettings
|
||||||
@@ -14,6 +14,8 @@ class LogSettings(BaseSettings):
|
|||||||
"""
|
"""
|
||||||
日志设置
|
日志设置
|
||||||
"""
|
"""
|
||||||
|
# 配置文件目录
|
||||||
|
CONFIG_DIR: Optional[str] = None
|
||||||
# 是否为调试模式
|
# 是否为调试模式
|
||||||
DEBUG: bool = False
|
DEBUG: bool = False
|
||||||
# 日志级别(DEBUG、INFO、WARNING、ERROR等)
|
# 日志级别(DEBUG、INFO、WARNING、ERROR等)
|
||||||
@@ -27,12 +29,16 @@ class LogSettings(BaseSettings):
|
|||||||
# 文件日志格式
|
# 文件日志格式
|
||||||
LOG_FILE_FORMAT: str = "【%(levelname)s】%(asctime)s - %(message)s"
|
LOG_FILE_FORMAT: str = "【%(levelname)s】%(asctime)s - %(message)s"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def CONFIG_PATH(self):
|
||||||
|
return SystemUtils.get_config_path(self.CONFIG_DIR)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def LOG_PATH(self):
|
def LOG_PATH(self):
|
||||||
"""
|
"""
|
||||||
获取日志存储路径
|
获取日志存储路径
|
||||||
"""
|
"""
|
||||||
return SystemUtils.get_config_path() / "logs"
|
return self.CONFIG_PATH / "logs"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def LOG_MAX_FILE_SIZE_BYTES(self):
|
def LOG_MAX_FILE_SIZE_BYTES(self):
|
||||||
@@ -43,7 +49,7 @@ class LogSettings(BaseSettings):
|
|||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
case_sensitive = True
|
case_sensitive = True
|
||||||
env_file = SystemUtils.get_config_path() / "app.env"
|
env_file = SystemUtils.get_env_path()
|
||||||
env_file_encoding = "utf-8"
|
env_file_encoding = "utf-8"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+13
-2
@@ -5,7 +5,7 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Union, Tuple
|
from typing import List, Union, Tuple, Optional
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
import psutil
|
import psutil
|
||||||
@@ -473,13 +473,24 @@ class SystemUtils:
|
|||||||
return os.stat(src).st_dev == os.stat(dest).st_dev
|
return os.stat(src).st_dev == os.stat(dest).st_dev
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config_path() -> Path:
|
def get_config_path(config_dir: Optional[str] = None) -> Path:
|
||||||
"""
|
"""
|
||||||
获取配置路径
|
获取配置路径
|
||||||
"""
|
"""
|
||||||
|
if not config_dir:
|
||||||
|
config_dir = os.getenv("CONFIG_DIR")
|
||||||
|
if config_dir:
|
||||||
|
return Path(config_dir)
|
||||||
if SystemUtils.is_docker():
|
if SystemUtils.is_docker():
|
||||||
return Path("/config")
|
return Path("/config")
|
||||||
elif SystemUtils.is_frozen():
|
elif SystemUtils.is_frozen():
|
||||||
return Path(sys.executable).parent / "config"
|
return Path(sys.executable).parent / "config"
|
||||||
else:
|
else:
|
||||||
return Path(__file__).parents[2] / "config"
|
return Path(__file__).parents[2] / "config"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_env_path() -> Path:
|
||||||
|
"""
|
||||||
|
获取配置路径
|
||||||
|
"""
|
||||||
|
return SystemUtils.get_config_path() / "app.env"
|
||||||
|
|||||||
+2
-2
@@ -29,8 +29,8 @@ DOH_RESOLVERS=1.0.0.1,1.1.1.1,9.9.9.9,149.112.112.112
|
|||||||
META_CACHE_EXPIRE=0
|
META_CACHE_EXPIRE=0
|
||||||
# 自动检查和更新站点资源包(索引、认证等)
|
# 自动检查和更新站点资源包(索引、认证等)
|
||||||
AUTO_UPDATE_RESOURCE=true
|
AUTO_UPDATE_RESOURCE=true
|
||||||
# 【*】API密钥,建议更换复杂字符串,有Jellyseerr/Overseerr、媒体服务器Webhook等配置以及部分支持API_TOKEN的API中使用
|
# 【*】API密钥,未设置时系统将随机生成,建议使用复杂字符串,用于Jellyseerr/Overseerr、媒体服务器Webhook等配置以及部分支持API_TOKEN的API请求
|
||||||
API_TOKEN=moviepilot
|
API_TOKEN=''
|
||||||
# 登录页面电影海报,tmdb/bing,tmdb要求能正常连接api.themoviedb.org
|
# 登录页面电影海报,tmdb/bing,tmdb要求能正常连接api.themoviedb.org
|
||||||
WALLPAPER=tmdb
|
WALLPAPER=tmdb
|
||||||
# TMDB图片地址,无需修改需保留默认值,如果默认地址连通性不好可以尝试修改为:`static-mdb.v.geilijiasu.com`
|
# TMDB图片地址,无需修改需保留默认值,如果默认地址连通性不好可以尝试修改为:`static-mdb.v.geilijiasu.com`
|
||||||
|
|||||||
Reference in New Issue
Block a user