import datetime import os import jwt from fittrackee import appLog, bcrypt, db from fittrackee.tasks import reset_password_email from flask import Blueprint, current_app, jsonify, request from sqlalchemy import exc, or_ from werkzeug.exceptions import RequestEntityTooLarge from werkzeug.utils import secure_filename from ..activities.utils_files import get_absolute_file_path from .models import User from .utils import ( authenticate, check_passwords, display_readable_file_size, get_readable_duration, register_controls, verify_extension_and_size, ) from .utils_token import decode_user_token auth_blueprint = Blueprint('auth', __name__) @auth_blueprint.route('/auth/register', methods=['POST']) def register_user(): """ register a user **Example request**: .. sourcecode:: http POST /api/auth/register HTTP/1.1 Content-Type: application/json **Example responses**: - successful registration .. sourcecode:: http HTTP/1.1 201 CREATED Content-Type: application/json { "auth_token": "JSON Web Token", "message": "Successfully registered.", "status": "success" } - error on registration .. sourcecode:: http HTTP/1.1 400 BAD REQUEST Content-Type: application/json { "message": "Errors: Valid email must be provided.\\n", "status": "error" } := user_mandatory_data: response_object = {'status': 'error', 'message': 'Invalid payload.'} return jsonify(response_object), 400 first_name = post_data.get('first_name') last_name = post_data.get('last_name') bio = post_data.get('bio') birth_date = post_data.get('birth_date') language = post_data.get('language') location = post_data.get('location') password = post_data.get('password') password_conf = post_data.get('password_conf') timezone = post_data.get('timezone') weekm = post_data.get('weekm') if password is not None and password != '': message = check_passwords(password, password_conf) if message != '': response_object = {'status': 'error', 'message': message} return jsonify(response_object), 400 password = bcrypt.generate_password_hash( password, current_app.config.get('BCRYPT_LOG_ROUNDS') ).decode() try: user = User.query.filter_by(id=auth_user_id).first() user.first_name = first_name user.last_name = last_name user.bio = bio user.language = language user.location = location user.birth_date = ( datetime.datetime.strptime(birth_date, '%Y-%m-%d') if birth_date else None ) if password is not None and password != '': user.password = password user.timezone = timezone user.weekm = weekm db.session.commit() response_object = { 'status': 'success', 'message': 'User profile updated.', 'data': user.serialize(), } return jsonify(response_object), 200 # handler errors except (exc.IntegrityError, exc.OperationalError, ValueError) as e: db.session.rollback() appLog.error(e) response_object = { 'status': 'error', 'message': 'Error. Please try again or contact the administrator.', } return jsonify(response_object), 500 @auth_blueprint.route('/auth/picture', methods=['POST']) @authenticate def edit_picture(auth_user_id): """ update authenticated user picture **Example request**: .. sourcecode:: http POST /api/auth/picture HTTP/1.1 Content-Type: multipart/form-data **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "message": "User picture updated.", "status": "success" } :form file: image file (allowed extensions: .jpg, .png, .gif) :reqheader Authorization: OAuth 2.0 Bearer Token :statuscode 200: User picture updated. :statuscode 400: - Invalid payload. - No file part. - No selected file. - File extension not allowed. :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 413: Error during picture update: file size exceeds 1.0MB. :statuscode 500: Error during picture update. """ try: response_object, response_code = verify_extension_and_size( 'picture', request ) except RequestEntityTooLarge as e: appLog.error(e) max_file_size = current_app.config['MAX_CONTENT_LENGTH'] response_object = { 'status': 'fail', 'message': 'Error during picture update, file size exceeds ' f'{display_readable_file_size(max_file_size)}.', } return jsonify(response_object), 413 if response_object['status'] != 'success': return jsonify(response_object), response_code file = request.files['file'] filename = secure_filename(file.filename) dirpath = os.path.join( current_app.config['UPLOAD_FOLDER'], 'pictures', str(auth_user_id) ) if not os.path.exists(dirpath): os.makedirs(dirpath) absolute_picture_path = os.path.join(dirpath, filename) relative_picture_path = os.path.join( 'pictures', str(auth_user_id), filename ) try: user = User.query.filter_by(id=auth_user_id).first() if user.picture is not None: old_picture_path = get_absolute_file_path(user.picture) if os.path.isfile(get_absolute_file_path(old_picture_path)): os.remove(old_picture_path) file.save(absolute_picture_path) user.picture = relative_picture_path db.session.commit() response_object = { 'status': 'success', 'message': 'User picture updated.', } return jsonify(response_object), 200 except (exc.IntegrityError, ValueError) as e: db.session.rollback() appLog.error(e) response_object = { 'status': 'fail', 'message': 'Error during picture update.', } return jsonify(response_object), 500 @auth_blueprint.route('/auth/picture', methods=['DELETE']) @authenticate def del_picture(auth_user_id): """ delete authenticated user picture **Example request**: .. sourcecode:: http DELETE /api/auth/picture HTTP/1.1 Content-Type: application/json **Example response**: .. sourcecode:: http HTTP/1.1 204 NO CONTENT Content-Type: application/json :reqheader Authorization: OAuth 2.0 Bearer Token :statuscode 204: picture deleted :statuscode 401: - Provide a valid auth token. - Signature expired. Please log in again. - Invalid token. Please log in again. :statuscode 500: Error during picture deletion. """ try: user = User.query.filter_by(id=auth_user_id).first() picture_path = get_absolute_file_path(user.picture) if os.path.isfile(picture_path): os.remove(picture_path) user.picture = None db.session.commit() response_object = {'status': 'no content'} return jsonify(response_object), 204 except (exc.IntegrityError, ValueError) as e: db.session.rollback() appLog.error(e) response_object = { 'status': 'fail', 'message': 'Error during picture deletion.', } return jsonify(response_object), 500 @auth_blueprint.route('/auth/password/reset-request', methods=['POST']) def request_password_reset(): """ handle password reset request **Example request**: .. sourcecode:: http POST /api/auth/password/reset-request HTTP/1.1 Content-Type: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "message": "Password reset request processed.", "status": "success" } :