From 7f7dbd68d08d7a5c306ee43bc7716e193c052ddd Mon Sep 17 00:00:00 2001 From: Augusto Gunsch Date: Sun, 17 Oct 2021 21:14:56 -0300 Subject: [PATCH] Adicionar endpoint: POST /trainer/authenticate --- api/config.py | 5 +++ api/models/trainer.py | 5 ++- api/routes/routes.py | 4 +++ api/views/errors.py | 18 +++++++++++ api/views/trainer.py | 74 +++++++++++++++++++++++++++++++++---------- 5 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 api/views/errors.py diff --git a/api/config.py b/api/config.py index 7461cfb..4f7d304 100644 --- a/api/config.py +++ b/api/config.py @@ -1,2 +1,7 @@ +import string +import random + +random_str = string.ascii_letters + string.digits + string.ascii_uppercase +SECRET_KEY = ''.join(random.choice(random_str) for i in range(12)) SQLALCHEMY_DATABASE_URI = 'sqlite:///database.db' SQLALCHEMY_TRACK_MODIFICATIONS = False diff --git a/api/models/trainer.py b/api/models/trainer.py index 91474c1..fac7329 100644 --- a/api/models/trainer.py +++ b/api/models/trainer.py @@ -1,6 +1,5 @@ from api.app import db, ma from werkzeug.security import generate_password_hash -from enum import Enum teams = ( "Team Valor", @@ -16,10 +15,10 @@ class Trainer(db.Model): __tablename__ = "trainers" id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True) - nickname = db.Column(db.String(20), nullable=False, index=True) + nickname = db.Column(db.String(20), unique=True, nullable=False, index=True) first_name = db.Column(db.String(30), nullable=False) last_name = db.Column(db.String(30), nullable=False) - email = db.Column(db.String(60), unique=True, nullable=False) + email = db.Column(db.String(60), unique=True, index=True, nullable=False) password = db.Column(db.String(200), nullable=False) team = db.Column(db.String(10), nullable=False) pokemons_owned = db.Column(db.Integer, default=0) diff --git a/api/routes/routes.py b/api/routes/routes.py index be7686c..8956b32 100644 --- a/api/routes/routes.py +++ b/api/routes/routes.py @@ -14,3 +14,7 @@ def route_get_trainers(): @app.route('/trainer', methods=['POST']) def route_create_trainer(): return trainer.post_trainer() + +@app.route("/trainer/authenticate", methods=['POST']) +def route_auth_trainer(): + return trainer.auth_trainer() diff --git a/api/views/errors.py b/api/views/errors.py new file mode 100644 index 0000000..709bb6a --- /dev/null +++ b/api/views/errors.py @@ -0,0 +1,18 @@ +def error(code, type, message, http_code=400): + return ({ + "code": code, + "type": type, + "message": message + }, http_code) + +def ParsingError(message): + return error(1, "ParsingError", message) + +def ConflictingParameters(message): + return error(2, "ConflictingParameters", message) + +def ConflictingResources(message): + return error(3, "ConflictingResources", message, http_code=409) + +def AuthenticationFailure(message): + return error(4, "AuthenticationFailure", message, http_code=401) diff --git a/api/views/trainer.py b/api/views/trainer.py index 0829bf3..6f9dc77 100644 --- a/api/views/trainer.py +++ b/api/views/trainer.py @@ -1,18 +1,18 @@ from sqlalchemy.exc import NoResultFound, IntegrityError from api.models.trainer import Trainer, trainer_schema, trainer_schemas, InvalidTeam from api.app import db +from api.app import app from flask import request, jsonify - -# função auxiliar -def error(code, type, message, http_code=400): - return ({ - "code": code, - "type": type, - "message": message - }, http_code) +from .errors import ParsingError, ConflictingParameters, ConflictingResources, AuthenticationFailure +from werkzeug.security import check_password_hash +import datetime +import jwt def get_trainer(id): - return trainer_schema.dump(Trainer.query.get(id)) + try: + return trainer_schema.dump(Trainer.query.get(id)) + except: + return jsonify({}) def get_trainers(): args = request.args @@ -21,10 +21,10 @@ def get_trainers(): limit = int(args.get("limit", -1)) offset = int(args.get("offset", 0)) except ValueError: - return error(0, "ParsingError", "Couldn't parse parameter as integer") + return ParsingError("Couldn't parse parameter as integer") if limit < -1 or offset < 0: - return error(1, "ParsingError", "Expected positive integer as parameter") + return ParsingError("Expected positive integer as parameter") nickname = args.get("nickname", "") nickname_contains = args.get("nickname_contains", "") @@ -32,7 +32,7 @@ def get_trainers(): try: if nickname: if nickname_contains: - return error(2, "ConflictingParameters", "nickname and nickname_contains are mutually exclusive") + return ConflictingParameters("nickname and nickname_contains are mutually exclusive") query = Trainer.query.filter_by(nickname=nickname) return trainer_schemas.dumps(query.all()) @@ -45,14 +45,20 @@ def get_trainers(): except NoResultFound: return jsonify([]) +def get_trainer_by_email(email): + try: + return Trainer.query.filter_by(email=email).one() + except: + return None + def post_trainer(): try: json = request.get_json() except: - return error(3, "ParsingError", "Failed to parse JSON content") + return ParsingError("Failed to parse JSON body") if type(json) is not dict: - return error(4, "ParsingError", "Expected JSON object as content") + return ParsingError("Expected JSON object as body") try: nickname = json["nickname"] @@ -76,8 +82,42 @@ def post_trainer(): return trainer_schema.dump(trainer) except InvalidTeam: - return error(5, "ParsingError", "Field team is invalid") + return ParsingError("Field team is invalid") except KeyError: - return error(6, "ParsingError", "Missing JSON object fields") + return ParsingError("Missing JSON object fields") except IntegrityError: - return error(7, "ConflictingResource", "Trainer with the same nickname or email already exists", http_code=500) + return ConflictingResources("Trainer with the same nickname or email already exists", http_code=500) + +def auth_trainer(): + try: + auth = request.get_json() + except: + return ParsingError("Failed to parse JSON body") + + if type(auth) is not dict: + return ParsingError("Expected JSON object as body") + + try: + email = auth["email"] + password = auth["password"] + except KeyError: + return AuthenticationFailure("Login required") + + trainer = get_trainer_by_email(email) + if not trainer: + return AuthenticationFailure("Trainer not found") + + if trainer and check_password_hash(trainer.password, password): + token = jwt.encode( + { + "username": trainer.nickname, + "exp": datetime.datetime.now() + datetime.timedelta(hours=12) + }, + app.config["SECRET_KEY"]) + return jsonify( + { + "id": trainer.id, + "token": token + }) + + return AuthenticationFailure("Invalid login")