March 17 2026: Mobile app phase 1 - fix error
This commit is contained in:
+11
-2
@@ -3,6 +3,7 @@ from flask_sqlalchemy import SQLAlchemy
|
|||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
|
from flask_wtf.csrf import CSRFProtect
|
||||||
from config import config
|
from config import config
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
@@ -12,6 +13,7 @@ db = SQLAlchemy()
|
|||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
migrate = Migrate()
|
migrate = Migrate()
|
||||||
mail = Mail()
|
mail = Mail()
|
||||||
|
csrf = CSRFProtect() # initialized here; .init_app() called in create_app()
|
||||||
|
|
||||||
|
|
||||||
def create_app(config_name='default'):
|
def create_app(config_name='default'):
|
||||||
@@ -22,6 +24,7 @@ def create_app(config_name='default'):
|
|||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
migrate.init_app(app, db)
|
migrate.init_app(app, db)
|
||||||
mail.init_app(app)
|
mail.init_app(app)
|
||||||
|
csrf.init_app(app) # enables CSRF protection for all web routes
|
||||||
|
|
||||||
login_manager.login_view = 'auth.login'
|
login_manager.login_view = 'auth.login'
|
||||||
login_manager.login_message = 'Please log in to access this page.'
|
login_manager.login_message = 'Please log in to access this page.'
|
||||||
@@ -69,6 +72,8 @@ def create_app(config_name='default'):
|
|||||||
|
|
||||||
# Register csrf_token() as an app-wide Jinja2 global so templates that
|
# Register csrf_token() as an app-wide Jinja2 global so templates that
|
||||||
# render manual forms (no WTForms object) can still inject the CSRF token.
|
# render manual forms (no WTForms object) can still inject the CSRF token.
|
||||||
|
# CSRFProtect (initialized above) is the authoritative guard for web routes;
|
||||||
|
# the /api/v1 blueprint is exempted below via csrf.exempt().
|
||||||
from flask_wtf.csrf import generate_csrf
|
from flask_wtf.csrf import generate_csrf
|
||||||
app.jinja_env.globals['csrf_token'] = generate_csrf
|
app.jinja_env.globals['csrf_token'] = generate_csrf
|
||||||
app.jinja_env.globals['enumerate'] = enumerate
|
app.jinja_env.globals['enumerate'] = enumerate
|
||||||
@@ -131,8 +136,12 @@ def create_app(config_name='default'):
|
|||||||
app.register_blueprint(customers.bp)
|
app.register_blueprint(customers.bp)
|
||||||
app.register_blueprint(scheduled_reports.bp)
|
app.register_blueprint(scheduled_reports.bp)
|
||||||
|
|
||||||
# ── Mobile API ─────────────────────────────────────────────
|
# ── Mobile API (Phase 7) ─────────────────────────────────────────────────
|
||||||
from app.api import register_api
|
# The /api/v1 blueprint uses JWT Bearer tokens — no CSRF cookies needed.
|
||||||
|
# Exempt it from CSRFProtect before registering so POST /api/v1/* routes
|
||||||
|
# are never rejected for a missing CSRF token.
|
||||||
|
from app.api import register_api, api_bp
|
||||||
|
csrf.exempt(api_bp)
|
||||||
register_api(app)
|
register_api(app)
|
||||||
|
|
||||||
# ── Error handler: 413 Request Entity Too Large ───────────────────────
|
# ── Error handler: 413 Request Entity Too Large ───────────────────────
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ GET /api/v1/auth/me
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask import Blueprint, request, g
|
from flask import Blueprint, request, g
|
||||||
from flask_wtf.csrf import csrf_exempt
|
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.models.api_token import RefreshToken, DeviceToken
|
from app.models.api_token import RefreshToken, DeviceToken
|
||||||
@@ -61,7 +59,6 @@ def _user_payload(user: User) -> dict:
|
|||||||
# ── Login ─────────────────────────────────────────────────────────────────────
|
# ── Login ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@bp.route('/auth/login', methods=['POST'])
|
@bp.route('/auth/login', methods=['POST'])
|
||||||
@csrf_exempt
|
|
||||||
def login():
|
def login():
|
||||||
"""
|
"""
|
||||||
Authenticate with username + password.
|
Authenticate with username + password.
|
||||||
@@ -137,7 +134,6 @@ def login():
|
|||||||
# ── Refresh ───────────────────────────────────────────────────────────────────
|
# ── Refresh ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@bp.route('/auth/refresh', methods=['POST'])
|
@bp.route('/auth/refresh', methods=['POST'])
|
||||||
@csrf_exempt
|
|
||||||
def refresh():
|
def refresh():
|
||||||
"""
|
"""
|
||||||
Exchange a valid refresh token for a new access token.
|
Exchange a valid refresh token for a new access token.
|
||||||
@@ -207,7 +203,6 @@ def refresh():
|
|||||||
# ── Logout ────────────────────────────────────────────────────────────────────
|
# ── Logout ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@bp.route('/auth/logout', methods=['POST'])
|
@bp.route('/auth/logout', methods=['POST'])
|
||||||
@csrf_exempt
|
|
||||||
@jwt_required
|
@jwt_required
|
||||||
def logout():
|
def logout():
|
||||||
"""
|
"""
|
||||||
@@ -261,7 +256,6 @@ def me():
|
|||||||
# ── Device token registration ─────────────────────────────────────────────────
|
# ── Device token registration ─────────────────────────────────────────────────
|
||||||
|
|
||||||
@bp.route('/devices/register', methods=['POST'])
|
@bp.route('/devices/register', methods=['POST'])
|
||||||
@csrf_exempt
|
|
||||||
@jwt_required
|
@jwt_required
|
||||||
def register_device():
|
def register_device():
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user