Fix: User login on production.
This commit is contained in:
6
app.py
6
app.py
@@ -96,10 +96,12 @@ def internal_server_error(error):
|
|||||||
app.logger.error('Traceback: %s', traceback.format_exc())
|
app.logger.error('Traceback: %s', traceback.format_exc())
|
||||||
return "Internal Server Error", 500
|
return "Internal Server Error", 500
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def make_session_permanent():
|
||||||
|
session.permanent = True
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
csrf = CSRFProtect()
|
csrf = CSRFProtect()
|
||||||
|
"""
|
||||||
cors = CORS()
|
cors = CORS()
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
mail = Mail()
|
mail = Mail()
|
||||||
|
|||||||
15
config.py
15
config.py
@@ -32,6 +32,15 @@ class Config:
|
|||||||
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')
|
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
# Auth0
|
# 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 = os.getenv('ID_AUTH0_CLIENT')
|
||||||
ID_AUTH0_CLIENT_SECRET = os.getenv('ID_AUTH0_CLIENT_SECRET')
|
ID_AUTH0_CLIENT_SECRET = os.getenv('ID_AUTH0_CLIENT_SECRET')
|
||||||
DOMAIN_AUTH0 = os.getenv('DOMAIN_AUTH0')
|
DOMAIN_AUTH0 = os.getenv('DOMAIN_AUTH0')
|
||||||
@@ -71,9 +80,10 @@ class Config:
|
|||||||
|
|
||||||
class DevelopmentConfig(Config):
|
class DevelopmentConfig(Config):
|
||||||
is_development = True
|
is_development = True
|
||||||
|
# Add development-specific configuration variables
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
MAIL_DEBUG = True
|
MAIL_DEBUG = True
|
||||||
# Add development-specific configuration variables
|
SESSION_COOKIE_SECURE = False
|
||||||
|
|
||||||
class ProductionConfig(Config):
|
class ProductionConfig(Config):
|
||||||
is_production = True
|
is_production = True
|
||||||
@@ -82,8 +92,9 @@ class ProductionConfig(Config):
|
|||||||
|
|
||||||
# Set the configuration class based on the environment
|
# Set the configuration class based on the environment
|
||||||
# You can change 'development' to 'production' when deploying
|
# 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:
|
with open('app.log', 'a') as f:
|
||||||
|
print(f'config_env: {config_env}')
|
||||||
f.write(f'config_env: {config_env}\n')
|
f.write(f'config_env: {config_env}\n')
|
||||||
# current_app.logger.error(f'config_env: {config_env}') # logger not yet initialised
|
# current_app.logger.error(f'config_env: {config_env}') # logger not yet initialised
|
||||||
if config_env == 'development':
|
if config_env == 'development':
|
||||||
|
|||||||
Binary file not shown.
@@ -21,52 +21,92 @@ import lib.argument_validation as av
|
|||||||
# external
|
# external
|
||||||
from flask import Flask, render_template, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session, Blueprint, current_app
|
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_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_wtf.csrf import generate_csrf
|
||||||
|
from werkzeug.exceptions import BadRequest
|
||||||
from extensions import oauth # db,
|
from extensions import oauth # db,
|
||||||
from urllib.parse import quote_plus, urlencode
|
from urllib.parse import quote_plus, urlencode
|
||||||
from authlib.integrations.flask_client import OAuth
|
from authlib.integrations.flask_client import OAuth
|
||||||
from authlib.integrations.base_client import OAuthError
|
from authlib.integrations.base_client import OAuthError
|
||||||
from urllib.parse import quote, urlparse, parse_qs
|
from urllib.parse import quote, urlparse, parse_qs
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
|
|
||||||
routes_user = Blueprint('routes_user', __name__)
|
routes_user = Blueprint('routes_user', __name__)
|
||||||
|
|
||||||
# User authentication
|
# User authentication
|
||||||
@routes_user.route("/login", methods=['POST'])
|
@routes_user.route("/login", methods=['POST', 'OPTIONS'])
|
||||||
def login():
|
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:
|
try:
|
||||||
data = request.json
|
data = request.json
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
except:
|
||||||
|
data = {}
|
||||||
except:
|
except:
|
||||||
data = {}
|
data = {}
|
||||||
Helper_App.console_log(f'data={data}')
|
Helper_App.console_log(f'data={data}')
|
||||||
# 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))
|
|
||||||
uri_redirect = url_for('routes_user.login_callback', _external=True) # , subpath=encoded_path
|
|
||||||
|
|
||||||
# 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)
|
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'hash_callback: {hash_callback}')
|
||||||
|
|
||||||
red = oauth.auth0.authorize_redirect(
|
"""
|
||||||
redirect_uri = uri_redirect,
|
# Verify CSRF token manually
|
||||||
state = quote(hash_callback)
|
Helper_App.console_log(f'request headers={request.headers}')
|
||||||
)
|
token = request.headers.get(Model_View_Base.FLAG_CSRF_TOKEN)
|
||||||
Helper_App.console_log(f'redirect: {red}')
|
Helper_App.console_log(f'token={token}')
|
||||||
headers = red.headers['Location']
|
Helper_App.console_log(f'session={session}')
|
||||||
Helper_App.console_log(f'headers: {headers}')
|
Helper_App.console_log(f'session token={session.get('csrf_token')}')
|
||||||
parsed_url = urlparse(headers)
|
if not token or token != session.get('csrf_token'):
|
||||||
query_params = parse_qs(parsed_url.query)
|
token = data.get(Model_View_Base.FLAG_CSRF_TOKEN, None)
|
||||||
Helper_App.console_log(f"""
|
Helper_App.console_log(f'token={token}')
|
||||||
OAuth Authorize Redirect URL:
|
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))
|
||||||
|
uri_redirect = url_for('routes_user.login_callback', _external=True) # , subpath=encoded_path
|
||||||
|
|
||||||
|
# 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}')
|
||||||
|
|
||||||
|
Helper_App.console_log(f'Before red')
|
||||||
|
|
||||||
|
red = oauth.auth0.authorize_redirect(
|
||||||
|
redirect_uri = uri_redirect,
|
||||||
|
state = quote(hash_callback)
|
||||||
|
)
|
||||||
|
Helper_App.console_log(f'redirect: {red}')
|
||||||
|
headers = red.headers['Location']
|
||||||
|
Helper_App.console_log(f'headers: {headers}')
|
||||||
|
parsed_url = urlparse(headers)
|
||||||
|
query_params = parse_qs(parsed_url.query)
|
||||||
|
Helper_App.console_log(f"""
|
||||||
|
OAuth Authorize Redirect URL:
|
||||||
|
|
||||||
|
Base URL: {parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}
|
||||||
|
{parsed_url}
|
||||||
|
|
||||||
|
Query Parameters: {query_params}
|
||||||
|
""")
|
||||||
|
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
|
||||||
|
|
||||||
Base URL: {parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}
|
|
||||||
{parsed_url}
|
|
||||||
|
|
||||||
Query Parameters: {query_params}
|
|
||||||
""")
|
|
||||||
return jsonify({'Success': True, Model_View_Base.FLAG_STATUS: Model_View_Base.FLAG_SUCCESS, f'{Model_View_Base.FLAG_CALLBACK}': headers})
|
|
||||||
|
|
||||||
@routes_user.route("/login_callback") # <path:subpath>/<code>
|
@routes_user.route("/login_callback") # <path:subpath>/<code>
|
||||||
def login_callback():
|
def login_callback():
|
||||||
|
|||||||
Binary file not shown.
@@ -71,6 +71,7 @@ class Model_View_Base(BaseModel, ABC):
|
|||||||
FLAG_CONTAINER_ICON_AND_LABEL: ClassVar[str] = 'container-icon-label'
|
FLAG_CONTAINER_ICON_AND_LABEL: ClassVar[str] = 'container-icon-label'
|
||||||
FLAG_CONTAINER_INPUT: ClassVar[str] = FLAG_CONTAINER + '-input'
|
FLAG_CONTAINER_INPUT: ClassVar[str] = FLAG_CONTAINER + '-input'
|
||||||
FLAG_COUNTY: ClassVar[str] = Base.FLAG_COUNTY
|
FLAG_COUNTY: ClassVar[str] = Base.FLAG_COUNTY
|
||||||
|
FLAG_CSRF_TOKEN: ClassVar[str] = 'X-CSRFToken'
|
||||||
FLAG_CURRENCY: ClassVar[str] = 'currency'
|
FLAG_CURRENCY: ClassVar[str] = 'currency'
|
||||||
FLAG_DATA: ClassVar[str] = 'data'
|
FLAG_DATA: ClassVar[str] = 'data'
|
||||||
FLAG_DATE_FROM: ClassVar[str] = Base.FLAG_DATE_FROM
|
FLAG_DATE_FROM: ClassVar[str] = Base.FLAG_DATE_FROM
|
||||||
|
|||||||
44
static/dist/js/main.bundle.js
vendored
44
static/dist/js/main.bundle.js
vendored
File diff suppressed because one or more lines are too long
@@ -4,21 +4,25 @@ import DOM from './dom.js';
|
|||||||
export default class API {
|
export default class API {
|
||||||
|
|
||||||
static getCsrfToken() {
|
static getCsrfToken() {
|
||||||
// return document.querySelectorAll('meta[name=' + nameCSRFToken + ']').getAttribute('content');
|
|
||||||
return document.querySelector(idCSRFToken).getAttribute('content');
|
return document.querySelector(idCSRFToken).getAttribute('content');
|
||||||
}
|
}
|
||||||
|
|
||||||
static async request(hashEndpoint, method = 'GET', data = null, params = null) {
|
static async request(hashEndpoint, method = 'GET', data = null, params = null) {
|
||||||
const url = API.getUrlFromHash(hashEndpoint, params);
|
const url = API.getUrlFromHash(hashEndpoint, params);
|
||||||
|
const csrfToken = API.getCsrfToken();
|
||||||
const options = {
|
const options = {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRFToken': API.getCsrfToken()
|
[flagCsrfToken]: csrfToken,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
||||||
|
data = {
|
||||||
|
...data,
|
||||||
|
[flagCsrfToken]: csrfToken,
|
||||||
|
};
|
||||||
options.body = JSON.stringify(data);
|
options.body = JSON.stringify(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<meta name="yandex-verification" content="4693a824cfda082a" />
|
<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
|
<!-- Scripts
|
||||||
<script src="{{ url_for('static', filename='js/lib/common.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/lib/common.js') }}"></script>
|
||||||
@@ -98,6 +98,7 @@
|
|||||||
var flagContainer = "{{ model.FLAG_CONTAINER }}";
|
var flagContainer = "{{ model.FLAG_CONTAINER }}";
|
||||||
var flagContainerInput = "{{ model.FLAG_CONTAINER_INPUT }}";
|
var flagContainerInput = "{{ model.FLAG_CONTAINER_INPUT }}";
|
||||||
var flagCounty = "{{ model.FLAG_COUNTY }}";
|
var flagCounty = "{{ model.FLAG_COUNTY }}";
|
||||||
|
var flagCsrfToken = "{{ model.FLAG_CSRF_TOKEN }}";
|
||||||
var flagCurrency = "{{ model.FLAG_CURRENCY }}";
|
var flagCurrency = "{{ model.FLAG_CURRENCY }}";
|
||||||
var flagDelete = "{{ model.FLAG_DELETE }}";
|
var flagDelete = "{{ model.FLAG_DELETE }}";
|
||||||
var flagDescription = "{{ model.FLAG_DESCRIPTION }}";
|
var flagDescription = "{{ model.FLAG_DESCRIPTION }}";
|
||||||
@@ -233,7 +234,6 @@
|
|||||||
var idTableMain = "#{{ model.ID_TABLE_MAIN }}";
|
var idTableMain = "#{{ model.ID_TABLE_MAIN }}";
|
||||||
var idTextareaConfirm = "#{{ model.ID_TEXTAREA_CONFIRM }}";
|
var idTextareaConfirm = "#{{ model.ID_TEXTAREA_CONFIRM }}";
|
||||||
var isUserLoggedIn = "{{ model.output_bool(model.IS_USER_LOGGED_IN) }}";
|
var isUserLoggedIn = "{{ model.output_bool(model.IS_USER_LOGGED_IN) }}";
|
||||||
var nameCSRFToken = "{{ model.NAME_CSRF_TOKEN }}";
|
|
||||||
var _pathHost = "{{ model.get_url_host() }}";
|
var _pathHost = "{{ model.get_url_host() }}";
|
||||||
var _rowBlank = null;
|
var _rowBlank = null;
|
||||||
var titlePageCurrent = "{{ model.title }}";
|
var titlePageCurrent = "{{ model.title }}";
|
||||||
|
|||||||
Reference in New Issue
Block a user