# blueprints/auth/routes.py import os import sys import jwt from flask import jsonify, make_response from jwt.exceptions import DecodeError import requests from datetime import datetime, timedelta parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) utils_dir = os.path.join(parent_dir, "utils") sys.path.append(utils_dir) #imports from flask import render_template, redirect, url_for, session, request, Blueprint from functools import wraps import pandas as pd from urllib.parse import urlsplit, urlunsplit, parse_qs, urlencode from utils.config import Config from utils.sme_ds_utilities import DatabaseUtils from authlib.integrations.flask_client import OAuth # Define the blueprint from . import auth_bp # Import the blueprint from __init__.py # Initialize OAuth with the Flask app oauth = OAuth() oauth.register( name='usm', client_id=Config.USM_CLIENT_ID, client_secret=Config.USM_CLIENT_SECRET, authorize_url=Config.USM_AUTH_URL, authorize_params=None, access_token_url=Config.USM_TOKEN_URL, access_token_params=None, refresh_token_url=None, redirect_uri=Config.USM_REDIRECT_URI, client_kwargs={'scope': Config.USM_SCOPE}, logout_uri=Config.USM_LOGOUT_URL, app_url = Config.USM_APP_URL ) def get_oauth(): return oauth #creating our utils object smeds = DatabaseUtils(Config.DB_SOURCE) @auth_bp.route('/login') def login(): redirect_uri = Config.USM_REDIRECT_URI callback_url = url_for('auth.callback', _external=True) if Config.DB_SOURCE != 'l': # callback_url = 'https://tadas-dev.gdbdatascience.com/auth/callback' callback_url = redirect_uri logout_url = url_for('auth.logout', _external=True) if Config.DB_SOURCE != 'l': #logout_url = 'https://tadas-dev.gdbdatascience.com/auth/logout' logout_url = Config.USM_LOGOUT_URL return oauth.usm.authorize_redirect(redirect_uri) @auth_bp.route('/callback') def callback(): code = request.args.get('code') if not code: return jsonify({'message': 'Authorization code missing'}), 400 options = { 'code': code, 'grant_type': 'authorization_code', 'redirect_uri': Config.USM_REDIRECT_URI, 'client_id': Config.USM_CLIENT_ID, 'client_secret': Config.USM_CLIENT_SECRET } try: response = requests.post(Config.USM_TOKEN_URL, data=options) response.raise_for_status() # Raise an exception for HTTP errors result = response.json() decoded_jwt = jwt.decode(result['id_token'], options={"verify_signature": False}) email = decoded_jwt.get('email') email = email.lower() if not email: return jsonify({'message': 'Email missing from ID token'}), 400 user_query = f"SELECT user_id, is_active FROM tadas_sandbox.tadas_users WHERE email_address = '{email}'" user_df = smeds.query_db(user_query, 'main') if user_df.empty: insert_query = f''' INSERT INTO tadas_sandbox.tadas_users (email_address, is_active, created_at) VALUES ('{email.lower()}', TRUE, NOW()) RETURNING user_id ''' #changing this back to querydb user_id_df = smeds.query_db(insert_query, 'main') user_id = user_id_df.iloc[0]['user_id'] elif user_df.iloc[0]['is_active']: user_id = user_df.iloc[0]['user_id'] print('user is active, here is their user id', user_id) else: return jsonify({'message': 'User is inactive or does not exist'}), 403 #stateless auth done here token = jwt.encode({'data': email, 'exp': datetime.utcnow() + timedelta(hours=1)}, Config.JWT_SECRET, algorithm='HS256') #backup - storing our info in the session as well session['email'] = email session['user_id'] = int(user_id) session['id_token'] = result['id_token'] # Log the visit here (only happens once in callback) log_email(user_id, email) #taking user to intended destination next_url = session.pop('next', Config.USM_APP_URL) print(f"Redirecting to: {next_url}") response = make_response(redirect(next_url)) response.set_cookie('access_token', token) response.set_cookie('user_email', email) response.set_cookie('given_name', decoded_jwt.get('given_name', '')) response.set_cookie('id_token', result['id_token']) response.set_cookie('user_id', str(user_id)) return response except requests.exceptions.RequestException as e: print(f"Error during token exchange: {e}") return jsonify({'message': 'Token exchange failed'}), 500 except DecodeError as e: print(f"Error decoding JWT: {e}") return jsonify({'message': 'Invalid ID token'}), 401 except Exception as e: print(f"Unexpected error: {e}") return jsonify({'message': 'Authentication failed'}), 401 def log_email(user_id, email): source_ip = request.headers.get('X-Forwarded-For', request.remote_addr) user_agent = request.headers.get('User-Agent', None) app_url = Config.USM_APP_URL full_endpoint = f"{app_url}{request.path}" df = pd.DataFrame({ 'email': [email], 'user_id': [user_id], 'source_ip': [source_ip], 'user_agent': [user_agent], 'event_type': [1], # Event type for login 'event_date': [pd.Timestamp.now()], # Current timestamp 'access_token': [session.get('access_token')], 'id_token': [session.get('id_token')], 'endpoint': [full_endpoint] }) smeds.save_to_db(df, 'tadas_sandbox', 'tadas_logs' , 'append', 'main') session['visit_logged'] = True @auth_bp.route('/logout', methods=['POST']) def logout(): id_token = request.form.get('idToken') email = request.form.get('email') event_type = 2 user_id = request.form.get('userId') access_token = request.form.get('accessToken') source_ip = request.headers.get('X-Forwarded-For') or request.remote_addr user_agent = request.headers.get('User-Agent') logout_query = f''' INSERT INTO logs (source_ip, user_agent, event_id, event_date, token, email_address, user_id, access_token) VALUES ('{source_ip}', '{user_agent}', {event_type}, NOW(), '{id_token}', '{email.lower()}', {user_id}, '{access_token}') ''' try: smeds.update_db(logout_query) response = { "status": "ok", "state": Config.USM_STATE, "post_logout_redirect_uri": Config.USM_LOGOUT_URL, "host": Config.USM_HOST } return jsonify(response), 200 except Exception as e: return jsonify({"error": "Logout failed", "message": str(e)}), 500 def session_url_setter(endpoint, previous=False): parsed_url = urlsplit(endpoint) base_url = urlunsplit((parsed_url.scheme, parsed_url.netloc, parsed_url.path, '', '')) query_params = parse_qs(parsed_url.query) for key, values in query_params.items(): query_params[key] = list(set(values)) cleaned_query_params = {} for key, values in query_params.items(): cleaned_values = set() for value in values: cleaned_value = value.split('?')[0] cleaned_values.add(cleaned_value) cleaned_query_params[key] = list(cleaned_values) unique_query_string = urlencode(cleaned_query_params, doseq=True) key = 'previous' if previous else 'next' if Config.DB_SOURCE == 'l': session[key] = base_url else: server_site = Config.USM_APP_URL session[key] = server_site + parsed_url.path if unique_query_string: session[key] += '?' + unique_query_string