1515
1616class TokenRequest (BaseModel ):
1717 authKey : str | None = Field (default = None , description = "Stremio auth key" )
18+ email : str | None = Field (default = None , description = "Stremio account email" )
19+ password : str | None = Field (default = None , description = "Stremio account password (stored securely)" )
1820 catalogs : list [CatalogConfig ] | None = Field (default = None , description = "Optional catalog configuration" )
1921 language : str = Field (default = "en-US" , description = "Language for TMDB API" )
2022 rpdb_key : str | None = Field (default = None , description = "Optional RPDB API Key" )
@@ -69,27 +71,44 @@ async def _verify_credentials_or_raise(payload: dict) -> str:
6971
7072@router .post ("/" , response_model = TokenResponse )
7173async def create_token (payload : TokenRequest , request : Request ) -> TokenResponse :
72- stremio_auth_key = payload .authKey .strip () if payload .authKey else None
74+ # Prefer email+password if provided; else require authKey
75+ email = (payload .email or "" ).strip () or None
76+ password = (payload .password or "" ).strip () or None
77+ stremio_auth_key = (payload .authKey or "" ).strip () or None
7378
74- if not stremio_auth_key :
75- raise HTTPException (status_code = 400 , detail = "Stremio auth key is required ." )
79+ if not ( email and password ) and not stremio_auth_key :
80+ raise HTTPException (status_code = 400 , detail = "Provide email+password or a valid Stremio auth key." )
7681
77- # Remove quotes if present
78- if stremio_auth_key .startswith ('"' ) and stremio_auth_key .endswith ('"' ):
82+ # Remove quotes if present for authKey
83+ if stremio_auth_key and stremio_auth_key .startswith ('"' ) and stremio_auth_key .endswith ('"' ):
7984 stremio_auth_key = stremio_auth_key [1 :- 1 ].strip ()
8085
8186 rpdb_key = payload .rpdb_key .strip () if payload .rpdb_key else None
8287
83- # 1. Fetch user info from Stremio (user_id and email)
84- stremio_service = StremioService (auth_key = stremio_auth_key )
85- try :
86- user_info = await stremio_service .get_user_info ()
87- user_id = user_info ["user_id" ]
88- email = user_info .get ("email" , "" )
89- except Exception as e :
90- raise HTTPException (status_code = 400 , detail = f"Failed to verify Stremio identity: { e } " )
91- finally :
92- await stremio_service .close ()
88+ # 1. Establish a valid auth key and fetch user info
89+ if email and password :
90+ stremio_service = StremioService (username = email , password = password )
91+ try :
92+ # Always get a fresh key
93+ fresh_key = await stremio_service ._login_for_auth_key ()
94+ stremio_auth_key = fresh_key
95+ user_info = await stremio_service .get_user_info ()
96+ user_id = user_info ["user_id" ]
97+ resolved_email = user_info .get ("email" , email )
98+ except Exception as e :
99+ raise HTTPException (status_code = 400 , detail = f"Failed to verify Stremio identity: { e } " )
100+ finally :
101+ await stremio_service .close ()
102+ else :
103+ stremio_service = StremioService (auth_key = stremio_auth_key )
104+ try :
105+ user_info = await stremio_service .get_user_info ()
106+ user_id = user_info ["user_id" ]
107+ resolved_email = user_info .get ("email" , "" )
108+ except Exception as e :
109+ raise HTTPException (status_code = 400 , detail = f"Failed to verify Stremio identity: { e } " )
110+ finally :
111+ await stremio_service .close ()
93112
94113 # 2. Check if user already exists
95114 token = token_store .get_token_from_user_id (user_id )
@@ -109,20 +128,30 @@ async def create_token(payload: TokenRequest, request: Request) -> TokenResponse
109128 is_new_account = not existing_data
110129
111130 # 4. Verify Stremio connection
112- verified_auth_key = await _verify_credentials_or_raise ({"authKey" : stremio_auth_key })
131+ # Already verified above. For authKey path, still validate to catch expired keys
132+ if not (email and password ):
133+ verified_auth_key = await _verify_credentials_or_raise ({"authKey" : stremio_auth_key })
134+ else :
135+ verified_auth_key = stremio_auth_key
113136
114137 # 5. Prepare payload to store
115138 payload_to_store = {
116139 "authKey" : verified_auth_key ,
117- "email" : email ,
140+ "email" : resolved_email or email or "" ,
118141 "settings" : user_settings .model_dump (),
119142 }
143+ # Store password if provided so we can refresh authKey later without user action
144+ if email and password :
145+ payload_to_store ["password" ] = password
120146
121147 # 6. Store user data
122148 try :
123149 token = await token_store .store_user_data (user_id , payload_to_store )
124150 logger .info (f"[{ redact_token (token )} ] Account { 'created' if is_new_account else 'updated' } for user { user_id } " )
125151 except RuntimeError as exc :
152+ # Surface a clear message when secure storage fails
153+ if "PASSWORD_ENCRYPT_FAILED" in str (exc ):
154+ raise HTTPException (status_code = 500 , detail = "Secure storage failed. Please log in again." ) from exc
126155 raise HTTPException (status_code = 500 , detail = "Server configuration error." ) from exc
127156 except (redis_exceptions .RedisError , OSError ) as exc :
128157 raise HTTPException (status_code = 503 , detail = "Storage temporarily unavailable." ) from exc
@@ -139,27 +168,38 @@ async def create_token(payload: TokenRequest, request: Request) -> TokenResponse
139168
140169
141170async def get_stremio_user_data (payload : TokenRequest ) -> tuple [str , str ]:
142- auth_key = payload .authKey .strip () if payload .authKey else None
143-
144- if not auth_key :
145- raise HTTPException (status_code = 400 , detail = "Auth Key required." )
146-
147- if auth_key .startswith ('"' ) and auth_key .endswith ('"' ):
148- auth_key = auth_key [1 :- 1 ].strip ()
149-
150- stremio_service = StremioService (auth_key = auth_key )
151- try :
152- user_info = await stremio_service .get_user_info ()
153- user_id = user_info ["user_id" ]
154- email = user_info .get ("email" , "" )
155- return user_id , email
156- except Exception as e :
157- logger .error (f"Stremio identity check failed: { e } " )
158- raise HTTPException (
159- status_code = 400 , detail = "Failed to verify Stremio identity. Your auth key might be invalid or expired."
160- )
161- finally :
162- await stremio_service .close ()
171+ email = (payload .email or "" ).strip () or None
172+ password = (payload .password or "" ).strip () or None
173+ auth_key = (payload .authKey or "" ).strip () or None
174+
175+ if email and password :
176+ svc = StremioService (username = email , password = password )
177+ try :
178+ await svc ._login_for_auth_key ()
179+ user_info = await svc .get_user_info ()
180+ return user_info ["user_id" ], user_info .get ("email" , email )
181+ except Exception as e :
182+ logger .error (f"Stremio identity check failed (email/password): { e } " )
183+ raise HTTPException (status_code = 400 , detail = "Failed to verify Stremio identity with email/password." )
184+ finally :
185+ await svc .close ()
186+ elif auth_key :
187+ if auth_key .startswith ('"' ) and auth_key .endswith ('"' ):
188+ auth_key = auth_key [1 :- 1 ].strip ()
189+ svc = StremioService (auth_key = auth_key )
190+ try :
191+ user_info = await svc .get_user_info ()
192+ return user_info ["user_id" ], user_info .get ("email" , "" )
193+ except Exception as e :
194+ logger .error (f"Stremio identity check failed: { e } " )
195+ raise HTTPException (
196+ status_code = 400 ,
197+ detail = "Failed to verify Stremio identity. Your auth key might be invalid or expired." ,
198+ )
199+ finally :
200+ await svc .close ()
201+ else :
202+ raise HTTPException (status_code = 400 , detail = "Provide email+password or auth key." )
163203
164204
165205@router .post ("/stremio-identity" , status_code = 200 )
@@ -183,7 +223,7 @@ async def check_stremio_identity(payload: TokenRequest):
183223
184224
185225@router .delete ("/" , status_code = 200 )
186- async def delete_token (payload : TokenRequest ):
226+ async def delete_redis_token (payload : TokenRequest ):
187227 """Delete a token based on Stremio auth key."""
188228 try :
189229 user_id , _ = await get_stremio_user_data (payload )
0 commit comments