diff --git a/config.default.toml b/config.default.toml index 5481262..ee858cc 100644 --- a/config.default.toml +++ b/config.default.toml @@ -7,8 +7,9 @@ debug = false # Whether to print verbose, d cache = true # Whether to cache requests for 3 hours (temporary solution to long load times). [api] -api_key = "" # Leave empty API key for public access to non-sensitive backend -api_key_admin = "CHANGEME" # Empty *admin* API key will autogenerate a random one every launch. +api_key = "" # Leave empty API key for public access to non-sensitive backend +api_key_admin = "CHANGEME" # Empty *admin* API key will autogenerate a random one every launch. +enable_debugger_halt = false # Whether to allow to trigger pdb using admin's API key. [extractor] user-agent = "" # Leave empty for default (Firefox ESR). diff --git a/ythdd_api_v1.py b/ythdd_api_v1.py index 516f54a..80e2a45 100644 --- a/ythdd_api_v1.py +++ b/ythdd_api_v1.py @@ -8,19 +8,20 @@ import ythdd_globals, ythdd_extractor #from flask_sqlalchemy import SQLAlchemy #import ythdd_api_v1_stats, ythdd_api_v1_user, ythdd_api_v1_info, ythdd_api_v1_query, ythdd_api_v1_meta, ythdd_api_v1_admin -def requireAuthentication(func): - @wraps(func) - def wrapper(*args, **kwargs): - token = kwargs["r"].args.get('token') - if token == lewy_globals.config['api']['api_key']: - try: - status, received, data = func(*args, **kwargs) - return status, received, data - except: - raise AssertionError(f"Function \"{func.__name__}\" does not return status, code, and data as it should!") - else: - return 401, "error", {'error_msg': "Unauthorized"} - return wrapper +def requireAuthentication(admin: bool = True): + def functionWrapper(func): + def wrapper(*args, **kwargs): + token = kwargs["r"].args.get('token') + if token == ythdd_globals.config['api']['api_key' + admin * '_admin']: + try: + status, received, data = func(*args, **kwargs) + return status, received, data + except: + raise AssertionError(f"Function \"{func.__name__}\" does not return status, code, and data as it should!") + else: + return 401, "error", {'error_msg': "Unauthorized"} + return wrapper + return functionWrapper def incrementBadRequests(): ythdd_globals.apiFailedRequests += 1 @@ -143,6 +144,13 @@ def hot(data): incrementBadRequests() return notImplemented([data[1]]) # workaround before notImplemented is reworked +@requireAuthentication(admin=True) +def debugger_halt(r): + if not ythdd_globals.config["api"]["enable_debugger_halt"]: + return 403, "Administrator has disabled access for this endpoint.", [] + breakpoint() + return 200, "Pdb triggered and ended successfully.", [] + def lookup(data, request): match data[0]: case 'stats': @@ -163,6 +171,8 @@ def lookup(data, request): case 'admin': # REQUIRE CREDENTIALS! return stub_hello() + case 'halt': + return debugger_halt(r=request) case _: incrementBadRequests() return notImplemented(data) \ No newline at end of file diff --git a/ythdd_globals.py b/ythdd_globals.py index 3b967e1..51208db 100644 --- a/ythdd_globals.py +++ b/ythdd_globals.py @@ -32,7 +32,7 @@ def getConfig(configfile): global randomly_generated_passcode if not os.path.exists(configfile): - dummy_config = {'general': {'db_file_path': 'ythdd_db.sqlite', 'video_storage_directory_path': 'videos/', 'is_proxied': False, 'public_facing_url': 'http://127.0.0.1:5000/', 'debug': False, 'cache': True}, 'api': {'api_key': 'CHANGEME'}, 'proxy': {'user-agent': '', 'allow_proxying_videos': True, 'match_initcwndbps': True}, 'extractor': {'user-agent': '', 'cookies_path': ''}, 'admin': {'admins': ['admin']}, 'yt_dlp': {}, 'postprocessing': {'presets': [{'name': 'recommended: [N][<=720p] best V+A', 'format': 'bv[height<=720]+ba', 'reencode': ''}, {'name': '[N][1080p] best V+A', 'format': 'bv[height=1080]+ba', 'reencode': ''}, {'name': '[R][1080p] webm', 'format': 'bv[height=1080]+ba', 'reencode': 'webm'}, {'name': '[N][720p] best V+A', 'format': 'bv[height=720]+ba', 'reencode': ''}, {'name': '[R][720p] webm', 'format': 'bv[height=720]+ba', 'reencode': 'webm'}, {'name': '[N][480p] best V+A', 'format': 'bv[height=480]+ba', 'reencode': ''}, {'name': '[480p] VP9 webm/reencode', 'format': 'bv*[height=480][ext=webm]+ba/bv[height=480]+ba', 'reencode': 'webm'}, {'name': '[N][1080p] best video only', 'format': 'bv[height=1080]', 'reencode': ''}, {'name': '[N][opus] best audio only', 'format': 'ba', 'reencode': 'opus'}]}} + dummy_config = {'general': {'db_file_path': 'ythdd_db.sqlite', 'video_storage_directory_path': 'videos/', 'is_proxied': False, 'public_facing_url': 'http://127.0.0.1:5000/', 'debug': False, 'cache': True}, 'api': {'api_key': 'CHANGEME', 'enable_debugger_halt': False}, 'proxy': {'user-agent': '', 'allow_proxying_videos': True, 'match_initcwndbps': True}, 'extractor': {'user-agent': '', 'cookies_path': ''}, 'admin': {'admins': ['admin']}, 'yt_dlp': {}, 'postprocessing': {'presets': [{'name': 'recommended: [N][<=720p] best V+A', 'format': 'bv[height<=720]+ba', 'reencode': ''}, {'name': '[N][1080p] best V+A', 'format': 'bv[height=1080]+ba', 'reencode': ''}, {'name': '[R][1080p] webm', 'format': 'bv[height=1080]+ba', 'reencode': 'webm'}, {'name': '[N][720p] best V+A', 'format': 'bv[height=720]+ba', 'reencode': ''}, {'name': '[R][720p] webm', 'format': 'bv[height=720]+ba', 'reencode': 'webm'}, {'name': '[N][480p] best V+A', 'format': 'bv[height=480]+ba', 'reencode': ''}, {'name': '[480p] VP9 webm/reencode', 'format': 'bv*[height=480][ext=webm]+ba/bv[height=480]+ba', 'reencode': 'webm'}, {'name': '[N][1080p] best video only', 'format': 'bv[height=1080]', 'reencode': ''}, {'name': '[N][opus] best audio only', 'format': 'ba', 'reencode': 'opus'}]}} # if a passcode has not been provided by the user (config file doesn't exist, and user didn't specify it using an argument) print(f"{colors.WARNING}WARNING{colors.ENDC}: Using default, baked in config data. {colors.ENDL}" f" Consider copying and editing the provided example file ({colors.OKCYAN}config.default.toml{colors.ENDC}).")