Skip to content

Commit

Permalink
Merge branch 'staging' into enhancement-selected-row-styling
Browse files Browse the repository at this point in the history
  • Loading branch information
campb303 authored Nov 16, 2020
2 parents 937dbc0 + 7e84b6e commit 2e6f87d
Show file tree
Hide file tree
Showing 28 changed files with 787 additions and 137 deletions.
9 changes: 6 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
# Testing
/coverage

# Productiom
# React Build Files
/build

# Misc
.DS_Store
# React local environment files
.env.local
.env.development.local
.env.test.local
.env.production.local

# Misc
.DS_Store
.vscode/

# Node Package Management
Expand All @@ -28,3 +30,4 @@ yarn-error.log*
/api/venv
__pycache__/
venv-manager.log
/api/.env
3 changes: 2 additions & 1 deletion Dev Environment Setup Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,5 @@ All of the tools in this project are accessible as an npm task so you can intera
| `kill:api` | Kills the runaway API process(es). |
| `venv:create` | This will create a virtual environment in `/api/venv` and install requirements from `/api/requirements.txt`. |
| `venv:delete` | This will delete the folder `/api/venv`. |
| `venv:reset` | This will run `venv:delete` then `venv:create`. |
| `venv:reset` | This will run `venv:delete` then `venv:create`. |
| `venv:freeze` | Regenerates the API requirements.txt file and mitigates [this pip bug](https://github.com/pypa/pip/issues/4022). |
2 changes: 1 addition & 1 deletion api/ECNQueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -1190,7 +1190,7 @@ def __getFormattedDate(self, date: str) -> str:
try:
# This date is never meant to be used. The default attribute is just to set timezone.
parsedDate = parse(date, default=datetime.datetime(
1970, 0, 1, tzinfo=tz.gettz('EDT')))
1970, 1, 1, tzinfo=tz.gettz('EDT')))
except:
return ""

Expand Down
143 changes: 124 additions & 19 deletions api/api.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,114 @@
from flask import Flask
from flask import Flask, request, after_this_request
from flask_restful import Api, Resource
from flask_jwt_extended import (
JWTManager, create_access_token, create_refresh_token,
jwt_required, get_jwt_identity, jwt_refresh_token_required,
set_refresh_cookies, unset_refresh_cookies
)
from werkzeug.security import check_password_hash
import os, dotenv
import ECNQueue

# Load envrionment variables for ./.env
dotenv.load_dotenv()

# Create Flask App
app = Flask(__name__)

# Create API Interface
api = Api(app)


################################################################################
# Configure Flask-JWT-Extended
################################################################################

# Set JWT secret key and create JWT manager
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY")
# Set identity claim field key to sub for JWT RFC complience
# Flask-JWT-Extended uses 'identity' by default for compatibility reasons
app.config["JWT_IDENTITY_CLAIM"] = "sub"
# Set the key for error messages generated by Flask-JWT-Extended
app.config["JWT_ERROR_MESSAGE_KEY"] = "message"

# Look for JWTs in headers (for access) then cookies (for refresh)
app.config["JWT_TOKEN_LOCATION"] = ["headers", "cookies"]
# Restrict cookies to HTTPS in prod, allow HTTP in dev
app.config["JWT_COOKIE_SECURE"] = False if os.environ.get("ENVIRONMENT") == "dev" else True
# Restrict cookies using SameSite=strict flag
app.config["JWT_COOKIE_SAMESITE"] = "strict"
# Restrict refresh tokens to /token/refresh endpoint
app.config["JWT_REFRESH_COOKIE_PATH"] = '/tokens/refresh'
# Set the cookie key for CRSF validation string
# This is the default value. Adding it for easy reference
app.config["JWT_REFRESH_CSRF_HEADER_NAME"] = "X-CSRF-TOKEN"

tokenManager = JWTManager(app)



class Login(Resource):
def post(self) -> tuple:
"""Validates username/password and returns both access and refresh tokens.
Return Codes:
200 (OK): On success.
401 (Unauthroized): When username or password are incorrect.
422 (Unprocessable Entitiy): When the username or password can't be parsed.
Example:
curl -X POST
-H "Content-Type: application/json"
-d '{"username": "bob", "password": "super_secret"}'
{ "access_token": fjr09hfp09h932jp9ruj3.3r8ihf8h0w8hr08ifhj804h8i.8h48ith08ity409hip0t4 }
Returns:
tuple: Response containing tokens and HTTP response code.
"""
if not request.is_json:
return ({ "message": "JSON missing from request body"}, 422)

data = request.json

fields_to_check = ["username", "password"]
for field in fields_to_check:
if field not in data.keys():
return ({ "message": f"{field} missing from request body"}, 422)

if data["username"] != os.environ.get("SHARED_USERNAME"):
return ({ "message": "Username invalid"}, 401)
if not check_password_hash(os.environ.get("SHARED_PASSWORD_HASH"), data["password"]):
return ({ "message": "Password invalid"}, 401)

access_token = create_access_token(data["username"])
refresh_token = create_refresh_token(data["username"])

# This decorator is needed because Flask-RESTful's 'resourceful routing`
# doesn't allow for direct modification to the Flask response object.
# See: https://flask-restful.readthedocs.io/en/latest/quickstart.html#resourceful-routing
@after_this_request
def _does_this_work(response):
set_refresh_cookies(response, refresh_token)
return response

return ({ "access_token": access_token }, 200)

class RefreshAccessToken(Resource):
@jwt_refresh_token_required
def post(self):
username = get_jwt_identity()
access_token = create_access_token(username)
return ({"access_token": access_token}, 200)

class Item(Resource):
def get(self, queue: str, number: int) -> str:
@jwt_required
def get(self, queue: str, number: int) -> tuple:
"""Returns the JSON representation of the item requested.
Return Codes:
200 (OK): On success.
Example:
/api/ce/100 returns:
{
Expand All @@ -38,32 +133,40 @@ def get(self, queue: str, number: int) -> str:
item (int): The number of the item requested.
Returns:
str: JSON representation of the item requested.
tuple: Item as JSON and HTTP response code.
"""
return ECNQueue.Item(queue, number).toJson()
return (ECNQueue.Item(queue, number).toJson(), 200)

class Queue(Resource):
def get(self, queue: str) -> str:
@jwt_required
def get(self, queues: str) -> tuple:
"""Returns the JSON representation of the queue requested.
Return Codes:
200 (OK): On success.
Args:
queue (str): The queue requested.
queues (str): Plus (+) deliminited list of queues.
Returns:
str: JSON representation of the queue requested.
tuple: Queues as JSON and HTTP response code.
"""
queues_requested = queue.split("+")
queues_requested = queues.split("+")

queues = []
queue_list = []
for queue in queues_requested:
queues.append(ECNQueue.Queue(queue).toJson())
queue_list.append(ECNQueue.Queue(queue).toJson())

return queues
return (queue_list, 200)

class QueueList(Resource):
def get(self) -> list:
@jwt_required
def get(self) -> tuple:
"""Returns a list of dictionaries with the number of items in each queue.
Return Codes:
200 (OK): On success.
Example:
[
{
Expand All @@ -77,15 +180,17 @@ def get(self) -> list:
]
Returns:
list: Dictionaries with the number of items in each queue.
tuple: Queues and item counts as JSON and HTTP response code.
"""
return ECNQueue.getQueueCounts()

api.add_resource(QueueList, "/api/get_queues")
api.add_resource(Item, "/api/<string:queue>/<int:number>")
api.add_resource(Queue, "/api/<string:queue>")
return (ECNQueue.getQueueCounts(), 200)



api.add_resource(Login, "/login")
api.add_resource(RefreshAccessToken, "/tokens/refresh")
api.add_resource(Item, "/api/<string:queue>/<int:number>")
api.add_resource(Queue, "/api/<string:queues>")
api.add_resource(QueueList, "/api/get_queues")

if __name__ == "__main__":
app.run()
app.run()
2 changes: 2 additions & 0 deletions api/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ astroid==2.4.2
click==7.1.2
Flask==1.1.2
Flask-RESTful==0.3.8
Flask-JWT-Extended==3.24.1
gunicorn==20.0.4
isort==4.3.21
itsdangerous==1.1.0
Expand All @@ -12,6 +13,7 @@ MarkupSafe==1.1.1
mccabe==0.6.1
pylint==2.5.3
python-dateutil==2.8.1
python-dotenv==0.15.0
pytz==2020.1
six==1.15.0
toml==0.10.1
Expand Down
33 changes: 33 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
"history": "^5.0.0",
"material-table": "^1.63.1",
"react": "^16.13.1",
"react-cookie": "^4.0.3",
"react-dom": "^16.13.1",
"react-relative-time": "0.0.7",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"react-table": "^7.5.1",
"react-router-dom": "^5.2.0"
"react-table": "^7.5.1"
},
"scripts": {
"start:frontend": "react-scripts start",
Expand All @@ -32,6 +33,7 @@
"venv:create": "python3 utils/venv-manager.py create",
"venv:delete": "python3 utils/venv-manager.py delete",
"venv:reset": "python3 utils/venv-manager.py reset",
"venv:freeze": "cd api/ && venv/bin/pip freeze | grep -v 'pkg-resources' > requirements.txt",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
Expand Down
Loading

0 comments on commit 2e6f87d

Please sign in to comment.