diff --git a/app/__init__.py b/app/__init__.py index f95c67b..b1e3889 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -10,16 +10,60 @@ load_dotenv() # Initialize SQLAlchemy db = SQLAlchemy() +# Get or generate a stable SECRET_KEY +def get_secret_key(): + # Check for an existing key file + key_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../instance/secret_key') + + # If running in production with Docker, store in a more appropriate location + if os.environ.get('FLASK_ENV') == 'production': + key_file = '/data/secret_key' + + try: + # Try to read existing key + if os.path.exists(key_file): + with open(key_file, 'rb') as f: + key = f.read() + if key: + return key + except: + pass + + # Generate a new key + key = secrets.token_hex(32).encode() + + # Try to save it for future use + try: + os.makedirs(os.path.dirname(key_file), exist_ok=True) + with open(key_file, 'wb') as f: + f.write(key) + except: + # If we can't save it, just use it for this session + pass + + return key + def create_app(): app = Flask(__name__, instance_relative_config=True) + # Determine if we're in production + is_production = os.environ.get('FLASK_ENV') == 'production' + # Configure the app app.config.from_mapping( - SECRET_KEY=os.environ.get('SECRET_KEY', secrets.token_hex(16)), + SECRET_KEY=os.environ.get('SECRET_KEY', get_secret_key()), SQLALCHEMY_DATABASE_URI=f"sqlite:///{os.path.join(app.instance_path, 'game_tracker.sqlite')}", SQLALCHEMY_TRACK_MODIFICATIONS=False, + # Session configuration for secure cookies + SESSION_COOKIE_SECURE=is_production, # Only send cookies over HTTPS in production + SESSION_COOKIE_HTTPONLY=True, # Prevent JavaScript access to cookies + SESSION_COOKIE_SAMESITE='Lax', # Prevent CSRF attacks + PERMANENT_SESSION_LIFETIME=86400, # 24 hours ) + # If behind a proxy (like Nginx), trust the proxy headers + app.config['PREFERRED_URL_SCHEME'] = 'https' if is_production else 'http' + # Ensure the instance folder exists try: os.makedirs(app.instance_path) @@ -33,6 +77,14 @@ def create_app(): from .routes import main_bp app.register_blueprint(main_bp) + # Set up session persistence for production + if is_production: + from flask_session import Session + app.config['SESSION_TYPE'] = 'filesystem' + app.config['SESSION_FILE_DIR'] = '/data/flask_session' + app.config['SESSION_PERMANENT'] = True + Session(app) + # Create database tables with app.app_context(): db.create_all() diff --git a/docker-compose.yml b/docker-compose.yml index 9c33d92..24fb2f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,12 @@ services: - "8000" volumes: - ./instance:/app/instance + - ./data/flask_session:/data/flask_session + - ./data/secrets:/data env_file: - .env + environment: + - FLASK_ENV=production restart: unless-stopped networks: - app-network diff --git a/requirements.txt b/requirements.txt index a63169b..3bcb6d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ Flask==2.3.3 Flask-SQLAlchemy==3.1.1 Flask-WTF==1.2.1 +Flask-Session==0.5.0 requests==2.31.0 python-dotenv==1.0.0 +gunicorn==21.2.0