Fix: User login on production.

This commit is contained in:
2024-11-15 15:55:12 +00:00
parent 569508b408
commit 21e7154672
9 changed files with 115 additions and 53 deletions

6
app.py
View File

@@ -96,10 +96,12 @@ def internal_server_error(error):
app.logger.error('Traceback: %s', traceback.format_exc())
return "Internal Server Error", 500
@app.before_request
def make_session_permanent():
session.permanent = True
"""
csrf = CSRFProtect()
"""
cors = CORS()
db = SQLAlchemy()
mail = Mail()

View File

@@ -32,6 +32,15 @@ class Config:
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')
SQLALCHEMY_TRACK_MODIFICATIONS = False
# Auth0
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
# SESSION_COOKIE_SAMESITE = 'Lax'
# PERMANENT_SESSION_LIFETIME = 3600
WTF_CSRF_ENABLED = True
# WTF_CSRF_CHECK_DEFAULT = False # We'll check it manually for API routes
# WTF_CSRF_HEADERS = ['X-CSRFToken'] # Accept CSRF token from this header
WTF_CSRF_TIME_LIMIT = None
WTF_CSRF_SSL_STRICT = False # Allows testing without HTTPS
ID_AUTH0_CLIENT = os.getenv('ID_AUTH0_CLIENT')
ID_AUTH0_CLIENT_SECRET = os.getenv('ID_AUTH0_CLIENT_SECRET')
DOMAIN_AUTH0 = os.getenv('DOMAIN_AUTH0')
@@ -71,9 +80,10 @@ class Config:
class DevelopmentConfig(Config):
is_development = True
# Add development-specific configuration variables
DEBUG = True
MAIL_DEBUG = True
# Add development-specific configuration variables
SESSION_COOKIE_SECURE = False
class ProductionConfig(Config):
is_production = True
@@ -82,8 +92,9 @@ class ProductionConfig(Config):
# Set the configuration class based on the environment
# You can change 'development' to 'production' when deploying
config_env = os.getenv('FLASK_ENV', "production")
config_env = os.getenv('FLASK_ENV', "development")
with open('app.log', 'a') as f:
print(f'config_env: {config_env}')
f.write(f'config_env: {config_env}\n')
# current_app.logger.error(f'config_env: {config_env}') # logger not yet initialised
if config_env == 'development':

View File

@@ -21,24 +21,59 @@ import lib.argument_validation as av
# external
from flask import Flask, render_template, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session, Blueprint, current_app
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import generate_csrf
from werkzeug.exceptions import BadRequest
from extensions import oauth # db,
from urllib.parse import quote_plus, urlencode
from authlib.integrations.flask_client import OAuth
from authlib.integrations.base_client import OAuthError
from urllib.parse import quote, urlparse, parse_qs
from functools import wraps
db = SQLAlchemy()
routes_user = Blueprint('routes_user', __name__)
# User authentication
@routes_user.route("/login", methods=['POST'])
@routes_user.route("/login", methods=['POST', 'OPTIONS'])
def login():
Helper_App.console_log('login')
Helper_App.console_log(f'method={request.method}')
"""
if request.method == 'OPTIONS':
# Handle preflight request
response = current_app.make_default_options_response()
response.headers['Access-Control-Allow-Headers'] = f'Content-Type, {Model_View_Base.FLAG_CSRF_TOKEN}'
response.headers['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
return response
"""
try:
data = request.json
try:
data = request.get_json()
except:
data = {}
except:
data = {}
Helper_App.console_log(f'data={data}')
hash_callback = data.get(Model_View_Base.FLAG_CALLBACK, Model_View_Base.HASH_PAGE_HOME)
Helper_App.console_log(f'hash_callback: {hash_callback}')
"""
# Verify CSRF token manually
Helper_App.console_log(f'request headers={request.headers}')
token = request.headers.get(Model_View_Base.FLAG_CSRF_TOKEN)
Helper_App.console_log(f'token={token}')
Helper_App.console_log(f'session={session}')
Helper_App.console_log(f'session token={session.get('csrf_token')}')
if not token or token != session.get('csrf_token'):
token = data.get(Model_View_Base.FLAG_CSRF_TOKEN, None)
Helper_App.console_log(f'token={token}')
if not token or token != session.get('csrf_token'):
raise BadRequest('Invalid or missing CSRF token')
"""
# OAuth login
try:
# callback_login = F'{Model_View_Base.HASH_CALLBACK_LOGIN}{data.get(Model_View_Base.FLAG_CALLBACK, Model_View_Base.HASH_PAGE_HOME)}'
# encoded_path = quote(data.get(Model_View_Base.FLAG_CALLBACK, Model_View_Base.HASH_PAGE_HOME))
@@ -46,8 +81,8 @@ def login():
# uri_redirect = f'{current_app.URL_HOST}/login_callback?subpath={data.get(Model_View_Base.FLAG_CALLBACK, Model_View_Base.HASH_PAGE_HOME)}'
Helper_App.console_log(f'redirect uri: {uri_redirect}')
hash_callback = data.get(Model_View_Base.FLAG_CALLBACK, Model_View_Base.HASH_PAGE_HOME)
Helper_App.console_log(f'hash_callback: {hash_callback}')
Helper_App.console_log(f'Before red')
red = oauth.auth0.authorize_redirect(
redirect_uri = uri_redirect,
@@ -68,6 +103,11 @@ def login():
""")
return jsonify({'Success': True, Model_View_Base.FLAG_STATUS: Model_View_Base.FLAG_SUCCESS, f'{Model_View_Base.FLAG_CALLBACK}': headers})
return jsonify({'status': 'success', 'redirect': callback})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
@routes_user.route("/login_callback") # <path:subpath>/<code>
def login_callback():
try:

View File

@@ -71,6 +71,7 @@ class Model_View_Base(BaseModel, ABC):
FLAG_CONTAINER_ICON_AND_LABEL: ClassVar[str] = 'container-icon-label'
FLAG_CONTAINER_INPUT: ClassVar[str] = FLAG_CONTAINER + '-input'
FLAG_COUNTY: ClassVar[str] = Base.FLAG_COUNTY
FLAG_CSRF_TOKEN: ClassVar[str] = 'X-CSRFToken'
FLAG_CURRENCY: ClassVar[str] = 'currency'
FLAG_DATA: ClassVar[str] = 'data'
FLAG_DATE_FROM: ClassVar[str] = Base.FLAG_DATE_FROM

File diff suppressed because one or more lines are too long

View File

@@ -4,21 +4,25 @@ import DOM from './dom.js';
export default class API {
static getCsrfToken() {
// return document.querySelectorAll('meta[name=' + nameCSRFToken + ']').getAttribute('content');
return document.querySelector(idCSRFToken).getAttribute('content');
}
static async request(hashEndpoint, method = 'GET', data = null, params = null) {
const url = API.getUrlFromHash(hashEndpoint, params);
const csrfToken = API.getCsrfToken();
const options = {
method,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': API.getCsrfToken()
[flagCsrfToken]: csrfToken,
}
};
if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
data = {
...data,
[flagCsrfToken]: csrfToken,
};
options.body = JSON.stringify(data);
}

View File

@@ -33,7 +33,7 @@
}
</script>
<meta name="yandex-verification" content="4693a824cfda082a" />
<meta id="{{ model.ID_CSRF_TOKEN }}" name="{{ model.NAME_CSRF_TOKEN }}" content="{{ csrf_token() }}" />
<meta id="{{ model.ID_CSRF_TOKEN }}" name="{{ model.FLAG_CSRF_TOKEN }}" content="{{ csrf_token() }}" />
<!-- Scripts
<script src="{{ url_for('static', filename='js/lib/common.js') }}"></script>
@@ -98,6 +98,7 @@
var flagContainer = "{{ model.FLAG_CONTAINER }}";
var flagContainerInput = "{{ model.FLAG_CONTAINER_INPUT }}";
var flagCounty = "{{ model.FLAG_COUNTY }}";
var flagCsrfToken = "{{ model.FLAG_CSRF_TOKEN }}";
var flagCurrency = "{{ model.FLAG_CURRENCY }}";
var flagDelete = "{{ model.FLAG_DELETE }}";
var flagDescription = "{{ model.FLAG_DESCRIPTION }}";
@@ -233,7 +234,6 @@
var idTableMain = "#{{ model.ID_TABLE_MAIN }}";
var idTextareaConfirm = "#{{ model.ID_TEXTAREA_CONFIRM }}";
var isUserLoggedIn = "{{ model.output_bool(model.IS_USER_LOGGED_IN) }}";
var nameCSRFToken = "{{ model.NAME_CSRF_TOKEN }}";
var _pathHost = "{{ model.get_url_host() }}";
var _rowBlank = null;
var titlePageCurrent = "{{ model.title }}";