Add token validation logic and protected routes
parent
6c856965a1
commit
f506e3dfaa
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue