LoveofSportsLLC / NFL

NFL + AI
https://loveoffootball.io/
MIT License
0 stars 0 forks source link

Implement API Authentication and Authorization Protocols (use Auth0): #67

Open zepor opened 1 month ago

zepor commented 1 month ago
    ○ Time: 3 weeks
    ○ Tools Required: OAuth2, JWT, Azure API Management
    ○ Steps:
        1. Configure Auth0 for authentication.
            □ Set up Auth0 tenant and applications.
            □ Define roles and permissions for API access.
        2. Use JWT for token-based authorization.
            □ Ensure tokens are securely managed and verified.
        3. Implement and configure Azure API Management for security.
            □ Set up API gateways and configure rate limiting, throttling policies.
        4. Store Auth0 credentials in GitHub Secrets.
            □ Secrets Needed: AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET
    ○ Documentation:
        § API security configurations and setup guides.
        § Auth0 integration documentation.
    ○ Major Milestone: API authentication and authorization implemented.
    ○ GitHub Issue:

Implement API Authentication and Authorization Protocols

Description: Implement secure authentication and authorization for APIs. Tasks:

codeautopilot[bot] commented 1 month ago

Potential solution

To implement API authentication and authorization protocols using Auth0, JWT, and Azure API Management, we need to configure the necessary settings, integrate Auth0 for authentication, use JWT for token-based authorization, and set up Azure API Management for security. This involves updating several files to ensure secure authentication and authorization for the APIs.

How to implement

Step 1: Configure Auth0 for Authentication

File: backend-container/flaskapp/config.py

Add the necessary configuration settings for Auth0, including AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, and AUTH0_DOMAIN.

import os
from dotenv import load_dotenv
import logging

# Load environment variables from .env file
dotenv_path = os.path.join(os.path.dirname(__file__), '../.env')
load_dotenv(dotenv_path)

class ConfigurationError(Exception):
    pass

# Base Configuration
class Config:
    DEBUG = False
    TESTING = False
    connection_string = os.getenv('MONGODB_URL')
    if not connection_string:
        raise ConfigurationError(
            "MONGODB_URL must be set in .env or environment variables")
    MONGODB_SETTINGS = {
        'db': 'Current_Season',
        'host': connection_string,
        'maxPoolSize': 50,
        'minPoolSize': 5
    }
    LOGGING_LEVEL = logging.ERROR

    # Auth0 Configuration
    AUTH0_CLIENT_ID = os.getenv('AUTH0_CLIENT_ID')
    AUTH0_CLIENT_SECRET = os.getenv('AUTH0_CLIENT_SECRET')
    AUTH0_DOMAIN = os.getenv('AUTH0_DOMAIN')
    if not AUTH0_CLIENT_ID or not AUTH0_CLIENT_SECRET or not AUTH0_DOMAIN:
        raise ConfigurationError(
            "AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, and AUTH0_DOMAIN must be set in .env or environment variables")

class DevelopmentConfig(Config):
    DEBUG = True
    TESTING = True
    LOGGING_LEVEL = logging.DEBUG

class ProductionConfig(Config):
    DEBUG = False
    TESTING = False
    LOGGING_LEVEL = logging.WARNING

Step 2: Enhance Logging for Authentication and Authorization

File: backend-container/src/utils/logandcatchexceptions.py

Add specific logging functions for authentication and authorization events.

from functools import wraps
from src.utils.log import be_logger

def log_and_catch_exceptions(func):
    @wraps(func)
    def func_wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            be_logger.error(f"Exception in {func.__name__}: {e}")
            raise
    return func_wrapper

def log_authentication_event(event, user_info=None):
    if user_info:
        be_logger.info(f"Authentication event: {event} for user: {user_info}")
    else:
        be_logger.info(f"Authentication event: {event}")

def log_authorization_event(event, user_info=None):
    if user_info:
        be_logger.info(f"Authorization event: {event} for user: {user_info}")
    else:
        be_logger.info(f"Authorization event: {event}")

# Example usage in authentication and authorization functions
def authenticate_user(user_credentials):
    log_authentication_event("User login attempt", user_credentials.get('username'))
    # Authentication logic here
    log_authentication_event("User login successful", user_credentials.get('username'))

def authorize_user(user, resource):
    log_authorization_event("Authorization check", user.get('username'))
    # Authorization logic here
    log_authorization_event("Authorization granted", user.get('username'))

Step 3: Enhance Error Handling for Authentication and Authorization

File: backend-container/src/utils/errorhandler.py

Add specific error handlers for authentication and authorization errors.

from flask import jsonify
from werkzeug.exceptions import Unauthorized, Forbidden

@app.errorhandler(Exception)
def handle_exception(e):
    be_logger.exception("An error occurred: %s", e)
    return str(e), 500

@app.errorhandler(Unauthorized)
def handle_unauthorized(e):
    be_logger.warning("Unauthorized access attempt: %s", e)
    response = jsonify({
        "error": "unauthorized",
        "message": "You are not authorized to access this resource. Please check your credentials."
    })
    return response, 401

@app.errorhandler(Forbidden)
def handle_forbidden(e):
    be_logger.warning("Forbidden access attempt: %s", e)
    response = jsonify({
        "error": "forbidden",
        "message": "You do not have permission to access this resource."
    })
    return response, 403

@app.errorhandler(jwt.ExpiredSignatureError)
def handle_expired_token(e):
    be_logger.warning("Expired token: %s", e)
    response = jsonify({
        "error": "token_expired",
        "message": "Your session has expired. Please log in again."
    })
    return response, 401

@app.errorhandler(jwt.InvalidTokenError)
def handle_invalid_token(e):
    be_logger.warning("Invalid token: %s", e)
    response = jsonify({
        "error": "invalid_token",
        "message": "The provided token is invalid. Please log in again."
    })
    return response, 401

Step 4: Implement JWT Token Management

File: backend-container/src/utils/jwt.py

Implement functions to create, verify, and manage JWT tokens securely.

import jwt
import datetime
from typing import Dict, Any

# Constants
SECRET_KEY = 'your_secret_key'  # Replace with your actual secret key
ALGORITHM = 'HS256'

def create_token(data: Dict[str, Any], expires_in: int = 3600) -> str:
    payload = data.copy()
    payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(seconds=expires_in)
    token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
    return token

def verify_token(token: str) -> Dict[str, Any]:
    try:
        decoded_data = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return decoded_data
    except jwt.ExpiredSignatureError:
        raise ValueError("Token has expired")
    except jwt.InvalidTokenError:
        raise ValueError("Invalid token")

def decode_token(token: str) -> Dict[str, Any]:
    decoded_data = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM], options={"verify_exp": False})
    return decoded_data

Step 5: Implement Auth0 Authentication in Flask

File: backend-container/src/utils/auth.py

Implement the required API authentication and authorization protocols using Auth0.

from flask import Blueprint, redirect, url_for, session
from authlib.integrations.flask_client import OAuth
from dotenv import load_dotenv
import os

# Ensure the environment variables are set
if ENV_FILE := find_dotenv():
    load_dotenv(ENV_FILE)

# Blueprint for authentication routes
auth_blueprint = Blueprint('auth_blueprint', __name__)
auth_blueprint.secret_key = os.getenv("APP_SECRET_KEY")

def init_oauth(app):
    global oauth
    oauth = OAuth()
    oauth.init_app(app)
    oauth.register(
        "auth0",
        client_id=os.getenv("AUTH0_CLIENT_ID"),
        client_secret=os.getenv("AUTH0_CLIENT_SECRET"),
        client_kwargs={
            "scope": "openid profile email",
        },
        server_metadata_url=f'https://{os.getenv("AUTH0_DOMAIN")}/.well-known/openid-configuration'
    )

@auth_blueprint.route("/login")
def login():
    return oauth.auth0.authorize_redirect(
        redirect_uri=url_for("auth_blueprint.callback", _external=True)
    )

@auth_blueprint.route("/callback", methods=["GET", "POST"])
def callback():
    token = oauth.auth0.authorize_access_token()
    session["user"] = token
    return redirect("/")

@auth_blueprint.route("/logout")
def logout():
    session.clear()
    return redirect(
        "https://" + os.getenv("AUTH0_DOMAIN")
        + "/v2/logout?"
        + urlencode(
            {
                "returnTo": url_for("home", _external=True),
                "client_id": os.getenv("AUTH0_CLIENT_ID"),
            },
            quote_via=quote_plus,
        )
    )

@auth_blueprint.route("/")
def home():
    return render_template("home.html", session=session.get('user'), pretty=json.dumps(session.get('user'), indent=4))

Integration with Flask App

File: backend-container/flaskapp/app.py

Integrate Auth0 authentication into the Flask app and ensure that routes are protected and tokens are verified.

from flask import Flask, render_template, redirect, url_for, session
from flask_cors import CORS
from authlib.integrations.flask_client import OAuth
from dotenv import load_dotenv
import os
import logging

# Load environment variables
load_dotenv()

# Initialize Flask app
app = Flask(__name__, template_folder='../templates')
app.secret_key = os.getenv('FLASK_SECRET_KEY', 'default_secret_key')
app.config['SESSION_COOKIE_SECURE'] = True
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
app.config['JSON_SORT_KEYS'] = False

# Initialize OAuth
oauth = OAuth(app)
auth0 = oauth.register(
    'auth0',
    client_id=os.getenv('AUTH0_CLIENT_ID'),
    client_secret=os.getenv('AUTH0_CLIENT_SECRET'),
    api_base_url=os.getenv('AUTH0_DOMAIN'),
    access_token_url=os.getenv('AUTH0_DOMAIN') + '/oauth/token',
    authorize_url=os.getenv('AUTH0_DOMAIN') + '/authorize',
    client_kwargs={
        'scope': 'openid profile email',
    },
)

# Enable CORS
CORS(app, resources={r"/api/*": {"origins": ["http://localhost:3000", "https://loveoffootball.io"]}})

# Logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)

# Auth0 routes
@app.route('/login')
def login():
    return auth0.authorize_redirect(redirect_uri=url_for('callback', _external=True))

@app.route('/callback')
def callback():
    auth0.authorize_access_token()
    resp = auth0.get('userinfo')
    userinfo = resp.json()
    session['user'] = userinfo
    return redirect('/')

@app.route('/logout')
def logout():
    session.clear()
    return redirect(auth0.api_base_url + '/v2/logout?returnTo=' + url_for('index', _external=True))

@app.route('/')
def index():
    return render_template('home.html')

@app.route('/image', methods=['GET'])
def image_page():
    return render_template('home.html')

# Protect routes
def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if 'user' not in session:
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return decorated

# Example of a protected route
@app.route('/protected')
@requires_auth
def protected():
    return 'This is a protected route.'

if __name__ == "__main__":
    FLASK_DEBUG = os.getenv('FLASK_DEBUG', 'true').lower() == 'true'
    if FLASK_DEBUG:
        app.run(host='0.0.0.0', port=5000, debug=True)
    else:
        app.run()

Step 6: Update README with Setup Instructions

File: README.md

Update the README.md file with instructions for setting up Auth0, managing JWT tokens, and configuring Azure API Management.

# NFLSTATS Development Environment
This is the NFLSTATS project, designed to provide statistical insights for the NFL.

## THIS APP NEEDS A TON OF WORK 
1. PIPELINES FROM SPORTS RADAR APIS FOR LIVE DATA 
2. OTHER ADVANCED ANALYTICS DATASOURCES FOR ADDITIONAL ANALYSIS FOR AN AI MODEL
3. DATA VISUALIZATION/DISPLAY
4. ADDING OTHER SPORTS
5. MAKING AI MODELS FOR SPECIFIC GAMES AND THEIR CONFIDENCE INTERVALS IN A PARTICULAR BET

This guide will help you set up the environment using GitHub Codespaces or set it up locally using Docker and Docker Compose. Be aware the application is half built right now. It needs quite a bit of TLC. This is my first project, feel free to criticize and make recommendations as you see fit!

## NFLSTATS Development Environment Setup Guide
Welcome to the NFLSTATS project! This guide will walk you through setting up your development environment to run the application locally. Before diving into the setup process, ensure you have the following prerequisites installed on your machine:

### Prerequisites
- Git: For cloning the repository. Download and install from Git's official site.
- Docker: Required for running the application's containers. Download and install Docker from Docker's official site.
- Docker Compose: For managing multi-container Docker applications. Typically included with Docker Desktop installations.
- Node.js: The runtime environment for the application. Download and install from Node.js's official site.
- npm: Comes with Node.js and is used for managing dependencies.
- Visual Studio Code (Optional): Recommended IDE for editing and debugging the application. Download from Visual Studio Code's official site.

### Environment Setup
#### Clone the Repository
First, clone the NFLSTATS repository to your local machine using Git:

```sh
git clone https://github.com/[your_username]/nflstats.git

Replace [your_username] with your GitHub username if you have forked the repository. Otherwise, use the project's main repository URL.

Docker Environment

Ensure Docker and Docker Compose are installed and running on your machine. The application uses Docker containers for both development (docker-compose.debug.yml) and production (docker-compose.prod.yml) environments.

Node.js and npm Packages

After cloning the repository, navigate to the project directory and install the required npm packages:

cd nflstats
npm install

This command installs all dependencies defined in package.json.

Environment Variables

The application requires several environment variables to be set. These variables can be found and modified in the .env files located in the project root or specific directories for different environments (e.g., .env.development, .env.production). Ensure all necessary variables are set before proceeding.

Running the Application for Development

With all prerequisites installed and environment variables set, you can start the development server using npm scripts defined in package.json:

npm run start:dev

This command executes the start:dev script, which builds the application for development and starts the development server. If everything is set up correctly, the application should now be running and accessible.

Accessing the Application

Once the application is running, you can access it by navigating to http://localhost:3000 in your web browser, assuming the default port configuration.

Additional Information

Dockerfiles

If the Dockerfiles are on .dockerignore, ensure to remove them from the ignore list or manually copy them into your project directory. The Dockerfiles are crucial for building the Docker images for both development and production environments.

Scripts

For detailed information on available npm scripts and their purposes, refer to the scripts section in package.json. These scripts provide shortcuts for common tasks such as building the application, starting the development server, and cleaning the build directory.

Contributing

Contributions to the NFLSTATS project are welcome! Please refer to the project's CONTRIBUTING.md file for guidelines on how to contribute.

Auth0 Setup

To implement secure authentication and authorization for the APIs, follow these steps:

  1. Configure Auth0 for Authentication

    • Set up an Auth0 tenant and applications.
    • Define roles and permissions for API access.
  2. Use JWT for Token-Based Authorization

    • Ensure tokens are securely managed and verified.
  3. Implement and Configure Azure API Management for Security

    • Set up API gateways and configure rate limiting, throttling policies.
  4. Store Auth0 Credentials in GitHub Secrets

    • Secrets Needed: AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET

Auth0 Integration

  1. Set Up Auth0 Tenant and Application

    • Go to the Auth0 Dashboard.
    • Create a new tenant and application.
    • Note down the Client ID, Client Secret, and Domain.
  2. Define Roles and Permissions

    • In the Auth0 Dashboard, navigate to the "Roles" section.
    • Create roles and assign permissions as needed.
  3. Configure Environment Variables

    • Add the following environment variables to your .env file:
      AUTH0_CLIENT_ID=your_auth0_client_id
      AUTH0_CLIENT_SECRET=your_auth0_client_secret
      AUTH0_DOMAIN=your_auth0_domain

JWT Token Management

  1. Creating JWT Tokens

    • Use libraries like pyjwt to create JWT tokens.
    • Ensure tokens are signed using a secure algorithm.
  2. Verifying JWT Tokens

    • Implement token verification in your application to ensure only valid tokens are accepted.

Azure API Management Configuration

  1. Set Up API Gateways

    • Go to the Azure Portal.
    • Create a new API Management service.
    • Configure API gateways and import your APIs.
  2. Configure Rate Limiting and Throttling Policies

    • In the Azure API Management service, navigate to the "Policies" section.
    • Set up rate limiting and throttling policies to protect your APIs.

By following this guide, you should now have a fully functional development environment for the NFLSTATS project with secure authentication and authorization implemented. Happy coding!


## Step 7: Integrate Auth0 Authentication into FastAPI
### File: `backend-container/src/nflfeapi/__init__.py`
Integrate Auth0 authentication into the FastAPI application.

```python
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from authlib.integrations.starlette_client import OAuth
import os

app = FastAPI()

oauth = OAuth()
oauth.register(
    name='auth0',
    client_id=os.getenv('AUTH0_CLIENT_ID'),
    client_secret=os.getenv('AUTH0_CLIENT_SECRET'),
    server_metadata_url=f'https://{os.getenv("AUTH0_DOMAIN")}/.well-known/openid-configuration',
    client_kwargs={
        'scope': 'openid profile email',
    },
)

def verify_token(token: str):
    try:
        payload = jwt.decode(token, os.getenv('AUTH0_CLIENT_SECRET'), algorithms=['HS256'], audience=os.getenv('AUTH0_CLIENT_ID'))
        return payload
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str = Depends(oauth2_scheme)):
    return verify_token(token)

@app.get("/protected-route")
async def protected_route(current_user: dict = Depends(get_current_user)):
    return {"message": "This is a protected route", "user": current_user}

Step 8: Implement Authentication Checks in Support API

File: `backend-container

Click here to create a Pull Request with the proposed solution

Files used for this task:

Changes on backend-container/flaskapp/config.py To implement the necessary configuration settings for Auth0 in the `config.py` file, you need to add the client ID, client secret, and domain. These settings will be used by the Flask application to integrate with Auth0 for authentication and authorization. Here's a concrete proposal for updating the `config.py` file: 1. **Load Environment Variables**: Ensure that the environment variables for Auth0 are loaded from a `.env` file or environment variables. 2. **Add Auth0 Configuration**: Add the necessary configuration settings for Auth0, including `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, and `AUTH0_DOMAIN`. Here's the updated `config.py` file: ```python import os from dotenv import load_dotenv import logging # Load environment variables from .env file dotenv_path = os.path.join(os.path.dirname(__file__), '../.env') load_dotenv(dotenv_path) class ConfigurationError(Exception): pass # Base Configuration class Config: DEBUG = False TESTING = False connection_string = os.getenv('MONGODB_URL') if not connection_string: raise ConfigurationError( "MONGODB_URL must be set in .env or environment variables") MONGODB_SETTINGS = { 'db': 'Current_Season', 'host': connection_string, 'maxPoolSize': 50, 'minPoolSize': 5 } LOGGING_LEVEL = logging.ERROR # Auth0 Configuration AUTH0_CLIENT_ID = os.getenv('AUTH0_CLIENT_ID') AUTH0_CLIENT_SECRET = os.getenv('AUTH0_CLIENT_SECRET') AUTH0_DOMAIN = os.getenv('AUTH0_DOMAIN') if not AUTH0_CLIENT_ID or not AUTH0_CLIENT_SECRET or not AUTH0_DOMAIN: raise ConfigurationError( "AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, and AUTH0_DOMAIN must be set in .env or environment variables") class DevelopmentConfig(Config): DEBUG = True TESTING = True LOGGING_LEVEL = logging.DEBUG class ProductionConfig(Config): DEBUG = False TESTING = False LOGGING_LEVEL = logging.WARNING ``` ### Steps to Implement: 1. **Create a `.env` File**: Ensure you have a `.env` file in the root directory with the following content: ``` MONGODB_URL=your_mongodb_connection_string AUTH0_CLIENT_ID=your_auth0_client_id AUTH0_CLIENT_SECRET=your_auth0_client_secret AUTH0_DOMAIN=your_auth0_domain ``` 2. **Load Environment Variables**: The `load_dotenv` function will load these variables into the environment, making them accessible via `os.getenv`. 3. **Add Auth0 Configuration**: The `Config` class now includes the necessary Auth0 configuration settings. If any of these settings are missing, a `ConfigurationError` will be raised. This setup ensures that your Flask application is configured to use Auth0 for authentication and authorization, and it provides a clear error message if any required configuration is missing.
Changes on backend-container/src/utils/logandcatchexceptions.py To address the task for the file `backend-container/src/utils/logandcatchexceptions.py`, we need to enhance the existing logging functionality to include specific logging for authentication and authorization events. This will help in monitoring and debugging issues related to these processes. Here is a concrete proposal to achieve this: 1. **Add Specific Logging Functions**: Create dedicated logging functions for authentication and authorization events. 2. **Integrate Logging in Authentication and Authorization Processes**: Ensure these logging functions are called at appropriate places in the authentication and authorization workflow. ### Updated Code for `logandcatchexceptions.py` ```python from functools import wraps from src.utils.log import be_logger def log_and_catch_exceptions(func): @wraps(func) def func_wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: be_logger.error(f"Exception in {func.__name__}: {e}") raise return func_wrapper def log_authentication_event(event, user_info=None): if user_info: be_logger.info(f"Authentication event: {event} for user: {user_info}") else: be_logger.info(f"Authentication event: {event}") def log_authorization_event(event, user_info=None): if user_info: be_logger.info(f"Authorization event: {event} for user: {user_info}") else: be_logger.info(f"Authorization event: {event}") # Example usage in authentication and authorization functions def authenticate_user(user_credentials): log_authentication_event("User login attempt", user_credentials.get('username')) # Authentication logic here log_authentication_event("User login successful", user_credentials.get('username')) def authorize_user(user, resource): log_authorization_event("Authorization check", user.get('username')) # Authorization logic here log_authorization_event("Authorization granted", user.get('username')) ``` ### Explanation 1. **`log_authentication_event` Function**: This function logs events related to authentication, such as login attempts and successful logins. It optionally takes `user_info` to log details about the user involved in the event. 2. **`log_authorization_event` Function**: This function logs events related to authorization, such as checks and grants. It also optionally takes `user_info` to log details about the user involved in the event. 3. **Example Usage**: The example functions `authenticate_user` and `authorize_user` demonstrate how to use these logging functions within the authentication and authorization processes. ### Next Steps - Integrate these logging functions into the actual authentication and authorization workflows in the relevant files (`auth.py`, `jwt.py`, etc.). - Ensure that all critical points in the authentication and authorization processes are covered by logging to facilitate effective monitoring and debugging. By implementing these changes, we will have a robust logging mechanism for authentication and authorization events, aiding in better monitoring and debugging of these critical processes.
Changes on backend-container/src/utils/errorhandler.py To address the task for the `backend-container/src/utils/errorhandler.py` file, we need to enhance the error handling to specifically manage authentication and authorization errors. This will ensure that users receive meaningful feedback when such errors occur. Here's a concrete plan and the updated code: ### Plan 1. **Identify Authentication and Authorization Errors**: We need to distinguish between general exceptions and those related to authentication and authorization. 2. **Create Custom Error Handlers**: Implement specific handlers for common authentication and authorization errors, such as invalid tokens or unauthorized access. 3. **Return Meaningful Responses**: Ensure that the responses for these errors are clear and informative, providing users with the necessary information to understand and potentially resolve the issue. ### Updated Code ```python from flask import jsonify from werkzeug.exceptions import Unauthorized, Forbidden @app.errorhandler(Exception) def handle_exception(e): be_logger.exception("An error occurred: %s", e) return str(e), 500 @app.errorhandler(Unauthorized) def handle_unauthorized(e): be_logger.warning("Unauthorized access attempt: %s", e) response = jsonify({ "error": "unauthorized", "message": "You are not authorized to access this resource. Please check your credentials." }) return response, 401 @app.errorhandler(Forbidden) def handle_forbidden(e): be_logger.warning("Forbidden access attempt: %s", e) response = jsonify({ "error": "forbidden", "message": "You do not have permission to access this resource." }) return response, 403 @app.errorhandler(jwt.ExpiredSignatureError) def handle_expired_token(e): be_logger.warning("Expired token: %s", e) response = jsonify({ "error": "token_expired", "message": "Your session has expired. Please log in again." }) return response, 401 @app.errorhandler(jwt.InvalidTokenError) def handle_invalid_token(e): be_logger.warning("Invalid token: %s", e) response = jsonify({ "error": "invalid_token", "message": "The provided token is invalid. Please log in again." }) return response, 401 ``` ### Explanation 1. **General Exception Handling**: The existing handler for general exceptions is retained to log and return a generic error message. 2. **Unauthorized Access**: A specific handler for `Unauthorized` exceptions (HTTP 401) is added to log the event and return a JSON response indicating unauthorized access. 3. **Forbidden Access**: A specific handler for `Forbidden` exceptions (HTTP 403) is added to log the event and return a JSON response indicating forbidden access. 4. **Expired Token**: A handler for `jwt.ExpiredSignatureError` is added to manage cases where the JWT token has expired, prompting the user to log in again. 5. **Invalid Token**: A handler for `jwt.InvalidTokenError` is added to manage cases where the JWT token is invalid, prompting the user to log in again. This approach ensures that authentication and authorization errors are handled gracefully, providing users with clear and actionable feedback.
Changes on backend-container/src/utils/jwt.py To implement functions to create, verify, and manage JWT tokens securely in the `backend-container/src/utils/jwt.py` file, follow these steps: 1. **Install Required Libraries**: Ensure you have the `pyjwt` library installed. You can install it using pip: ```sh pip install pyjwt ``` 2. **Import Necessary Modules**: Import the required modules for handling JWT tokens. 3. **Define Constants**: Define constants for the secret key and algorithm used for encoding and decoding JWT tokens. 4. **Implement Functions**: Implement functions to create, verify, and decode JWT tokens. Here is a concrete implementation plan for the `jwt.py` file: ### Step-by-Step Implementation 1. **Install `pyjwt`**: ```sh pip install pyjwt ``` 2. **Create `jwt.py` File**: Implement the following code in the `backend-container/src/utils/jwt.py` file: ```python import jwt import datetime from typing import Dict, Any # Constants SECRET_KEY = 'your_secret_key' # Replace with your actual secret key ALGORITHM = 'HS256' def create_token(data: Dict[str, Any], expires_in: int = 3600) -> str: """ Create a JWT token. :param data: Data to be encoded in the token. :param expires_in: Expiration time in seconds. :return: JWT token as a string. """ payload = data.copy() payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(seconds=expires_in) token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM) return token def verify_token(token: str) -> Dict[str, Any]: """ Verify a JWT token. :param token: JWT token as a string. :return: Decoded data if the token is valid. :raises: jwt.ExpiredSignatureError, jwt.InvalidTokenError """ try: decoded_data = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) return decoded_data except jwt.ExpiredSignatureError: raise ValueError("Token has expired") except jwt.InvalidTokenError: raise ValueError("Invalid token") def decode_token(token: str) -> Dict[str, Any]: """ Decode a JWT token without verification. :param token: JWT token as a string. :return: Decoded data. """ decoded_data = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM], options={"verify_exp": False}) return decoded_data ``` ### Explanation of Functions 1. **`create_token`**: - Takes a dictionary `data` and an optional `expires_in` parameter. - Adds an expiration time to the payload. - Encodes the payload using the secret key and algorithm. - Returns the JWT token as a string. 2. **`verify_token`**: - Takes a JWT token as input. - Tries to decode the token using the secret key and algorithm. - If the token is valid, returns the decoded data. - Raises an error if the token is expired or invalid. 3. **`decode_token`**: - Takes a JWT token as input. - Decodes the token without verifying the expiration. - Returns the decoded data. ### Security Considerations - **Secret Key**: Ensure the `SECRET_KEY` is stored securely and not hard-coded in the source code. Use environment variables or a secrets management service. - **Token Expiration**: Always set an expiration time for tokens to enhance security. ### Next Steps - Integrate these functions into your authentication and authorization workflows in the relevant files. - Ensure proper error handling and logging for authentication events. This implementation will provide a solid foundation for managing JWT tokens securely in your application.
Changes on backend-container/src/utils/auth.py To implement the required API authentication and authorization protocols using Auth0 in the `backend-container/src/utils/auth.py` file, the following steps should be taken: 1. **Initialize OAuth with Auth0**: - The `init_oauth` function is already set up to initialize OAuth with Auth0 using the `authlib` library. Ensure that the `client_id`, `client_secret`, and `domain` are correctly loaded from environment variables. 2. **Handle User Sessions**: - The `callback` function handles the OAuth callback and stores the user token in the session. This is essential for maintaining user sessions after authentication. 3. **Manage Login, Callback, and Logout Processes**: - The `login` function redirects users to the Auth0 login page. - The `callback` function processes the callback from Auth0 and stores the user information in the session. - The `logout` function clears the session and redirects users to the Auth0 logout endpoint. ### Detailed Implementation Plan: 1. **Ensure Environment Variables**: - Make sure the environment variables `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_DOMAIN`, and `APP_SECRET_KEY` are set in your environment or `.env` file. 2. **Initialize OAuth in Your Flask App**: - Call the `init_oauth` function in your Flask app initialization code to set up OAuth with Auth0. 3. **Blueprint Registration**: - Register the `auth_blueprint` with your Flask app to enable the authentication routes. ### Example Code: ```python # Ensure the environment variables are set if ENV_FILE := find_dotenv(): load_dotenv(ENV_FILE) # Blueprint for authentication routes auth_blueprint = Blueprint('auth_blueprint', __name__) auth_blueprint.secret_key = env.get("APP_SECRET_KEY") def init_oauth(app): global oauth oauth = OAuth() oauth.init_app(app) oauth.register( "auth0", client_id=env.get("AUTH0_CLIENT_ID"), client_secret=env.get("AUTH0_CLIENT_SECRET"), client_kwargs={ "scope": "openid profile email", }, server_metadata_url=f'https://{env.get("AUTH0_DOMAIN")}/.well-known/openid-configuration' ) @auth_blueprint.route("/login") def login(): return oauth.auth0.authorize_redirect( redirect_uri=url_for("auth_blueprint.callback", _external=True) ) @auth_blueprint.route("/callback", methods=["GET", "POST"]) def callback(): token = oauth.auth0.authorize_access_token() session["user"] = token return redirect("/") @auth_blueprint.route("/logout") def logout(): session.clear() return redirect( "https://" + env.get("AUTH0_DOMAIN") + "/v2/logout?" + urlencode( { "returnTo": url_for("home", _external=True), "client_id": env.get("AUTH0_CLIENT_ID"), }, quote_via=quote_plus, ) ) @auth_blueprint.route("/") def home(): return render_template("home.html", session=session.get('user'), pretty=json.dumps(session.get('user'), indent=4)) ``` ### Integration with Flask App: In your main Flask application file (e.g., `app.py`), you need to initialize OAuth and register the blueprint: ```python from flask import Flask from src.utils.auth import init_oauth, auth_blueprint app = Flask(__name__) app.secret_key = 'your_secret_key' # Initialize OAuth init_oauth(app) # Register the auth blueprint app.register_blueprint(auth_blueprint) if __name__ == "__main__": app.run(debug=True) ``` ### Summary: - Ensure environment variables are correctly set. - Initialize OAuth with Auth0 in your Flask app. - Register the authentication blueprint to handle login, callback, and logout processes. - Verify that user sessions are managed correctly and tokens are securely stored. This setup will provide a robust authentication and authorization mechanism for your API using Auth0.
Changes on backend-container/flaskapp/app.py To integrate Auth0 authentication into the Flask app and ensure that routes are protected and tokens are verified, follow these steps: 1. **Install Required Libraries**: Ensure you have the necessary libraries installed. You will need `authlib` for OAuth integration and `flask` for the web framework. ```bash pip install authlib flask ``` 2. **Initialize OAuth with Auth0**: Modify the `init_oauth` function in `src/utils/auth.py` to configure Auth0. This function should set up the OAuth client with the necessary Auth0 credentials. 3. **Protect Routes**: Use decorators to protect routes and ensure that only authenticated users can access them. 4. **Handle Login, Callback, and Logout**: Implement the login, callback, and logout routes to handle the authentication flow. Here's how you can modify `backend-container/flaskapp/app.py`: ```python from flask import Flask, render_template, redirect, url_for, session from flask_cors import CORS from authlib.integrations.flask_client import OAuth from dotenv import load_dotenv import os import logging # Load environment variables load_dotenv() # Initialize Flask app app = Flask(__name__, template_folder='../templates') app.secret_key = os.getenv('FLASK_SECRET_KEY', 'default_secret_key') app.config['SESSION_COOKIE_SECURE'] = True app.config['TEMPLATES_AUTO_RELOAD'] = True app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 app.config['JSON_SORT_KEYS'] = False # Initialize OAuth oauth = OAuth(app) auth0 = oauth.register( 'auth0', client_id=os.getenv('AUTH0_CLIENT_ID'), client_secret=os.getenv('AUTH0_CLIENT_SECRET'), api_base_url=os.getenv('AUTH0_DOMAIN'), access_token_url=os.getenv('AUTH0_DOMAIN') + '/oauth/token', authorize_url=os.getenv('AUTH0_DOMAIN') + '/authorize', client_kwargs={ 'scope': 'openid profile email', }, ) # Enable CORS CORS(app, resources={r"/api/*": {"origins": ["http://localhost:3000", "https://loveoffootball.io"]}}) # Logging logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) # Auth0 routes @app.route('/login') def login(): return auth0.authorize_redirect(redirect_uri=url_for('callback', _external=True)) @app.route('/callback') def callback(): auth0.authorize_access_token() resp = auth0.get('userinfo') userinfo = resp.json() session['user'] = userinfo return redirect('/') @app.route('/logout') def logout(): session.clear() return redirect(auth0.api_base_url + '/v2/logout?returnTo=' + url_for('index', _external=True)) @app.route('/') def index(): return render_template('home.html') @app.route('/image', methods=['GET']) def image_page(): return render_template('home.html') # Protect routes def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): if 'user' not in session: return redirect(url_for('login')) return f(*args, **kwargs) return decorated # Example of a protected route @app.route('/protected') @requires_auth def protected(): return 'This is a protected route.' if __name__ == "__main__": FLASK_DEBUG = os.getenv('FLASK_DEBUG', 'true').lower() == 'true' if FLASK_DEBUG: app.run(host='0.0.0.0', port=5000, debug=True) else: app.run() ``` ### Explanation: 1. **OAuth Initialization**: - The `oauth` object is initialized with the Flask app. - The `auth0` client is registered with the necessary credentials and endpoints. 2. **Auth0 Routes**: - `/login`: Redirects the user to the Auth0 login page. - `/callback`: Handles the callback from Auth0, retrieves the user info, and stores it in the session. - `/logout`: Clears the session and redirects the user to the Auth0 logout page. 3. **Protect Routes**: - The `requires_auth` decorator checks if the user is authenticated before allowing access to the route. 4. **Example Protected Route**: - The `/protected` route is an example of a route that requires authentication. By following these steps, you will integrate Auth0 authentication into your Flask app, ensuring that routes are protected and tokens are verified.
Changes on README.md To address the task of updating the `README.md` file with instructions for setting up Auth0, managing JWT tokens, and configuring Azure API Management, you can follow these steps: ### Updated `README.md` Content ```markdown # NFLSTATS Development Environment This is the NFLSTATS project, designed to provide statistical insights for the NFL. ## THIS APP NEEDS A TON OF WORK 1. PIPELINES FROM SPORTS RADAR APIS FOR LIVE DATA 2. OTHER ADVANCED ANALYTICS DATASOURCES FOR ADDITIONAL ANALYSIS FOR AN AI MODEL 3. DATA VISUALIZATION/DISPLAY 4. ADDING OTHER SPORTS 5. MAKING AI MODELS FOR SPECIFIC GAMES AND THEIR CONFIDENCE INTERVALS IN A PARTICULAR BET This guide will help you set up the environment using GitHub Codespaces or set it up locally using Docker and Docker Compose. Be aware the application is half built right now. It needs quite a bit of TLC. This is my first project, feel free to criticize and make recommendations as you see fit! ## NFLSTATS Development Environment Setup Guide Welcome to the NFLSTATS project! This guide will walk you through setting up your development environment to run the application locally. Before diving into the setup process, ensure you have the following prerequisites installed on your machine: ### Prerequisites - Git: For cloning the repository. Download and install from Git's official site. - Docker: Required for running the application's containers. Download and install Docker from Docker's official site. - Docker Compose: For managing multi-container Docker applications. Typically included with Docker Desktop installations. - Node.js: The runtime environment for the application. Download and install from Node.js's official site. - npm: Comes with Node.js and is used for managing dependencies. - Visual Studio Code (Optional): Recommended IDE for editing and debugging the application. Download from Visual Studio Code's official site. ### Environment Setup #### Clone the Repository First, clone the NFLSTATS repository to your local machine using Git: ```sh git clone https://github.com/[your_username]/nflstats.git ``` Replace `[your_username]` with your GitHub username if you have forked the repository. Otherwise, use the project's main repository URL. #### Docker Environment Ensure Docker and Docker Compose are installed and running on your machine. The application uses Docker containers for both development (`docker-compose.debug.yml`) and production (`docker-compose.prod.yml`) environments. #### Node.js and npm Packages After cloning the repository, navigate to the project directory and install the required npm packages: ```sh cd nflstats npm install ``` This command installs all dependencies defined in `package.json`. #### Environment Variables The application requires several environment variables to be set. These variables can be found and modified in the `.env` files located in the project root or specific directories for different environments (e.g., `.env.development`, `.env.production`). Ensure all necessary variables are set before proceeding. ### Running the Application for Development With all prerequisites installed and environment variables set, you can start the development server using npm scripts defined in `package.json`: ```sh npm run start:dev ``` This command executes the `start:dev` script, which builds the application for development and starts the development server. If everything is set up correctly, the application should now be running and accessible. ### Accessing the Application Once the application is running, you can access it by navigating to `http://localhost:3000` in your web browser, assuming the default port configuration. ### Additional Information #### Dockerfiles If the Dockerfiles are on `.dockerignore`, ensure to remove them from the ignore list or manually copy them into your project directory. The Dockerfiles are crucial for building the Docker images for both development and production environments. #### Scripts For detailed information on available npm scripts and their purposes, refer to the scripts section in `package.json`. These scripts provide shortcuts for common tasks such as building the application, starting the development server, and cleaning the build directory. #### Contributing Contributions to the NFLSTATS project are welcome! Please refer to the project's `CONTRIBUTING.md` file for guidelines on how to contribute. ### Auth0 Setup To implement secure authentication and authorization for the APIs, follow these steps: 1. **Configure Auth0 for Authentication** - Set up an Auth0 tenant and applications. - Define roles and permissions for API access. 2. **Use JWT for Token-Based Authorization** - Ensure tokens are securely managed and verified. 3. **Implement and Configure Azure API Management for Security** - Set up API gateways and configure rate limiting, throttling policies. 4. **Store Auth0 Credentials in GitHub Secrets** - Secrets Needed: `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET` ### Auth0 Integration 1. **Set Up Auth0 Tenant and Application** - Go to the [Auth0 Dashboard](https://manage.auth0.com/). - Create a new tenant and application. - Note down the `Client ID`, `Client Secret`, and `Domain`. 2. **Define Roles and Permissions** - In the Auth0 Dashboard, navigate to the "Roles" section. - Create roles and assign permissions as needed. 3. **Configure Environment Variables** - Add the following environment variables to your `.env` file: ```env AUTH0_CLIENT_ID=your_auth0_client_id AUTH0_CLIENT_SECRET=your_auth0_client_secret AUTH0_DOMAIN=your_auth0_domain ``` ### JWT Token Management 1. **Creating JWT Tokens** - Use libraries like `pyjwt` to create JWT tokens. - Ensure tokens are signed using a secure algorithm. 2. **Verifying JWT Tokens** - Implement token verification in your application to ensure only valid tokens are accepted. ### Azure API Management Configuration 1. **Set Up API Gateways** - Go to the [Azure Portal](https://portal.azure.com/). - Create a new API Management service. - Configure API gateways and import your APIs. 2. **Configure Rate Limiting and Throttling Policies** - In the Azure API Management service, navigate to the "Policies" section. - Set up rate limiting and throttling policies to protect your APIs. By following this guide, you should now have a fully functional development environment for the NFLSTATS project with secure authentication and authorization implemented. Happy coding! ``` ### Summary - **Added instructions for setting up Auth0**: Including tenant and application setup, roles and permissions, and environment variable configuration. - **Detailed JWT token management**: Instructions for creating and verifying JWT tokens. - **Azure API Management configuration**: Steps to set up API gateways and configure rate limiting and throttling policies. This update ensures that developers have clear guidance on implementing secure authentication and authorization for the NFLSTATS project.
Changes on backend-container/src/nflfeapi/__init__.py To integrate Auth0 authentication into the FastAPI application in the `backend-container/src/nflfeapi/__init__.py` file, you will need to follow these steps: 1. **Install Required Libraries**: Ensure you have the necessary libraries installed. You will need `fastapi`, `authlib`, and `python-jose` for handling JWT tokens. ```bash pip install fastapi authlib python-jose ``` 2. **Configure Auth0**: Set up your Auth0 tenant and applications as described in the issue. Obtain the `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, and `AUTH0_DOMAIN`. 3. **Initialize Auth0 in FastAPI**: Create functions to handle the authentication and authorization processes. This includes verifying JWT tokens and protecting routes. 4. **Protect Routes**: Use dependency injection to protect your routes and ensure only authenticated users can access them. Here is a concrete implementation plan for the `backend-container/src/nflfeapi/__init__.py` file: ### Step-by-Step Implementation 1. **Import Necessary Modules**: Import the required modules at the beginning of the file. ```python from fastapi import FastAPI, Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from authlib.integrations.starlette_client import OAuth import os ``` 2. **Initialize FastAPI and OAuth**: Initialize the FastAPI application and configure OAuth with Auth0. ```python app = FastAPI() oauth = OAuth() oauth.register( name='auth0', client_id=os.getenv('AUTH0_CLIENT_ID'), client_secret=os.getenv('AUTH0_CLIENT_SECRET'), server_metadata_url=f'https://{os.getenv("AUTH0_DOMAIN")}/.well-known/openid-configuration', client_kwargs={ 'scope': 'openid profile email', }, ) ``` 3. **Create JWT Token Verification Function**: Implement a function to verify JWT tokens. ```python def verify_token(token: str): try: payload = jwt.decode(token, os.getenv('AUTH0_CLIENT_SECRET'), algorithms=['HS256'], audience=os.getenv('AUTH0_CLIENT_ID')) return payload except JWTError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) ``` 4. **Create Dependency for Protected Routes**: Use FastAPI's dependency injection to protect routes. ```python oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") async def get_current_user(token: str = Depends(oauth2_scheme)): return verify_token(token) ``` 5. **Protect Routes**: Apply the dependency to your routes to ensure they are protected. ```python @app.get("/protected-route") async def protected_route(current_user: dict = Depends(get_current_user)): return {"message": "This is a protected route", "user": current_user} ``` ### Full Example Code Here is the complete code for the `backend-container/src/nflfeapi/__init__.py` file: ```python from fastapi import FastAPI, Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from authlib.integrations.starlette_client import OAuth import os app = FastAPI() oauth = OAuth() oauth.register( name='auth0', client_id=os.getenv('AUTH0_CLIENT_ID'), client_secret=os.getenv('AUTH0_CLIENT_SECRET'), server_metadata_url=f'https://{os.getenv("AUTH0_DOMAIN")}/.well-known/openid-configuration', client_kwargs={ 'scope': 'openid profile email', }, ) def verify_token(token: str): try: payload = jwt.decode(token, os.getenv('AUTH0_CLIENT_SECRET'), algorithms=['HS256'], audience=os.getenv('AUTH0_CLIENT_ID')) return payload except JWTError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") async def get_current_user(token: str = Depends(oauth2_scheme)): return verify_token(token) @app.get("/protected-route") async def protected_route(current_user: dict = Depends(get_current_user)): return {"message": "This is a protected route", "user": current_user} ``` ### Conclusion This implementation ensures that your FastAPI application is integrated with Auth0 for authentication and authorization. Routes are protected, and JWT tokens are verified to secure access to your API. Make sure to update your environment variables with the necessary Auth0 credentials.
Changes on backend-container/src/nflfeapi/support_api.py To implement authentication checks in the `support_api.py` file, you need to ensure that only authenticated users can access the support request routes. This can be achieved by verifying the JWT tokens provided by Auth0. Here’s a step-by-step guide to modify the file accordingly: 1. **Import Required Libraries**: Import the necessary libraries for JWT token verification. 2. **Create a Decorator for Authentication**: Create a decorator function to check the validity of the JWT token. 3. **Apply the Decorator to Routes**: Apply this decorator to the routes that need protection. Here’s how you can modify the `support_api.py` file: ### Updated `support_api.py` File ```python import sys import os from datetime import datetime from flask import request, jsonify, Blueprint, Flask, make_response from flask_cors import CORS, cross_origin from functools import wraps import jwt from src.utils.logandcatchexceptions import log_and_catch_exceptions from src.utils.log import be_logger current_dir = os.path.dirname(os.path.abspath(__file__)) project_root = os.path.abspath(os.path.join(current_dir, '..', '..')) sys.path.append(project_root) sys.path.append(os.path.join(project_root, 'src')) from pymongo import MongoClient from flask_cors import CORS from dotenv import load_dotenv load_dotenv() bp_support_api = Blueprint('support_api', __name__) ip_timestamps = {} api_key = os.getenv('APIKEY') mongodb_url = os.getenv('MONGODB_URL') mongodb_database = os.getenv('MONGODB_DATABASE') auth0_domain = os.getenv('AUTH0_DOMAIN') auth0_audience = os.getenv('AUTH0_AUDIENCE') def token_required(f): @wraps(f) def decorated(*args, **kwargs): token = None if 'Authorization' in request.headers: token = request.headers['Authorization'].split()[1] if not token: return jsonify({'message': 'Token is missing!'}), 401 try: payload = jwt.decode(token, os.getenv('AUTH0_PUBLIC_KEY'), algorithms=['RS256'], audience=auth0_audience) request.user = payload except jwt.ExpiredSignatureError: return jsonify({'message': 'Token has expired!'}), 401 except jwt.InvalidTokenError: return jsonify({'message': 'Invalid token!'}), 401 return f(*args, **kwargs) return decorated @bp_support_api.route('/submit-support', methods=['OPTIONS', 'POST']) @cross_origin(origin='http://localhost:3000', headers=['Content-Type', 'Authorization']) # type: ignore @log_and_catch_exceptions @token_required def submit_support_request(): if request.method != 'POST': return data = request.get_json() client = get_mongodb_connection() if not client: be_logger.error("Failed to connect to MongoDB.") return make_response(jsonify({"error": "Database connection failed"}), 500) db = client["Current_Season"] collection = db["SupportEmails"] name = data.get('name') email = data.get('email') phone = data.get('phone') message = data.get('message') if not email or "@" not in email: return jsonify({"error": "Invalid email address"}), 400 user_ip = request.remote_addr if user_ip in ip_timestamps: last_timestamp = ip_timestamps[user_ip] days_diff = (datetime.now() - last_timestamp).days if days_diff < 1: return jsonify({"error": "You have already submitted today. Please try again tomorrow."}), 400 support_request = { "name": name, "email": email, "phone": phone, "message": message, "timestamp": datetime.now() } try: collection.insert_one(support_request) except Exception as e: be_logger.error("Error inserting support request: %s", str(e)) return jsonify({"error": "Error inserting support request"}), 500 ip_timestamps[user_ip] = datetime.now() return jsonify({"message": "Support request submitted successfully"}), 200 ``` ### Explanation: 1. **Import JWT Library**: Import the `jwt` library to handle JWT token decoding. 2. **Token Required Decorator**: The `token_required` decorator function checks for the presence of a JWT token in the `Authorization` header. It decodes the token using the Auth0 public key and verifies its validity. If the token is missing or invalid, it returns a 401 Unauthorized response. 3. **Apply Decorator**: The `@token_required` decorator is applied to the `submit_support_request` route to ensure that only authenticated users can access it. This implementation ensures that the support request route is protected and only accessible to users with valid JWT tokens issued by Auth0.
Changes on backend-container/src/nflfeapi/getdata.py To implement authentication and authorization checks using JWT tokens in the `get_data` function, you can follow these steps: 1. **Import Required Libraries**: Ensure you have the necessary libraries for JWT handling and authentication. 2. **Create a Decorator for JWT Verification**: This decorator will check the validity of the JWT token before allowing access to the `get_data` function. 3. **Apply the Decorator to the `get_data` Route**: Use the decorator to protect the route. Here's a concrete implementation plan for the `get_data.py` file: ### Step-by-Step Implementation 1. **Import Required Libraries**: - Import the `jwt` library for handling JWT tokens. - Import any other necessary libraries for authentication. 2. **Create a JWT Verification Decorator**: - This decorator will extract the token from the request headers, verify it, and ensure the user is authenticated. 3. **Apply the Decorator to the `get_data` Route**: - Use the decorator to protect the `get_data` route, ensuring only authenticated users can access it. ### Updated Code ```python import sys import os import jwt # Import JWT library from functools import wraps from flask import Flask, request, jsonify, Blueprint, render_template from pymongo import MongoClient from datetime import datetime from flask_cors import CORS from dotenv import load_dotenv from src.utils.logandcatchexceptions import log_and_catch_exceptions from src.utils.log import be_logger bp_get_data = Blueprint('get_data', __name__) # Load environment variables load_dotenv() # Secret key for JWT decoding JWT_SECRET = os.getenv('JWT_SECRET') JWT_ALGORITHM = 'HS256' def token_required(f): @wraps(f) def decorated(*args, **kwargs): token = None if 'Authorization' in request.headers: token = request.headers['Authorization'].split(" ")[1] # Extract token from header if not token: return jsonify({'message': 'Token is missing!'}), 401 try: data = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM]) current_user = data['sub'] # Assuming 'sub' contains the user identifier except Exception as e: be_logger.error(f"Token verification failed: {str(e)}") return jsonify({'message': 'Token is invalid!'}), 401 return f(current_user, *args, **kwargs) return decorated @log_and_catch_exceptions @bp_get_data.route('/get-data', methods=['GET']) @token_required def get_data(current_user): try: selected_year_from_frontend = request.args.get('year', default=None, type=int) selected_season_type_from_frontend = request.args.get('season_type', default=None, type=str) selected_team_from_frontend = request.args.get('single_team', default=None, type=str) be_logger.info(f"get_data: From frontend - Year: {selected_year_from_frontend}, Season: {selected_season_type_from_frontend}, Team: {selected_team_from_frontend}") updated = False if data.current_year_season_cache: current_year, current_season_type, _, _ = data.current_year_season_cache be_logger.info(f"get_data: Using cached year and season: {current_year} {current_season_type}") else: current_year, current_season_type, _, _ = get_season_info_and_selected(request) data.current_year_season_cache = current_year, current_season_type, data.selected_year, data.selected_season_type be_logger.info("get_data: Data fetched with get_season_info_and_selected and saved to cache") key = f"{data.selected_year}_{data.selected_season_type}" be_logger.info(f"get_data: Generated key for selected year and season: {key}") if selected_year_from_frontend is not None and selected_year_from_frontend != data.selected_year: data.selected_year = selected_year_from_frontend updated = True if selected_season_type_from_frontend is not None and selected_season_type_from_frontend != data.selected_season_type: data.selected_season_type = selected_season_type_from_frontend updated = True if selected_team_from_frontend is not None and selected_team_from_frontend != data.selected_team: data.selected_team = selected_team_from_frontend updated = True be_logger.info(f"get_data: Updated flag: {updated}") if (team_top_10_data_cache_json := structure_data_for_categories()) is None: be_logger.error("get_data: structure_data_for_categories() returned None") return jsonify({"error": "Error in getting top 10 data"}), 500 else: be_logger.info("get_data: Fetched data with structure_data_for_categories as it was not found in cache") if not data.year_season_combinations_cache: data.year_season_combinations_cache = get_year_season_combinations(data.selected_year, data.selected_season_type) be_logger.info(f"get_data: Number of Documents returned to year_season_combinations_cache: {len(data.year_season_combinations_cache)}") if key in data.teams_dict_cache: be_logger.debug(f"get_data: Teams_dict_cache content: {data.teams_dict_cache[key][1:4]}") else: be_logger.warning(f"get_data: Key {key} not found in data.teams_dict_cache") data.selected_teams = data.teams_dict_cache.get(key, []) be_logger.info(f"get_data: Selected Teams (first 2): {data.selected_teams[:2]}") if (team_top_10_data := data.team_top_10_data_cache.get(key)) is None: be_logger.error(f"get_data: No team top 10 data Cache: {data.team_top_10_data_cache}") return jsonify({"error": "No team top 10 data found"}), 500 offensive_leaders = team_top_10_data.get('Offensive Leaders', {}) defensive_leaders = team_top_10_data.get('Defensive Leaders', {}) for category_name, stats_list in offensive_leaders.items(): be_logger.info(f"Offensive Leaders - {category_name}: Number of Teams {len(stats_list)}") for category_name, stats_list in defensive_leaders.items(): be_logger.info(f"Defensive Leaders - {category_name}: Number of Teams {len(stats_list)}") return render_template('DynamicSeasonStats.html', team_top_10_data=data.team_top_10_data_cache[key], selected_year=data.selected_year, selected_season_type=data.selected_season_type, teams=data.selected_teams, year_season_combinations=data.year_season_combinations_cache) except Exception as e: be_logger.error(f"get_data: Error occurred: {str(e)}") return jsonify({"error": str(e)}), 500 ``` ### Explanation 1. **JWT Secret and Algorithm**: The `JWT_SECRET` and `JWT_ALGORITHM` are used to decode the JWT token. 2. **`token_required` Decorator**: This decorator extracts the token from the `Authorization` header, verifies it, and ensures the user is authenticated. 3. **Protected Route**: The `get_data` route is protected by the `@token_required` decorator, ensuring only authenticated users can access it. This implementation ensures that the `get_data` function is secure and only accessible to users with valid JWT tokens.