Add token validation logic and protected routes

master
esensar 2018-05-07 17:17:19 +02:00
parent 6c856965a1
commit f506e3dfaa
8 changed files with 117 additions and 22 deletions

View File

@ -49,3 +49,15 @@ def create_token(username, password):
raise ValueError("Invalid credentials") raise ValueError("Invalid credentials")
return account.create_auth_token() return account.create_auth_token()
def validate_token(token):
"""
Validates token and returns associated account
:param token: auth token to validate
:type token: string
:returns created token
:rtype Account
"""
return Account.validate_token(token)

View File

@ -77,20 +77,31 @@ class Account(db.Model):
Generates the Auth Token Generates the Auth Token
:return: string :return: string
""" """
try: current_time = datetime.datetime.utcnow()
current_time = datetime.datetime.utcnow() payload = {
payload = { 'exp': current_time + datetime.timedelta(days=0, hours=1),
'exp': current_time + datetime.timedelta(days=0, hours=1), 'iat': current_time,
'iat': current_time, 'sub': self.id
'sub': self.id }
} return jwt.encode(
return jwt.encode( payload,
payload, app.config.get('SECRET_KEY'),
app.config.get('SECRET_KEY'), algorithm='HS256'
algorithm='HS256' ).decode('utf-8')
).decode('utf-8')
except Exception as e: @staticmethod
return e def validate_token(token):
"""
Validates given Auth token
:rtype: Account
:return: Account associated with token
"""
payload = jwt.decode(
token,
app.config.get('SECRET_KEY'),
algorithms=['HS256']
)
return Account.get(id=payload['sub'])
def __repr__(self): def __repr__(self):
return '<Account (name=%s, role=%s)>' % self.username, self.role return '<Account (name=%s, role=%s)>' % self.username, self.role

View File

@ -1,16 +1,45 @@
from flask import Blueprint from flask import Blueprint, request, g
from flask_restful import Api from flask_restful import Api, Resource, abort
from .resources.account import AccountResource from functools import wraps
from .resources.token import TokenResource
from marshmallow import ValidationError from marshmallow import ValidationError
from app.accounts import validate_token
api_bp = Blueprint('api', __name__) api_bp = Blueprint('api', __name__)
api = Api(api_bp) api = Api(api_bp)
# Add resources
api.add_resource(AccountResource, '/v1/accounts') def protected(func):
api.add_resource(TokenResource, '/v1/token') @wraps(func)
def protected_function(*args, **kwargs):
try:
token = request.headers['Authorization'] or None
if not token:
abort(401, message='Unauthorized', status='error')
g.current_account = validate_token(token.replace("Bearer ", ""))
if not g.current_account:
abort(401, message='Unauthorized', status='error')
except Exception:
abort(401, message='Unauthorized', status='error')
return protected_function
class ProtectedResource(Resource):
method_decorators = [protected]
def add_resources():
from .resources.account import AccountResource
from .resources.token import TokenResource
api.add_resource(AccountResource, '/v1/accounts')
api.add_resource(TokenResource, '/v1/token')
add_resources()
@api_bp.errorhandler(ValidationError) @api_bp.errorhandler(ValidationError)

View File

@ -3,6 +3,7 @@ from webargs import fields
from webargs.flaskparser import use_args from webargs.flaskparser import use_args
from flasgger import swag_from from flasgger import swag_from
import app.accounts as accounts import app.accounts as accounts
from app.api import protected
class AccountResource(Resource): class AccountResource(Resource):
@ -28,6 +29,7 @@ class AccountResource(Resource):
except ValueError: except ValueError:
abort(422, message='Account already exists', status='error') abort(422, message='Account already exists', status='error')
@protected
@swag_from('swagger/get_account_spec.yaml') @swag_from('swagger/get_account_spec.yaml')
def get(self): def get(self):
return '', 200 return '', 200

View File

@ -19,6 +19,7 @@ parameters:
- password - password
- email - email
$ref: '#/definitions/User' $ref: '#/definitions/User'
security: []
responses: responses:
201: 201:
description: Successful creation description: Successful creation

View File

@ -16,6 +16,7 @@ parameters:
properties: properties:
user: user:
$ref: '#/definitions/Credentials' $ref: '#/definitions/Credentials'
security: []
responses: responses:
200: 200:
description: Successful creation description: Successful creation
@ -33,4 +34,4 @@ responses:
401: 401:
description: Bad credentials description: Bad credentials
schema: schema:
$ref: '#/definitions/Error' $ref: '#/definitions/UnauthorizedError'

View File

@ -57,6 +57,19 @@ definitions:
email: email:
$ref: '#/definitions/email' $ref: '#/definitions/email'
UnauthorizedError:
type: object
required:
- status
- message
properties:
status:
$ref: '#/definitions/status'
default: error
message:
$ref: '#/definitions/message'
default: Unauthorized
Error: Error:
type: object type: object
required: required:
@ -70,6 +83,27 @@ definitions:
$ref: '#/definitions/message' $ref: '#/definitions/message'
default: Error message default: Error message
securityDefinitions:
Bearer:
type: apiKey
name: Authorization
in: header
description: |
For accessing the API a valid JWT token must be passed in all the queries in
the 'Authorization' header as Bearer token.
A valid JWT token is generated by the API and returned as answer of a call
to the route /login giving a valid user & password.
The following syntax must be used in the 'Authorization' header :
Bearer xxxxxx.yyyyyyy.zzzzzz
security:
- Bearer: []
info: info:
description: Python (Flask) backend for IoT sysyem made for master's degree final project description: Python (Flask) backend for IoT sysyem made for master's degree final project
title: IoT Backend title: IoT Backend

View File

@ -33,3 +33,8 @@ MQTT_BROKER_PORT = 1883
MQTT_USERNAME = 'user' MQTT_USERNAME = 'user'
MQTT_PASSWORD = 'secret' MQTT_PASSWORD = 'secret'
MQTT_REFRESH_TIME = 1.0 # refresh time in seconds MQTT_REFRESH_TIME = 1.0 # refresh time in seconds
# Flassger config
SWAGGER = {
'uiversion': 3
}