Skip to content

Commit

Permalink
add api key feature to application
Browse files Browse the repository at this point in the history
  • Loading branch information
scottpickett committed Feb 12, 2024
1 parent 872c5c4 commit f9fe342
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
/flask/logs
/.vscode
# .env removed for docker config directives
.jsonk


53 changes: 52 additions & 1 deletion flask/app/app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,59 @@
from flask import Flask, render_template, request, url_for
import json
from functools import wraps
from flask import request, abort, Flask, render_template, request, url_for
import app.server as server

app = Flask(__name__)


def require_api_key(view_function):
"""
Decorator to enforce API key validation on Flask view functions.
This decorator loads the list of valid API keys from a specified JSON file.
It then checks the incoming request for an 'X-Api-Key' header and validates
the provided API key against the list of keys loaded from the file.
If the API key is valid, the request is allowed to proceed to
the decorated view function. If the API key is invalid or missing,
an HTTP 401 Unauthorized error is returned.
Args:
view_function (function): The Flask view function to be decorated.
Returns:
function: The decorated view function which includes API key validation.
Raises:
FileNotFoundError: If the API keys file does not exist.
with HTTP 500 Internal Server Error
json.JSONDecodeError: If there is an error parsing the API keys file.
with HTTP 500 Internal Server Error
HTTP 401 Unauthorized if the API key is invalid or missing
"""
@wraps(view_function)
def decorated_function(*args, **kwargs):
# Load API keys
try:
with open('app/credentials/api_keys.jsonk', 'r') as file:
api_keys_data = json.load(file)
except (FileNotFoundError, json.JSONDecodeError) as e:
abort(500, f'Error loading or parsing API keys file: {e}')

# Retrieve the API key from the request header
provided_api_key = request.headers.get('X-Api-Key')

# Check if the provided API key matches any key in the file
for item in api_keys_data.values():
if provided_api_key == item.get('key'):
return view_function(*args, **kwargs)

# If no matching key is found, return an unauthorized error
abort(401) # Unauthorized access
return decorated_function


@app.route('/', methods=['GET'])
def root_get():
"""The default GET view for hitting the application with a browser
Expand Down Expand Up @@ -39,6 +89,7 @@ def api_get() -> dict:


@app.route('/api', methods=['POST'])
@require_api_key
def api_post() -> dict:
"""The POST view for the API. Submitted data is passed to the "back-end"
and "reversed". Returned data is given to the rendered template.
Expand Down
10 changes: 10 additions & 0 deletions flask/app/credentials/api_keys.jsonk
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"0": {
"key": "MyAPIKey1",
"comment": "This key is for Application A"
},
"1": {
"key": "YourActualAPIKeyHere",
"comment": "This key is for Application B"
}
}
51 changes: 46 additions & 5 deletions flask/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ flask/
app.py <--- the main applicaiton file with the Flask views ("front-end")
server.py <--- the "back-end" application file with application functions
wsgi.py <--- the web server callable WSGI Python file to launch the Flask app
|---static/ <--- directory for static front-end resources, like CSS files
|---templates/ <--- directory for HTML code
|---work_files/ <--- optional directory for writing out or reading in data
|---static/ <--- directory for static front-end resources, like CSS files
|---templates/ <--- directory for HTML code
|---work_files/ <--- optional directory for writing out or reading in data
|---credentials/ <--- credentials for accessing the app, when required
|---tests/ <--- directory containing all Python tests (see "tests" below)
|---coverage <--- directory containing coverage.py resources
.coveragerc <--- the configuration file for coverage.py
Expand All @@ -32,6 +33,42 @@ flask/
readme.md <--- this file
```

## API Keys File (`api_keys.jsonk`)

### Overview

The `api_keys.jsonk` file is used to store API keys for authenticating requests to selected endpoints of the application. Each key is associated with a comment explaining its purpose or the application it is intended for. This file should be placed in the `credentials` directory at the root of the app.

### Format

The file contains a JSON object where each entry consists of a numeric string as a key, and the value is another object with two properties: `key` and `comment`. The `key` property holds the actual API key, while the `comment` property provides a description or note about the key.

### Example

```json
{
"0": {
"key": "MyAPIKey1",
"comment": "This key is for Application A"
},
"1": {
"key": "AnotherAPIKey",
"comment": "This key is for Application B"
}
}
```

### Usage

The application reads this file to authenticate API requests. When making a request, include the API key in the X-Api-Key request header. The server will verify the API key against those listed in the api_keys.jsonk file.

### Security

- DO NOT commit this file to version control.
- Ensure api_keys.jsonk is listed in your .gitignore file to prevent accidental exposure of API keys.
- Regularly rotate and review API keys to ensure they remain secure.
- Future: transition to a more secure key management system for production environments.

## Tests

### Running unit tests
Expand Down Expand Up @@ -60,5 +97,9 @@ HTML test coverage report:

### web test

```curl -X GET localhost:8080/api```
```curl -X POST -d data="reverse me" server1:8080/api```
#### GET method
``` curl -X GET http://localhost:8080/api```

#### POST method with API Key
```curl -X POST -H "X-Api-Key: YourActualAPIKeyHere" -d "data=reverse me" http://localhost:8080/api```

6 changes: 6 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ sudo docker container ls
sudo docker-compose up --build -d
```

### Restart the containers

```
sudo docker-compose restart
```

#### Stop containers and remove them, if needed

```
Expand Down

0 comments on commit f9fe342

Please sign in to comment.