@@ -20,50 +20,110 @@ def __init__(self, db):
2020 self .encryption_key = self ._get_or_create_encryption_key ()
2121
2222 def _get_or_create_encryption_key (self ) -> bytes :
23- """Get or create encryption key for TOTP secrets. Prioritizes environment variables."""
23+ """Get or create encryption key for TOTP secrets. Prioritizes environment variables, then Redis, then local file ."""
2424 import os
2525 import logging
26+ from cryptography .fernet import Fernet
27+
2628 logger = logging .getLogger (__name__ )
2729
30+ # 1. Try Environment Variables
2831 # Prioritize environment variable for Azure and production stability
29- env_key = os .environ .get ('ENCRYPTION_KEY' )
32+ # Check multiple potential variable names
33+ env_keys = ['TOTP_ENCRYPTION_KEY' , 'TOTP_SECRET' , 'ENCRYPTION_KEY' ]
34+ env_key = None
35+
36+ for var_name in env_keys :
37+ val = os .environ .get (var_name )
38+ if val :
39+ env_key = val
40+ logger .info (f"Found encryption key in environment variable: { var_name } " )
41+ break
42+
3043 if env_key :
3144 try :
3245 # Ensure it's a valid Fernet key
33- from cryptography .fernet import Fernet
3446 Fernet (env_key .encode ())
3547 logger .info ("Using encryption key from environment variable" )
3648 return env_key .encode ()
3749 except Exception as e :
38- logger .error (f"Invalid ENCRYPTION_KEY in environment: { str (e )} " )
50+ logger .error (f"Invalid encryption key in environment variable: { str (e )} " )
51+ # Fall through to other methods if env key is invalid
52+
53+ # 2. Try Redis (for persistence between deploys without env vars)
54+ redis_client = None
55+ try :
56+ from flask import current_app
57+ if current_app :
58+ redis_client = current_app .config .get ('REDIS_CLIENT' )
59+ if redis_client :
60+ try :
61+ stored_key = redis_client .get ('totp_encryption_key' )
62+ if stored_key :
63+ if isinstance (stored_key , bytes ):
64+ stored_key = stored_key .decode ('utf-8' )
65+
66+ Fernet (stored_key .encode ())
67+ logger .info ("Using encryption key from Redis" )
68+ return stored_key .encode ()
69+ except Exception as e :
70+ logger .error (f"Invalid encryption key in Redis: { e } " )
71+ except Exception as e :
72+ logger .warning (f"Failed to check Redis for encryption key: { e } " )
3973
74+ # 3. Try Local File (Legacy/Fallback)
4075 # Use absolute path to ensure key is found regardless of working directory
4176 # Key file should be in the backend root directory (parent of models directory)
4277 current_dir = os .path .dirname (os .path .abspath (__file__ ))
4378 backend_dir = os .path .dirname (current_dir )
4479 key_file = os .path .join (backend_dir , 'totp_encryption.key' )
4580
81+ final_key = None
82+
4683 if os .path .exists (key_file ):
4784 with open (key_file , 'rb' ) as f :
4885 key = f .read ()
4986 # Validate key to ensure it's not corrupt
5087 try :
51- from cryptography .fernet import Fernet
5288 Fernet (key )
53- return key
89+ final_key = key
90+ logger .info (f"Using encryption key from local file: { key_file } " )
5491 except Exception as e :
5592 logger .error (f"Invalid encryption key in { key_file } : { e } " )
56- return key
57- else :
58- from cryptography .fernet import Fernet
59- key = Fernet .generate_key ()
93+
94+ # 4. Generate New Key if nothing found
95+ if not final_key :
96+ final_key = Fernet .generate_key ()
97+ logger .info ("Generated NEW encryption key" )
98+
99+ # Save to file
60100 try :
61101 with open (key_file , 'wb' ) as f :
62- f .write (key )
63- logger .info (f"Generated new encryption key at { key_file } " )
102+ f .write (final_key )
103+ logger .info (f"Saved new encryption key to { key_file } " )
64104 except Exception as e :
65105 logger .error (f"Failed to write encryption key to { key_file } : { e } " )
66- return key
106+
107+ # 5. Persist to Redis if available (to prevent future loss)
108+ if final_key and redis_client :
109+ try :
110+ # Store indefinitely (no TTL) or with very long TTL
111+ redis_client .set ('totp_encryption_key' , final_key .decode ('utf-8' ))
112+ logger .info ("Persisted encryption key to Redis for future deployments" )
113+ except Exception as e :
114+ logger .error (f"Failed to persist encryption key to Redis: { e } " )
115+
116+ # 6. Log the key for the user (as requested)
117+ try :
118+ key_str = final_key .decode ('utf-8' )
119+ logger .warning ("=" * 60 )
120+ logger .warning ("TOTP ENCRYPTION KEY (SAVE THIS TO AZURE ENV VAR 'TOTP_ENCRYPTION_KEY'):" )
121+ logger .warning (f"{ key_str } " )
122+ logger .warning ("=" * 60 )
123+ except :
124+ pass
125+
126+ return final_key
67127
68128 def _encrypt_totp_secret (self , secret : str ) -> str :
69129 """Encrypt TOTP secret before storing."""
0 commit comments