diff --git a/app/__init__.py b/app/__init__.py index 9bd918e..ba3f0d3 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,15 +1,22 @@ import os from flask import Flask, redirect, url_for, request, g, session, flash -from .blueprints.auth.routes import auth_bp -from .blueprints.main.routes import main_bp -from .blueprints.public.routes import public_bp + +# import blueprints +from app.blueprints.auth import auth_bp +from app.blueprints.main import main_bp +from app.blueprints.char import char_bp +from app.blueprints.public import public_bp +from app.blueprints.ajax import ajax_bp # load_dotenv() from .utils.settings import get_settings +from app.utils.logging import get_logger from .utils.session_user import SessionUser +logger = get_logger() settings = get_settings() + def create_app(): app = Flask(__name__, template_folder="templates") app.config.update( @@ -25,7 +32,9 @@ def create_app(): # Blueprints app.register_blueprint(auth_bp) app.register_blueprint(main_bp) + app.register_blueprint(char_bp) app.register_blueprint(public_bp) + app.register_blueprint(ajax_bp) @app.before_request def require_login(): @@ -68,7 +77,6 @@ def create_app(): @app.before_request def load_user(): user_data = session.get("user") - print(user_data) if user_data: g.current_user = SessionUser( diff --git a/app/blueprints/auth/routes.py b/app/blueprints/auth/routes.py deleted file mode 100644 index 131e0a2..0000000 --- a/app/blueprints/auth/routes.py +++ /dev/null @@ -1,113 +0,0 @@ -# app/auth/routes.py -from flask import Blueprint, render_template, request, redirect, url_for, flash, session,make_response -from flask_login import login_user, logout_user, login_required - -from app.services.appwrite_client import AppWriteClient - - -auth_bp = Blueprint("auth", __name__, url_prefix="/auth") - - -@auth_bp.route("/register", methods=["GET", "POST"]) -def register(): - if request.method == "POST": - email = request.form.get("email", "").strip() - password = request.form.get("password", "") - name = (request.form.get("name") or "").strip() or None - - if not email or not password: - flash("Email and password are required.", "error") - return redirect(url_for("auth.register")) - - try: - aw = AppWriteClient() - aw.create_new_user(email,password,name) - - login_valid, error = aw.log_user_in(email,password) - if login_valid: - flash("Account created and you are now logged in.", "success") - return redirect(url_for("main.dashboard")) - else: - flash(str(error), "error") - except Exception as e: - flash(str(e), "error") - return redirect(url_for("auth.register")) - - return render_template("auth/register.html") - -@auth_bp.route("/login", methods=["GET", "POST"]) -def login(): - if request.method == "POST": - email = request.form.get("email", "").strip() - password = request.form.get("password", "") - if not email or not password: - flash("Email and password are required.", "error") - return redirect(url_for("auth.login")) - - aw = AppWriteClient() - login_valid, error = aw.log_user_in(email,password) - try: - if login_valid: - username = session.get("user",{}).get("name","User") - flash(f"Welcome Back {username}", "success") - return redirect(url_for("main.dashboard")) - else: - flash(str(error), "error") - except Exception as e: - flash(str(e), "error") - return redirect(url_for("auth.login")) - - # Get method - - return render_template("auth/login.html") - -@auth_bp.route("/logout", methods=["GET", "POST"]) -def logout(): - aw = AppWriteClient() - aw.log_user_out() - session.clear() - flash("Signed out.", "success") - return redirect(url_for("auth.login")) - -@auth_bp.route("/send", methods=["POST"]) -def send(): - """ - Sends a verification email to the currently logged-in user. - Appwrite will redirect the user back to /verify/callback with userId & secret. - """ - try: - aw = AppWriteClient() - aw.send_email_verification() - flash("Verification email sent. Please check your inbox.", "info") - except Exception as e: - flash(f"Could not send verification email: {e}", "error") - # Go back to where the user came from, or your dashboard - return redirect(request.referrer or url_for("main.dashboard")) - -@auth_bp.route("/callback", methods=["GET"]) -def callback(): - """ - Completes verification after user clicks the email link. - Requires the user to be logged in (Appwrite Account endpoints need a session). - If not logged in, we stash the link params and send them to log in first. - """ - aw = AppWriteClient() - user_id = request.args.get("userId") - secret = request.args.get("secret") - if not user_id or not secret: - flash("Invalid verification link.", "error") - return redirect(url_for("auth.login")) - - try: - # If we don't currently have an Appwrite session, ask them to log in, then resume. - if not aw.verify_email(user_id,secret): - session["pending_verification"] = {"userId": user_id, "secret": secret} - flash("Please log in to complete email verification.", "warning") - return redirect(url_for("auth.login")) - - # We have a session; complete verification - flash("Email verified! You're all set.", "success") - return redirect(url_for("main.dashboard")) - except Exception as e: - flash(f"Verification failed: {e}", "error") - return redirect(url_for("auth.login")) \ No newline at end of file diff --git a/app/blueprints/main/routes.py b/app/blueprints/main/routes.py deleted file mode 100644 index 98809a1..0000000 --- a/app/blueprints/main/routes.py +++ /dev/null @@ -1,10 +0,0 @@ -from flask import Blueprint, redirect, url_for, render_template, session,flash -from app.services.appwrite_client import AppWriteClient - -aw = AppWriteClient() -main_bp = Blueprint("main", __name__, url_prefix="/main") - - -@main_bp.route("/dashboard") -def dashboard(): - return render_template("main/dashboard.html", profile="", jwt_info="") diff --git a/app/blueprints/public/routes.py b/app/blueprints/public/routes.py deleted file mode 100644 index 1cb9689..0000000 --- a/app/blueprints/public/routes.py +++ /dev/null @@ -1,10 +0,0 @@ -from flask import Blueprint, redirect, url_for, render_template, session,flash -from app.services.appwrite_client import AppWriteClient - - -public_bp = Blueprint("public", __name__, url_prefix="/") - - -@public_bp.route("/") -def home(): - return render_template("public/home.html") \ No newline at end of file diff --git a/app/services/appwrite_client.py b/app/services/appwrite_client.py index cf14fe5..18e30a0 100644 --- a/app/services/appwrite_client.py +++ b/app/services/appwrite_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os from typing import Optional, Dict, Any, Mapping, Union, List -from flask import session, redirect, url_for +from flask import session, redirect, url_for, request from appwrite.client import Client from appwrite.services.account import Account from appwrite.id import ID @@ -60,6 +60,7 @@ class AppWriteClient: client = (Client() .set_endpoint(ENDPOINT) .set_project(PROJECT_ID) + .set_forwarded_user_agent(request.headers.get('user-agent')) ) if session[self.session_key] is not None: @@ -82,6 +83,25 @@ class AppWriteClient: user = user_account.get() session['user']=user + def get_user_from_jwt_token(self, jwt_token:str): + try: + client = (Client() + .set_endpoint(ENDPOINT) + .set_project(PROJECT_ID) + .set_jwt(jwt_token) + ) + return Account(client).get() + except Exception as e: + return {} + + def mint_jwt(self): + try: + client = self._get_user_client() + account = Account(client) + return account.create_jwt() + except Exception as e: + return "" + def log_user_in(self, email:str,password:str): admin_client = self._get_admin_client() try: diff --git a/app/templates/bases/login_base.html b/app/templates/bases/login_base.html index 617642c..25ebc9a 100644 --- a/app/templates/bases/login_base.html +++ b/app/templates/bases/login_base.html @@ -29,11 +29,10 @@

v 0.1.0

- - + {% include "_flash_sticky.html" %} {% block scripts %}{% endblock %} diff --git a/app/templates/bases/main_base.html b/app/templates/bases/main_base.html index 1c36c41..a842609 100644 --- a/app/templates/bases/main_base.html +++ b/app/templates/bases/main_base.html @@ -59,7 +59,7 @@ - + {% include "_flash_sticky.html" %} {% block scripts %}{% endblock %} diff --git a/app/templates/main/dashboard.html b/app/templates/main/dashboard.html index db6ddc4..cef2e87 100644 --- a/app/templates/main/dashboard.html +++ b/app/templates/main/dashboard.html @@ -10,10 +10,12 @@

Code of Conquest Dashboard

-

- logo +

+ {{jwt_info}} + {{profile}} +

-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/app/templates/public/home.html b/app/templates/public/home.html index be2e7f8..7396486 100644 --- a/app/templates/public/home.html +++ b/app/templates/public/home.html @@ -5,8 +5,9 @@
Welcome to Code of Conquest
- + logo

+ In the world of Code of Conquest, the line between hero and villain blurs as you embark on a legendary adventure. This immersive game drops you into a realm of wonder and danger, where every decision, every action, and every roll of the dice determines the fate of your character.

diff --git a/app/utils/logging.py b/app/utils/logging.py index e1ea7b0..b44a9cd 100644 --- a/app/utils/logging.py +++ b/app/utils/logging.py @@ -85,7 +85,7 @@ def configure_logging(settings=None) -> None: return if settings is None: - from app.core.utils.settings import get_settings # lazy import + from app.utils.settings import get_settings # lazy import settings = get_settings() env = settings.env.value diff --git a/app/utils/tokens.py b/app/utils/tokens.py deleted file mode 100644 index ab1d507..0000000 --- a/app/utils/tokens.py +++ /dev/null @@ -1,27 +0,0 @@ -import time -from flask import session -from ..services.appwrite_client import AppwriteAccountClient - -def ensure_fresh_appwrite_jwt(skew_seconds: int = 120) -> str: - """ - Returns a valid Appwrite JWT, refreshing it if it's missing or expiring soon. - Relies on the saved Appwrite session cookie in Flask's session. - """ - jwt_info = session.get("appwrite_jwt") - now = int(time.time()) - - if jwt_info and isinstance(jwt_info, dict): - exp = int(jwt_info.get("expire", 0)) - # If token still safely valid, reuse it - if exp - now > skew_seconds and "jwt" in jwt_info: - return jwt_info["jwt"] - - # Need to mint a new JWT using the user's Appwrite session cookie - cookies = session.get("appwrite_cookies") - if not cookies: - raise RuntimeError("Missing Appwrite session; user must sign in again.") - - aw = AppwriteAccountClient(cookies=cookies) - new_jwt = aw.create_jwt() # -> {"jwt": "...", "expire": } - session["appwrite_jwt"] = new_jwt - return new_jwt["jwt"] \ No newline at end of file diff --git a/docs/arch.md b/docs/arch.md index 97f2616..3508d26 100644 --- a/docs/arch.md +++ b/docs/arch.md @@ -22,18 +22,6 @@ │ (auth, game OLTP + semantic vectors) │ └─────────────────────────────────────────────┘ ``` -## Front end / Back end auth ---- -``` -[Frontend] ── login(email,pass) ─▶ [API Gateway] ─▶ [Auth Service] - │ │ - │ <── Set-Cookie: access_token=JWT ───┘ - │ - ├─▶ call /game/start (cookie auto-attached) - │ - └─▶ logout → clear cookie -``` - --- ## Services & responsibilities diff --git a/requirements.txt b/requirements.txt index fe74440..fd078d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ +appwrite==13.5.0 blinker==1.9.0 certifi==2025.10.5 charset-normalizer==3.4.4 click==8.3.0 Flask==3.1.2 -Flask-Login==0.6.3 idna==3.11 itsdangerous==2.2.0 Jinja2==3.1.6