All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m23s
- Introduced a dual-token system for user authentication: a short-lived access token and a long-lived rotating refresh token. - Created a new RefreshToken model to manage refresh tokens securely. - Updated auth_api.py to handle login, refresh, and logout processes with the new token system. - Enhanced security measures including token rotation and theft detection. - Updated frontend to handle token refresh on 401 errors and adjusted SSE authentication. - Removed CORS middleware as it's unnecessary behind the nginx proxy. - Added tests to ensure functionality and security of the new token system.
117 lines
3.8 KiB
Python
117 lines
3.8 KiB
Python
import logging
|
|
import sys
|
|
import os
|
|
|
|
from flask import Flask, request, jsonify
|
|
|
|
from api.admin_api import admin_api
|
|
from api.auth_api import auth_api
|
|
from api.child_api import child_api
|
|
from api.child_override_api import child_override_api
|
|
from api.chore_api import chore_api
|
|
from api.chore_schedule_api import chore_schedule_api
|
|
from api.image_api import image_api
|
|
from api.kindness_api import kindness_api
|
|
from api.penalty_api import penalty_api
|
|
from api.reward_api import reward_api
|
|
from api.task_api import task_api
|
|
from api.tracking_api import tracking_api
|
|
from api.user_api import user_api
|
|
from config.version import get_full_version
|
|
|
|
from db.default import initializeImages, createDefaultTasks, createDefaultRewards
|
|
from events.broadcaster import Broadcaster
|
|
from events.sse import sse_response_for_user, send_to_user
|
|
from api.utils import get_current_user_id
|
|
from utils.account_deletion_scheduler import start_deletion_scheduler
|
|
|
|
# Configure logging once at application startup
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
datefmt='%Y-%m-%d %H:%M:%S',
|
|
stream=sys.stdout,
|
|
force=True # Override any existing config
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
app = Flask(__name__)
|
|
#CORS(app, resources={r"/api/*": {"origins": ["http://localhost:3000", "http://localhost:5173"]}})
|
|
#Todo - add prefix to all these routes instead of in each blueprint
|
|
app.register_blueprint(admin_api)
|
|
app.register_blueprint(child_api)
|
|
app.register_blueprint(child_override_api)
|
|
app.register_blueprint(chore_api)
|
|
app.register_blueprint(chore_schedule_api)
|
|
app.register_blueprint(kindness_api)
|
|
app.register_blueprint(penalty_api)
|
|
app.register_blueprint(reward_api)
|
|
app.register_blueprint(task_api)
|
|
app.register_blueprint(image_api)
|
|
app.register_blueprint(auth_api, url_prefix='/auth')
|
|
app.register_blueprint(user_api)
|
|
app.register_blueprint(tracking_api)
|
|
|
|
app.config.update(
|
|
MAIL_SERVER='smtp.gmail.com',
|
|
MAIL_PORT=587,
|
|
MAIL_USE_TLS=True,
|
|
MAIL_USERNAME='ryan.kegel@gmail.com',
|
|
MAIL_PASSWORD='ruyj hxjf nmrz buar',
|
|
MAIL_DEFAULT_SENDER='ryan.kegel@gmail.com',
|
|
FRONTEND_URL=os.environ.get('FRONTEND_URL', 'https://localhost:5173'), # Dynamic via env var, defaults to localhost
|
|
)
|
|
|
|
# Security: require SECRET_KEY and REFRESH_TOKEN_EXPIRY_DAYS from environment
|
|
_secret_key = os.environ.get('SECRET_KEY')
|
|
if not _secret_key:
|
|
raise RuntimeError(
|
|
'SECRET_KEY environment variable is required. '
|
|
'Set it to a random string (e.g. python -c "import secrets; print(secrets.token_urlsafe(64))")')
|
|
app.config['SECRET_KEY'] = _secret_key
|
|
|
|
_refresh_expiry = os.environ.get('REFRESH_TOKEN_EXPIRY_DAYS')
|
|
if not _refresh_expiry:
|
|
raise RuntimeError('REFRESH_TOKEN_EXPIRY_DAYS environment variable is required (e.g. 90).')
|
|
try:
|
|
app.config['REFRESH_TOKEN_EXPIRY_DAYS'] = int(_refresh_expiry)
|
|
except ValueError:
|
|
raise RuntimeError('REFRESH_TOKEN_EXPIRY_DAYS must be an integer.')
|
|
|
|
@app.route("/version")
|
|
def api_version():
|
|
return jsonify({"version": get_full_version()})
|
|
|
|
@app.route("/events")
|
|
def events():
|
|
user_id = get_current_user_id()
|
|
if not user_id:
|
|
return {"error": "Authentication required"}, 401
|
|
|
|
return sse_response_for_user(user_id)
|
|
|
|
|
|
@app.route("/notify/<user_id>")
|
|
def notify_user(user_id):
|
|
# Example trigger
|
|
send_to_user(user_id, {
|
|
"type": "notification",
|
|
"message": f"Hello {user_id}, this is a private message!"
|
|
})
|
|
|
|
return {"status": "sent"}
|
|
|
|
def start_background_threads():
|
|
broadcaster = Broadcaster()
|
|
broadcaster.daemon = True
|
|
broadcaster.start()
|
|
|
|
# TODO: implement users
|
|
initializeImages()
|
|
createDefaultTasks()
|
|
createDefaultRewards()
|
|
start_background_threads()
|
|
start_deletion_scheduler()
|
|
|
|
if __name__ == '__main__':
|
|
app.run(debug=False, host='0.0.0.0', port=5000, threaded=True) |