for basic auth rely on cookies and not just the Authorization header
This commit is contained in:
parent
0b04f098de
commit
6e52b3c356
7 changed files with 142 additions and 16 deletions
1
Pipfile
1
Pipfile
|
|
@ -23,6 +23,7 @@ brotli = "*"
|
|||
brotlicffi = "*"
|
||||
yt-dlp = "*"
|
||||
anyio = "*"
|
||||
pycryptodome = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
|||
53
Pipfile.lock
generated
53
Pipfile.lock
generated
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "17668280c8e863033313b83f64240f6837e733957fbc2613767d297fee6c6fc2"
|
||||
"sha256": "9fd5fe76e070040f587d879d9190d2bda03ef0a45a8ac970f408cb57bd578349"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
|
@ -27,11 +27,11 @@
|
|||
},
|
||||
"aiohappyeyeballs": {
|
||||
"hashes": [
|
||||
"sha256:19728772cb12263077982d2f55453babd8bec6a052a926cd5c0c42796da8bf62",
|
||||
"sha256:6cac4f5dd6e34a9644e69cf9021ef679e4394f54e58a183056d12009e42ea9e3"
|
||||
"sha256:0850b580748c7071db98bffff6d4c94028d0d3035acc20fd721a0ce7e8cac35d",
|
||||
"sha256:18fde6204a76deeabc97c48bdd01d5801cfda5d6b9c8bbeb1aaaee9d648ca191"
|
||||
],
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==2.4.8"
|
||||
"version": "==2.5.0"
|
||||
},
|
||||
"aiohttp": {
|
||||
"hashes": [
|
||||
|
|
@ -842,6 +842,45 @@
|
|||
"markers": "python_version >= '3.8'",
|
||||
"version": "==2.22"
|
||||
},
|
||||
"pycryptodome": {
|
||||
"hashes": [
|
||||
"sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8",
|
||||
"sha256:0fa0a05a6a697ccbf2a12cec3d6d2650b50881899b845fac6e87416f8cb7e87d",
|
||||
"sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0",
|
||||
"sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93",
|
||||
"sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4",
|
||||
"sha256:26412b21df30b2861424a6c6d5b1d8ca8107612a4cfa4d0183e71c5d200fb34a",
|
||||
"sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764",
|
||||
"sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca",
|
||||
"sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e",
|
||||
"sha256:3ba4cc304eac4d4d458f508d4955a88ba25026890e8abff9b60404f76a62c55e",
|
||||
"sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd",
|
||||
"sha256:590ef0898a4b0a15485b05210b4a1c9de8806d3ad3d47f74ab1dc07c67a6827f",
|
||||
"sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6",
|
||||
"sha256:6cce52e196a5f1d6797ff7946cdff2038d3b5f0aba4a43cb6bf46b575fd1b5bb",
|
||||
"sha256:7cb087b8612c8a1a14cf37dd754685be9a8d9869bed2ffaaceb04850a8aeef7e",
|
||||
"sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1",
|
||||
"sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6",
|
||||
"sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a",
|
||||
"sha256:8acd7d34af70ee63f9a849f957558e49a98f8f1634f86a59d2be62bb8e93f71c",
|
||||
"sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2",
|
||||
"sha256:a1752eca64c60852f38bb29e2c86fca30d7672c024128ef5d70cc15868fa10f4",
|
||||
"sha256:a3804675283f4764a02db05f5191eb8fec2bb6ca34d466167fc78a5f05bbe6b3",
|
||||
"sha256:a4e74c522d630766b03a836c15bff77cb657c5fdf098abf8b1ada2aebc7d0819",
|
||||
"sha256:a915597ffccabe902e7090e199a7bf7a381c5506a747d5e9d27ba55197a2c568",
|
||||
"sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53",
|
||||
"sha256:cc2269ab4bce40b027b49663d61d816903a4bd90ad88cb99ed561aadb3888dd3",
|
||||
"sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8",
|
||||
"sha256:dad9bf36eda068e89059d1f07408e397856be9511d7113ea4b586642a429a4fd",
|
||||
"sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b",
|
||||
"sha256:f35e442630bc4bc2e1878482d6f59ea22e280d7121d7adeaedba58c23ab6386b",
|
||||
"sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297",
|
||||
"sha256:ff99f952db3db2fbe98a0b355175f93ec334ba3d01bbde25ad3a5a33abc02b58"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==3.21.0"
|
||||
},
|
||||
"pyjson5": {
|
||||
"hashes": [
|
||||
"sha256:00361a0aaabc3162992087d83bc067517c95c35810b631d9a40b80732003066e",
|
||||
|
|
@ -1159,11 +1198,11 @@
|
|||
},
|
||||
"tzlocal": {
|
||||
"hashes": [
|
||||
"sha256:2fafbfc07e9d8b49ade18f898d6bcd37ae88ce3ad6486842a2e4f03af68323d2",
|
||||
"sha256:3814135a1bb29763c6e4f08fd6e41dbb435c7a60bfbb03270211bcc537187d8c"
|
||||
"sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd",
|
||||
"sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d"
|
||||
],
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==5.3"
|
||||
"version": "==5.3.1"
|
||||
},
|
||||
"wsproto": {
|
||||
"hashes": [
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import random
|
|||
import time
|
||||
import uuid
|
||||
from collections.abc import Awaitable
|
||||
from datetime import UTC, datetime
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
|
||||
|
|
@ -38,6 +38,8 @@ from .Utils import (
|
|||
IGNORED_KEYS,
|
||||
StreamingError,
|
||||
arg_converter,
|
||||
decrypt_data,
|
||||
encrypt_data,
|
||||
get_file,
|
||||
get_mime_type,
|
||||
get_sidecar_subtitles,
|
||||
|
|
@ -281,6 +283,18 @@ class HttpAPI(Common):
|
|||
if auth_header is None and request.query.get("apikey") is not None:
|
||||
auth_header = f"Basic {request.query.get('apikey')}"
|
||||
|
||||
if auth_header is None:
|
||||
auth_cookie = request.cookies.get("auth")
|
||||
if auth_cookie is not None:
|
||||
try:
|
||||
data = decrypt_data(auth_cookie, key=Config.get_instance().secret_key)
|
||||
if data is not None:
|
||||
LOG.info(f"Decoded cookie data '{data}'.")
|
||||
data = base64.b64encode(data.encode()).decode()
|
||||
auth_header = f"Basic {data}"
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
|
||||
if auth_header is None:
|
||||
return web.json_response(
|
||||
status=web.HTTPUnauthorized.status_code,
|
||||
|
|
@ -309,7 +323,26 @@ class HttpAPI(Common):
|
|||
data={"error": "Unauthorized (Invalid credentials)."}, status=web.HTTPUnauthorized.status_code
|
||||
)
|
||||
|
||||
return await handler(request)
|
||||
response = await handler(request)
|
||||
if request.path != "/":
|
||||
return response
|
||||
|
||||
try:
|
||||
response.set_cookie(
|
||||
"auth",
|
||||
encrypt_data(
|
||||
f"{user_name}:{user_password}",
|
||||
key=Config.get_instance().secret_key,
|
||||
),
|
||||
max_age=60 * 60 * 24 * 7,
|
||||
expires=datetime.now(UTC) + timedelta(days=7),
|
||||
httponly=True,
|
||||
samesite="Strict",
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
|
||||
return response
|
||||
|
||||
return middleware_handler
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import base64
|
||||
import copy
|
||||
import glob
|
||||
import ipaddress
|
||||
|
|
@ -13,6 +14,7 @@ from functools import lru_cache
|
|||
from typing import Any
|
||||
|
||||
import yt_dlp
|
||||
from Crypto.Cipher import AES
|
||||
from yt_dlp.networking.impersonate import ImpersonateTarget
|
||||
|
||||
from .LogWrapper import LogWrapper
|
||||
|
|
@ -615,3 +617,44 @@ def get_file(download_path: str, file: str | pathlib.Path) -> tuple[pathlib.Path
|
|||
return (realFile, 404)
|
||||
|
||||
return (pathlib.Path(possibleFile), 302)
|
||||
|
||||
|
||||
def encrypt_data(data: str, key: bytes) -> str:
|
||||
"""
|
||||
Encrypts data using AES-GCM
|
||||
|
||||
Args:
|
||||
data (str): The data to encrypt
|
||||
key (bytes): The encryption key
|
||||
|
||||
Returns:
|
||||
str: The encrypted data as a base64 encoded string
|
||||
|
||||
"""
|
||||
iv = os.urandom(12) # AES-GCM requires a 12-byte IV
|
||||
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)
|
||||
ciphertext, tag = cipher.encrypt_and_digest(data.encode())
|
||||
|
||||
return base64.urlsafe_b64encode(iv + ciphertext + tag).decode()
|
||||
|
||||
|
||||
def decrypt_data(data: str, key: bytes) -> str:
|
||||
"""
|
||||
Decrypts AES-GCM encrypted data
|
||||
|
||||
Args:
|
||||
data (str): The encrypted data as a base64 encoded string
|
||||
key (bytes): The encryption key
|
||||
|
||||
Returns:
|
||||
str: The decrypted data
|
||||
|
||||
"""
|
||||
try:
|
||||
data = base64.urlsafe_b64decode(data)
|
||||
iv, ciphertext, tag = data[:12], data[12:-16], data[-16:]
|
||||
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)
|
||||
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
|
||||
return plaintext.decode()
|
||||
except Exception:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import json
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
|
|
@ -141,6 +140,9 @@ class Config:
|
|||
sentry_dsn: str | None = None
|
||||
"The Sentry DSN to use for error reporting."
|
||||
|
||||
secret_key: str
|
||||
"The secret key to use for the application."
|
||||
|
||||
_manual_vars: tuple = (
|
||||
"temp_path",
|
||||
"config_path",
|
||||
|
|
@ -347,6 +349,18 @@ class Config:
|
|||
handler.setFormatter(formatter)
|
||||
logging.getLogger().addHandler(handler)
|
||||
|
||||
key_file: str = os.path.join(self.config_path, "secret.key")
|
||||
|
||||
# save key as bytes.
|
||||
if os.path.exists(key_file) and os.path.getsize(key_file) > 5:
|
||||
with open(key_file,"rb") as f:
|
||||
self.secret_key = f.read().strip()
|
||||
else:
|
||||
self.secret_key = os.urandom(32)
|
||||
with open(key_file, "wb") as f:
|
||||
f.write(self.secret_key)
|
||||
|
||||
|
||||
self.started = time.time()
|
||||
|
||||
def _get_attributes(self) -> dict:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export default defineNuxtConfig({
|
|||
{ "name": "viewport", "content": "width=device-width, initial-scale=1.0, maximum-scale=1.0" },
|
||||
{ "name": "theme-color", "content": "#000000" },
|
||||
],
|
||||
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
|
||||
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico?v=100' }]
|
||||
},
|
||||
pageTransition: { name: 'page', mode: 'out-in' }
|
||||
},
|
||||
|
|
|
|||
|
|
@ -235,14 +235,10 @@ const encodePath = item => {
|
|||
*/
|
||||
const request = (url, options = {}) => {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const token = useStorage('token', null)
|
||||
options = options || {}
|
||||
options.method = options.method || 'GET'
|
||||
options.headers = options.headers || {}
|
||||
|
||||
if (token && undefined === options.headers['Authorization']) {
|
||||
options.headers['Authorization'] = 'Token ' + token.value
|
||||
}
|
||||
options.withCredentials = true
|
||||
|
||||
if (undefined === options.headers['Content-Type']) {
|
||||
if (!(options?.body instanceof FormData)) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue