From 60fd5a25ce63bb4628a62ff2b8c2b30aa3478a95 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 22 Dec 2025 05:57:35 +0000 Subject: [PATCH] Refactor: Read environment variables at runtime This change ensures that environment variables are read at runtime, which is important for worker reloads on Windows. Co-authored-by: mathieumoran1 --- .../__pycache__/server.cpython-312.pyc | Bin 11653 -> 12094 bytes python_video_server/server.py | 41 ++++++++++++------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/python_video_server/__pycache__/server.cpython-312.pyc b/python_video_server/__pycache__/server.cpython-312.pyc index 1eb5ce224174b4df2465417df476651a222dd8df..d1138afa704eb84fb65d7cfaa19d6e3a2f72236e 100644 GIT binary patch delta 3797 zcmbVOYit|G5xzZ=$H$2jpL&s$NQrt-k!@MBWjl84Dzcr(Q7lVxYlpGJ(%hp&N+RVw z%CVdy#2*Tf7h1cUCJvkiF5;p|B&2o}AZeOD(iVYJ2LV+INPJMy6li}0=no|Z+Wslf z*`r9S2KuK9-0U~AyR)-1v$OZ~ik1`yN9C90VPmxLGsgJ}{&O_%7Jc2d0MrZ{az~u?YJ?tLGwHfo;UC zxNVUy$s*gNZK)VN+vGy+^eXQ~ar#kJgGoj&xD_s?F()taFSP2pIG^JpJs3rLLB|fM zXAzY&U~aO5<7C%8(hepiRZs`U!qMUOH%r>Y!uaojM>dbJ-2wLcXE0#xX9;A5|?!P8^Sp9U2%tbo@<30x3i<#E_JjPt3^0R0bHQ@}HoG9p zq*}u$YbfI^qUm-&Ei@WyFn>V=Pw5yfdux%W3+f%pg)kq{cGj&e7_k{MSwkn!PPDyeUCkg3> zH39hKULefT)i0U~^_@!tUGM7OGu<#PTi!pp(tYTraix2>*mbOEA1R8XpNo+dF;Wyy z74)Yx|53^K71~aRO(DZ>(9^nn-t;;`ebi?Op*^(U(qk9^ao6&BOD{qP>5|xOI0AzE zm;WLb5bCAxSUZHBARvA8OKYw82!lh6?4rEwupt8Q0DaW9Dx74($ClfxcOo=GPupvo zkAp_AF%Dd?+l>b^t#h6Ot2Z-=^va2 z3}*qxsLeI&odgs|K+27|Sc~sx^fgz=Qwi$g#)KSF5WVHv&qL>O{{yvAi^qSQCrptl zlu}w|RVxrE<+?Qru}LQ8qS?%(lqNYCnubqK0?Bi?Mf;k~`-ZNtnE-cu2%0anD)~bJstbrs_WNrAL)YqAzd6aRGJ=;W;b*8=De&J z&MEZoo_2>)Nj@#>$T`|o6P%Y}RamE81;XwjaWMf>b#Oss=AX)GO7|u{m6Xz1l82Ew z_+)my^4yxm{d`w`;I?SnIMm3hZM0w-T@!0A%0jC74Di5LJJa z@jsxi)lUfQl9C*)YG|1!V+;ov@iW4%p(?~?W~4M$^{GrUO|l>gi{u$L{468e8L1@q zERj^Bl#XYxgw>jOCNr6oqSLYS(b%}e>Uk!OWwm-kF2|-NIhmDIQwh;)4FN#}eSrYI z+0ZKf1mH{X$v#B?)i4_H2d>o>ItOp=Dh9`j{^JFG!&*&ap{ehEL$PLme)tP|#5;lt z^i}UN5|)qot|C9H+FGk5xka%kW>#TSeimP=nDvzBC?X9vw($^_#~OPLPDKRtN@Gwb zrdnA>2*j?4sdfhGKO6lZ4l@mVtCXr#XIaPHK4;BtDRYWg4sp(gt%?oXaP_OYTs56; z3h%70jM+EYVl7J*$R!r~EDN&Muv4+qTTQ{8)=K(16i2>A(a9(s$g+TFP1&RwyA>LdvY}$NVpFP@Y8N+Z9b0hFJ^z;D7g=l=NM$>nTo3ZYs0=e8YtecaofgMGSBe6@qUx=;Gxg*RR> zZJUr{Q`w0NaoDrcDwX_uAbJ^o6ls&#M5o(_ER{O9NPp3OTF{)?N*gr3oW=O6~B&N)7`@>KNHif7o#t&C#`v zg@@aiAk2ui1*?au{!At_MYJHYpp^=v7N(yumPM$P2Olz|B}1Nm81|u2dOO^M&d{pP z1~02$syQ~3jFQZ3RwB&bq=AvoXn$v;_$5I0bjly|G}SrUv2Ni}^Tn|{9CQo^FAgvq zWcW0?9AeCAv~Hno_kGZ8MvtS*u62&}SKW8Ce!WhQd>5a(!$J48jq+U|gzJ#~YWq3| z-Sv}NcXOg}BJ#=fH%xR#$MJ!69?*Ip{aaV~!6OI>U)UV$ItIS9y4MX1tP7mmcX{B_ z1M5bOHE~t$iv!O-@Qs_9-3b7)Ejxob7izJP+0De{XRD{SRg{ BPAdQa delta 3416 zcmZ`5X-u5Qb!P8-*@IoKg$0&lIcy9zU^_klV;f&2V8;%2ve_^L>>76Ie2Y2ytss?? zrfSsMomNen)O8iBYK7I>&t!NG!{)JVEWC2Xq#wQ8841nMT*L@>c4T2<3RY9Iv7 zq?UR&IbtAnU}FoZR~^|3j)Z}30Sjd#8qxsxHaLuHaqWa?s8t-zO&Up4{%mcuEgs`8 zvnSA;i@*W&Z8WSIh52+fLTMzU_<1#!5`Nj6BltOub-tb8p$@RwN!xRq2|8bokmT9K z-6FWmM%#&cdeWrQx=}RLPPPtp(AK!L?&OAr`e{4uAT6Y66pML%+oEAjj1laZ5^i_3 z=DrMHbQ65NEk}=FvK=C}gBCuD8brPQqCOvw-C$)_SQ+jhon+^z=K8K&U^ZLZOhbHk zL)66=9woAaoAZwByNL3B?3=~I8VmtXqiGcGUiyc2OygH9 zM^2xN4EFX6^qy4A$NNtl>W`cn=;L1+gIrVE&99g~#f|(Wb4cF`K?y9~G{1py3qNHI z;Wj>JZPo7qc4+CQwGHFF{O`6F{Q+RKE|u6b7&r6J9QE~Efx%h;D0*pfJeE?lu>^pL zSR!mMLSM~y;DMSZL7eZdiUwi1Nsy{!_w#NauI%cp0Sx5A7mnd&d+yhG~ zHOK~HxPY047}EkbsimqR=7b3-%Q|IEV4_bOiA~nOhJL6acH&5RbB0xpQ#Pt=!KglM zl68_kZ_6c{__Gz>7UIf<0=#g?MYG#9WK&K<#&MIVAiw7^&>>zKM?{>|F^j^5Tq!B* zYz8unAp@f$lr;!K3)vT)Q4CUw#fDRgE|E-7_995wO8^wz2|<6IpD!IVh|FUb_=D2w zdBxe&-+#2PH!|3DymzqgWG@Q{p(q!{w=O!~btckv2vmA|`cE7fR1DEc$bO>e;>lQo zB|tT7X3vXlUlX8F0Fhqc3mu+fR58-TaFS4>n55_gm0~HX*w--rLYYrz23`K+vKs3T z0C^dv^lwP6vH{)`cml8R?**2yWvQ?HDmIsZP#yd~71ef2sxU{(7QQ7|Ye_v)crw^# z_sJVM-wIY@3%?Pp0GJ8x)LP)Nyf zu4;?8Dn483-r>mWI5#+E%W-8Vafu5}wXGY4M39~QPG#l1E!T?E1#-bcwXBtJBAD8i z(~~z?hIFurTmx%`o2>a{=XFoc(jWp${!~ZK#s-h_XOZo)H(9~EG>ViN4GN;fLF9~xA;1)o< z0bwt@B5<`})LQwGASr*Vrp)&Spo)29YJ41?gwz=Og%BO&|EdWV2|pFgHohysk-U6c zt)KVRdM$4PDNZC^;iI*gU9RBOUCXZK`vvXG1?|~_&V{}Yt&W#wzq$9mHMDFET^qb! zk*(SJR`+iY{`%l@P4|km=dO9>6#2_xvNT;6$5`T@)NkKjj(*^eLh>d^>d@nW>`f!y+Pd(#%!(C#Skh$;r(pCFjFKzC%@Kk5ZwH%;o^LiU{nK8 zw8PVcEecZo^l^cGM}W%$sBfl zWH`yBNRq`yV+kq3qSNe{&(v93aw8!nS_Qc8k=H{PR1fE zIhCSJTon_&ioL@hYbv+C4=9EynY5fX4a7gUAcybLc-CIMV0mEkUOJn#RW6txxP6yr zGQn*(+iuLxqcRZPGJDv5P$-1Aq?~X3JqgnUxg7ZOf*{f$V zwY_(yvw{9>@#7ifd*Cj~l(ydJ&AN9jI9Cn)ZT!vV+hHFrxLUW0V7YNdUG9!$P7mFi_(HHAYLOA4@if Q&bykz!*#lMi`)SJ3#2p-LjV8( diff --git a/python_video_server/server.py b/python_video_server/server.py index f384f1e..0002acb 100644 --- a/python_video_server/server.py +++ b/python_video_server/server.py @@ -19,11 +19,19 @@ def _env_str(name: str, default: str = "") -> str: return v.strip() if isinstance(v, str) and v.strip() else default -JWT_SECRET = _env_str("JWT_SECRET") -MONGO_URI = _env_str("MONGO_URI") -VIDEO_FILES_DIR = Path(_env_str("VIDEO_FILES_DIR", str(Path.cwd() / "public" / "video"))).resolve() -COOKIE_MAX_AGE_SECONDS = int(_env_str("VIDEO_SESSION_MAX_AGE_SECONDS", "28800")) # 8h -COOKIE_SAMESITE = _env_str("VIDEO_COOKIE_SAMESITE", "Lax") # Lax|Strict|None +def _jwt_secret() -> str: + # Read env at runtime (important on Windows/uvicorn worker reloads). + return _env_str("JWT_SECRET") + + +def _mongo_uri() -> str: + # Read env at runtime (important on Windows/uvicorn worker reloads). + return _env_str("MONGO_URI") + + +def _video_files_dir() -> Path: + # Read env at runtime (important on Windows/uvicorn worker reloads). + return Path(_env_str("VIDEO_FILES_DIR", str(Path.cwd() / "public" / "video"))).resolve() def _parse_bearer(authorization: Optional[str]) -> str: @@ -101,18 +109,20 @@ def _get_token(req: Request, authorization: Optional[str]) -> str: def _verify_token(token: str) -> dict: if not token: raise HTTPException(status_code=401, detail="Missing video auth token") - if not JWT_SECRET: + secret = _jwt_secret() + if not secret: raise HTTPException(status_code=500, detail="JWT_SECRET is not configured") try: - return jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) + return jwt.decode(token, secret, algorithms=["HS256"]) except jwt.PyJWTError: raise HTTPException(status_code=401, detail="Invalid or expired video auth token") def _mongo_client() -> MongoClient: - if not MONGO_URI: + uri = _mongo_uri() + if not uri: raise HTTPException(status_code=500, detail="MONGO_URI is not configured") - return MongoClient(MONGO_URI) + return MongoClient(uri) app = FastAPI() @@ -144,14 +154,16 @@ def create_video_session(request: Request, response: Response, authorization: Op secure = request.url.scheme == "https" or (request.headers.get("x-forwarded-proto") == "https") encoded = quote(token, safe="") - samesite = (COOKIE_SAMESITE or "Lax").strip().lower() + cookie_max_age_seconds = int(_env_str("VIDEO_SESSION_MAX_AGE_SECONDS", "28800")) # 8h + cookie_samesite = _env_str("VIDEO_COOKIE_SAMESITE", "Lax") # Lax|Strict|None + samesite = (cookie_samesite or "Lax").strip().lower() if samesite not in ("lax", "strict", "none"): samesite = "lax" samesite_attr = "None" if samesite == "none" else ("Strict" if samesite == "strict" else "Lax") cookie_parts = [ f"video_auth={encoded}", "Path=/api/video", - f"Max-Age={COOKIE_MAX_AGE_SECONDS}", + f"Max-Age={cookie_max_age_seconds}", f"SameSite={samesite_attr}", "HttpOnly", ] @@ -189,8 +201,9 @@ def stream_video( if not _is_safe_relative_file(video_file): raise HTTPException(status_code=400, detail="Invalid video_file path") - full_path = (VIDEO_FILES_DIR / video_file).resolve() - if not str(full_path).startswith(str(VIDEO_FILES_DIR) + os.sep) and full_path != VIDEO_FILES_DIR: + video_dir = _video_files_dir() + full_path = (video_dir / video_file).resolve() + if not str(full_path).startswith(str(video_dir) + os.sep) and full_path != video_dir: raise HTTPException(status_code=400, detail="Invalid video_file path") if not full_path.exists() or not full_path.is_file(): raise HTTPException(status_code=404, detail="Video file not found on server") @@ -245,6 +258,6 @@ def healthz(): return { "ok": True, "time": datetime.now(timezone.utc).isoformat(), - "video_files_dir": str(VIDEO_FILES_DIR), + "video_files_dir": str(_video_files_dir()), }