added JWT Authentication (expiring bearer token)
This commit is contained in:
parent
b9d35b9244
commit
3f211919af
@ -2,10 +2,52 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"_postman_id": "9242b2d1-196b-4b2e-af57-c0e9eb141dba",
|
"_postman_id": "9242b2d1-196b-4b2e-af57-c0e9eb141dba",
|
||||||
"name": "BreCal",
|
"name": "BreCal",
|
||||||
|
"description": "Bremen Calling relevant API calls",
|
||||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||||
"_exporter_id": "10427908"
|
"_exporter_id": "10427908"
|
||||||
},
|
},
|
||||||
"item": [
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Login user",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"pm.environment.set(\"LOGON_TOKEN\", responseBody)"
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "noauth"
|
||||||
|
},
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"username\" : \"Londo\",\r\n \"password\" : \"Hallowach\"\r\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "http://{{PATH}}/login",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"{{PATH}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"login"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Participant GET",
|
"name": "Participant GET",
|
||||||
"request": {
|
"request": {
|
||||||
@ -14,7 +56,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "xxxTest",
|
"value": "{{LOGON_TOKEN}}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -48,7 +90,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "xxxTest",
|
"value": "{{LOGON_TOKEN}}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -76,7 +118,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "xxxTest",
|
"value": "{{LOGON_TOKEN}}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -113,7 +155,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "xxxTest",
|
"value": "{{LOGON_TOKEN}}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -150,7 +192,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "xxxTest",
|
"value": "{{LOGON_TOKEN}}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -173,6 +215,16 @@
|
|||||||
{
|
{
|
||||||
"name": "Notifications GET",
|
"name": "Notifications GET",
|
||||||
"request": {
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "{{LOGON_TOKEN}}",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
@ -202,7 +254,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "xxxTest",
|
"value": "{{LOGON_TOKEN}}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -230,7 +282,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "xxxTest",
|
"value": "{{LOGON_TOKEN}}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -264,7 +316,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "xxxTest",
|
"value": "{{LOGON_TOKEN}}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -301,7 +353,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "xxxTest",
|
"value": "{{LOGON_TOKEN}}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -333,7 +385,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "xxxTest",
|
"value": "{{LOGON_TOKEN}}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -359,5 +411,28 @@
|
|||||||
},
|
},
|
||||||
"response": []
|
"response": []
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer"
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "prerequest",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -2,7 +2,6 @@ from flask import Flask
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
|
||||||
from . import local_db
|
from . import local_db
|
||||||
|
|
||||||
from .api import shipcalls
|
from .api import shipcalls
|
||||||
@ -48,6 +47,3 @@ def create_app(test_config=None):
|
|||||||
logging.info('App started')
|
logging.info('App started')
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
def create_api_key():
|
|
||||||
return secrets.token_urlsafe(16)
|
|
||||||
@ -1,12 +1,15 @@
|
|||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
from webargs.flaskparser import parser
|
from webargs.flaskparser import parser
|
||||||
from .. import impl
|
from .. import impl
|
||||||
|
from ..services.auth_guard import auth_guard
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('berths', __name__)
|
bp = Blueprint('berths', __name__)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/berths', methods=['get'])
|
@bp.route('/berths', methods=['get'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def GetBerths():
|
def GetBerths():
|
||||||
|
|
||||||
if 'Authorization' in request.headers:
|
if 'Authorization' in request.headers:
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
from .. import impl
|
from .. import impl
|
||||||
|
from ..services.auth_guard import auth_guard
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@ -7,6 +8,7 @@ bp = Blueprint('notifications', __name__)
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/notifications', methods=['get'])
|
@bp.route('/notifications', methods=['get'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def GetNotifications():
|
def GetNotifications():
|
||||||
|
|
||||||
# TODO: verify token
|
# TODO: verify token
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
from .. import impl
|
from .. import impl
|
||||||
|
from ..services.auth_guard import auth_guard
|
||||||
import json
|
import json
|
||||||
|
|
||||||
bp = Blueprint('participant', __name__)
|
bp = Blueprint('participant', __name__)
|
||||||
|
|
||||||
@bp.route('/participant', methods=['get'])
|
@bp.route('/participant', methods=['get'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def GetParticipant():
|
def GetParticipant():
|
||||||
|
|
||||||
if 'Authorization' in request.headers:
|
if 'Authorization' in request.headers:
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from webargs.flaskparser import parser
|
|||||||
from marshmallow import Schema, fields
|
from marshmallow import Schema, fields
|
||||||
from ..schemas import model
|
from ..schemas import model
|
||||||
from .. import impl
|
from .. import impl
|
||||||
|
from ..services.auth_guard import auth_guard
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
@ -12,6 +13,7 @@ bp = Blueprint('shipcalls', __name__)
|
|||||||
# TODO: verify token
|
# TODO: verify token
|
||||||
|
|
||||||
@bp.route('/shipcalls', methods=['get'])
|
@bp.route('/shipcalls', methods=['get'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def GetShipcalls():
|
def GetShipcalls():
|
||||||
if 'Authorization' in request.headers:
|
if 'Authorization' in request.headers:
|
||||||
token = request.headers.get('Authorization')
|
token = request.headers.get('Authorization')
|
||||||
@ -24,6 +26,7 @@ def GetShipcalls():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/shipcalls', methods=['post'])
|
@bp.route('/shipcalls', methods=['post'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def PostShipcalls():
|
def PostShipcalls():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -38,6 +41,7 @@ def PostShipcalls():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/shipcalls', methods=['put'])
|
@bp.route('/shipcalls', methods=['put'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def PutShipcalls():
|
def PutShipcalls():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
from .. import impl
|
from .. import impl
|
||||||
|
from ..services.auth_guard import auth_guard
|
||||||
import json
|
import json
|
||||||
|
|
||||||
bp = Blueprint('ships', __name__)
|
bp = Blueprint('ships', __name__)
|
||||||
|
|
||||||
@bp.route('/ships', methods=['get'])
|
@bp.route('/ships', methods=['get'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def GetShips():
|
def GetShips():
|
||||||
if 'Authentication' in request.headers:
|
if 'Authentication' in request.headers:
|
||||||
token = request.headers.get('Authentication')
|
token = request.headers.get('Authentication')
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
from ..schemas import model
|
from ..schemas import model
|
||||||
from .. import impl
|
from .. import impl
|
||||||
|
from ..services.auth_guard import auth_guard
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -8,6 +9,7 @@ bp = Blueprint('times', __name__)
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/times', methods=['get'])
|
@bp.route('/times', methods=['get'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def GetTimes():
|
def GetTimes():
|
||||||
|
|
||||||
options = {}
|
options = {}
|
||||||
@ -17,6 +19,7 @@ def GetTimes():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/times', methods=['post'])
|
@bp.route('/times', methods=['post'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def PostTimes():
|
def PostTimes():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -36,6 +39,7 @@ def PostTimes():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/times', methods=['put'])
|
@bp.route('/times', methods=['put'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def PutTimes():
|
def PutTimes():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -51,6 +55,7 @@ def PutTimes():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/times', methods=['delete'])
|
@bp.route('/times', methods=['delete'])
|
||||||
|
@auth_guard() # no restriction by role
|
||||||
def DeleteTimes():
|
def DeleteTimes():
|
||||||
|
|
||||||
# TODO check if I am allowd to delete this thing by deriving the participant from the bearer token
|
# TODO check if I am allowd to delete this thing by deriving the participant from the bearer token
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import bcrypt
|
|||||||
|
|
||||||
from ..schemas import model
|
from ..schemas import model
|
||||||
from .. import local_db
|
from .. import local_db
|
||||||
|
from ..services import jwt_handler
|
||||||
|
|
||||||
def GetUser(options):
|
def GetUser(options):
|
||||||
|
|
||||||
@ -15,15 +16,19 @@ def GetUser(options):
|
|||||||
commands = pydapper.using(local_db.connection_pool)
|
commands = pydapper.using(local_db.connection_pool)
|
||||||
data = commands.query("SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, api_key FROM user WHERE user_name = ?username? OR user_email = ?username?",
|
data = commands.query("SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, api_key FROM user WHERE user_name = ?username? OR user_email = ?username?",
|
||||||
model=model.User, param={"username" : options["username"]})
|
model=model.User, param={"username" : options["username"]})
|
||||||
print(data)
|
# print(data)
|
||||||
if len(data) == 1:
|
if len(data) == 1:
|
||||||
if bcrypt.checkpw(options["password"].encode("utf-8"), bytes(data[0].password_hash, "utf-8")):
|
if bcrypt.checkpw(options["password"].encode("utf-8"), bytes(data[0].password_hash, "utf-8")):
|
||||||
return json.dumps({ "id": data[0].id,
|
result = {
|
||||||
"participant_id": data[0].participant_id,
|
"id": data[0].id,
|
||||||
"first_name": data[0].first_name,
|
"participant_id": data[0].participant_id,
|
||||||
"last_name": data[0].last_name,
|
"first_name": data[0].first_name,
|
||||||
"user_name": data[0].user_name,
|
"last_name": data[0].last_name,
|
||||||
"user_phone": data[0].user_phone}), 200
|
"user_name": data[0].user_name,
|
||||||
|
"user_phone": data[0].user_phone
|
||||||
|
}
|
||||||
|
token = jwt_handler.generate_jwt(payload=result, lifetime=60) # generate token valid 60 mins
|
||||||
|
return token, 200
|
||||||
|
|
||||||
if len(data) > 1:
|
if len(data) > 1:
|
||||||
return json.dumps("credential lookup mismatch"), 500
|
return json.dumps("credential lookup mismatch"), 500
|
||||||
|
|||||||
34
src/server/BreCal/services/auth_guard.py
Normal file
34
src/server/BreCal/services/auth_guard.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import json
|
||||||
|
from flask import request
|
||||||
|
from .jwt_handler import decode_jwt
|
||||||
|
|
||||||
|
def check_jwt():
|
||||||
|
# get header and try to get payload
|
||||||
|
# this will throw an exception if the payload is missing, invalid or expired
|
||||||
|
token = request.headers.get('Authorization')
|
||||||
|
if not token:
|
||||||
|
raise Exception('Missing access token')
|
||||||
|
jwt = token.split('Bearer ')[1]
|
||||||
|
try:
|
||||||
|
return decode_jwt(jwt)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f'invalid access token: {e}')
|
||||||
|
|
||||||
|
# magic. use this to decorate the api calls
|
||||||
|
# https://brunotatsuya.dev/blog/jwt-authentication-and-authorization-for-python-flask-rest-apis
|
||||||
|
|
||||||
|
def auth_guard(role=None):
|
||||||
|
def wrapper(route_function):
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
# Authentication gate
|
||||||
|
try:
|
||||||
|
user_data = check_jwt()
|
||||||
|
except Exception as e:
|
||||||
|
return json.dumps({"message" : f'{e}', "status": 401}), 401
|
||||||
|
if role and role not in user_data['roles']:
|
||||||
|
return json.dumps({"message": 'Authorization required.', "status" : 403}), 403
|
||||||
|
# get on to original route
|
||||||
|
return route_function(*args, **kwargs)
|
||||||
|
decorated_function.__name__ = route_function.__name__
|
||||||
|
return decorated_function
|
||||||
|
return wrapper
|
||||||
17
src/server/BreCal/services/jwt_handler.py
Normal file
17
src/server/BreCal/services/jwt_handler.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import os
|
||||||
|
import jwt
|
||||||
|
import datetime
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
def create_api_key():
|
||||||
|
return secrets.token_urlsafe(16)
|
||||||
|
|
||||||
|
def generate_jwt(payload, lifetime=None):
|
||||||
|
if lifetime:
|
||||||
|
payload['exp'] = (datetime.datetime.now() + datetime.timedelta(minutes=lifetime)).timestamp()
|
||||||
|
return jwt.encode(payload, os.environ.get('SECRET_KEY'), algorithm="HS256")
|
||||||
|
|
||||||
|
def decode_jwt(token):
|
||||||
|
return jwt.decode(token, os.environ.get('SECRET_KEY'), algorithms=["HS256"])
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user