2018-01-01 16:59:46 +01:00
|
|
|
import datetime
|
2018-01-01 21:54:03 +01:00
|
|
|
import os
|
2018-01-14 20:49:35 +01:00
|
|
|
|
2020-05-17 16:42:44 +02:00
|
|
|
import jwt
|
2020-09-16 15:41:02 +02:00
|
|
|
from fittrackee import appLog, bcrypt, db
|
|
|
|
from fittrackee.tasks import reset_password_email
|
2018-01-01 22:36:44 +01:00
|
|
|
from flask import Blueprint, current_app, jsonify, request
|
2017-12-16 21:00:46 +01:00
|
|
|
from sqlalchemy import exc, or_
|
2019-08-31 14:11:00 +02:00
|
|
|
from werkzeug.exceptions import RequestEntityTooLarge
|
2018-01-01 21:54:03 +01:00
|
|
|
from werkzeug.utils import secure_filename
|
2017-12-16 21:00:46 +01:00
|
|
|
|
2018-07-04 14:13:19 +02:00
|
|
|
from ..activities.utils_files import get_absolute_file_path
|
2017-12-16 21:00:46 +01:00
|
|
|
from .models import User
|
2019-08-31 14:11:00 +02:00
|
|
|
from .utils import (
|
|
|
|
authenticate,
|
2020-05-17 16:42:44 +02:00
|
|
|
check_passwords,
|
2019-08-31 14:11:00 +02:00
|
|
|
display_readable_file_size,
|
2020-07-11 19:35:20 +02:00
|
|
|
get_readable_duration,
|
2019-08-31 14:11:00 +02:00
|
|
|
register_controls,
|
|
|
|
verify_extension_and_size,
|
|
|
|
)
|
2020-05-17 16:42:44 +02:00
|
|
|
from .utils_token import decode_user_token
|
2017-12-16 21:00:46 +01:00
|
|
|
|
|
|
|
auth_blueprint = Blueprint('auth', __name__)
|
|
|
|
|
|
|
|
|
|
|
|
@auth_blueprint.route('/auth/register', methods=['POST'])
|
|
|
|
def register_user():
|
2019-07-20 14:27:05 +02:00
|
|
|
"""
|
|
|
|
register a user
|
|
|
|
|
|
|
|
**Example request**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
2019-07-20 21:57:35 +02:00
|
|
|
POST /api/auth/register HTTP/1.1
|
2019-07-20 14:27:05 +02:00
|
|
|
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"
|
|
|
|
}
|
|
|
|
|
|
|
|
:<json string username: user name (3 to 12 characters required)
|
|
|
|
:<json string email: user email
|
|
|
|
:<json string password: password (8 characters required)
|
|
|
|
:<json string password_conf: password confirmation
|
|
|
|
|
|
|
|
:statuscode 201: Successfully registered.
|
|
|
|
:statuscode 400:
|
|
|
|
- Invalid payload.
|
|
|
|
- Sorry. That user already exists.
|
|
|
|
- Errors:
|
2019-09-16 17:54:21 +02:00
|
|
|
- 3 to 12 characters required for usernanme.
|
2019-07-20 14:27:05 +02:00
|
|
|
- Valid email must be provided.
|
|
|
|
- Password and password confirmation don't match.
|
2019-09-16 17:54:21 +02:00
|
|
|
- 8 characters required for password.
|
2019-08-25 12:06:58 +02:00
|
|
|
:statuscode 403:
|
|
|
|
Error. Registration is disabled.
|
2019-07-20 14:27:05 +02:00
|
|
|
:statuscode 500:
|
|
|
|
Error. Please try again or contact the administrator.
|
|
|
|
|
|
|
|
"""
|
2019-11-13 18:40:01 +01:00
|
|
|
if not current_app.config.get('is_registration_enabled'):
|
2019-08-25 12:06:58 +02:00
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
|
|
|
'message': 'Error. Registration is disabled.',
|
|
|
|
}
|
|
|
|
return jsonify(response_object), 403
|
2017-12-16 21:00:46 +01:00
|
|
|
# get post data
|
|
|
|
post_data = request.get_json()
|
2019-08-28 13:25:39 +02:00
|
|
|
if (
|
|
|
|
not post_data
|
|
|
|
or post_data.get('username') is None
|
|
|
|
or post_data.get('email') is None
|
|
|
|
or post_data.get('password') is None
|
|
|
|
or post_data.get('password_conf') is None
|
|
|
|
):
|
|
|
|
response_object = {'status': 'error', 'message': 'Invalid payload.'}
|
2017-12-16 21:00:46 +01:00
|
|
|
return jsonify(response_object), 400
|
|
|
|
username = post_data.get('username')
|
|
|
|
email = post_data.get('email')
|
|
|
|
password = post_data.get('password')
|
2018-01-01 11:10:39 +01:00
|
|
|
password_conf = post_data.get('password_conf')
|
|
|
|
|
2018-05-13 18:36:31 +02:00
|
|
|
try:
|
|
|
|
ret = register_controls(username, email, password, password_conf)
|
|
|
|
except TypeError as e:
|
|
|
|
db.session.rollback()
|
|
|
|
appLog.error(e)
|
|
|
|
|
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'Error. Please try again or contact the administrator.',
|
2018-05-13 18:36:31 +02:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 500
|
2018-01-01 11:10:39 +01:00
|
|
|
if ret != '':
|
2019-09-16 17:54:21 +02:00
|
|
|
response_object = {'status': 'error', 'message': ret}
|
2018-01-01 11:10:39 +01:00
|
|
|
return jsonify(response_object), 400
|
|
|
|
|
2017-12-16 21:00:46 +01:00
|
|
|
try:
|
|
|
|
# check for existing user
|
|
|
|
user = User.query.filter(
|
2019-08-28 13:25:39 +02:00
|
|
|
or_(User.username == username, User.email == email)
|
|
|
|
).first()
|
2017-12-16 21:00:46 +01:00
|
|
|
if not user:
|
|
|
|
# add new user to db
|
2019-08-28 13:25:39 +02:00
|
|
|
new_user = User(username=username, email=email, password=password)
|
2018-06-11 16:44:49 +02:00
|
|
|
new_user.timezone = 'Europe/Paris'
|
2017-12-16 21:00:46 +01:00
|
|
|
db.session.add(new_user)
|
|
|
|
db.session.commit()
|
|
|
|
# generate auth token
|
|
|
|
auth_token = new_user.encode_auth_token(new_user.id)
|
|
|
|
response_object = {
|
|
|
|
'status': 'success',
|
|
|
|
'message': 'Successfully registered.',
|
2019-08-28 13:25:39 +02:00
|
|
|
'auth_token': auth_token.decode(),
|
2017-12-16 21:00:46 +01:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 201
|
|
|
|
else:
|
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'Sorry. That user already exists.',
|
2017-12-16 21:00:46 +01:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 400
|
|
|
|
# handler errors
|
|
|
|
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
|
|
|
db.session.rollback()
|
2017-12-17 09:16:08 +01:00
|
|
|
appLog.error(e)
|
2017-12-25 17:45:28 +01:00
|
|
|
|
2017-12-16 21:00:46 +01:00
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'Error. Please try again or contact the administrator.',
|
2017-12-16 21:00:46 +01:00
|
|
|
}
|
2018-05-13 18:36:31 +02:00
|
|
|
return jsonify(response_object), 500
|
2017-12-16 21:00:46 +01:00
|
|
|
|
|
|
|
|
|
|
|
@auth_blueprint.route('/auth/login', methods=['POST'])
|
|
|
|
def login_user():
|
2019-07-20 14:27:05 +02:00
|
|
|
"""
|
|
|
|
user login
|
|
|
|
|
|
|
|
**Example request**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
2019-07-20 21:57:35 +02:00
|
|
|
POST /api/auth/login HTTP/1.1
|
2019-07-20 14:27:05 +02:00
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
**Example responses**:
|
|
|
|
|
|
|
|
- successful login
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
|
|
|
HTTP/1.1 200 OK
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
{
|
|
|
|
"auth_token": "JSON Web Token",
|
|
|
|
"message": "Successfully logged in.",
|
|
|
|
"status": "success"
|
|
|
|
}
|
|
|
|
|
|
|
|
- error on login
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
|
|
|
HTTP/1.1 404 NOT FOUND
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
{
|
|
|
|
"message": "Invalid credentials.",
|
|
|
|
"status": "error"
|
|
|
|
}
|
|
|
|
|
|
|
|
:<json string email: user email
|
|
|
|
:<json string password_conf: password confirmation
|
|
|
|
|
|
|
|
:statuscode 200: Successfully logged in.
|
|
|
|
:statuscode 404: Invalid credentials.
|
|
|
|
:statuscode 500: Error. Please try again or contact the administrator.
|
|
|
|
|
|
|
|
"""
|
2017-12-16 21:00:46 +01:00
|
|
|
# get post data
|
|
|
|
post_data = request.get_json()
|
|
|
|
if not post_data:
|
2019-08-28 13:25:39 +02:00
|
|
|
response_object = {'status': 'error', 'message': 'Invalid payload.'}
|
2017-12-16 21:00:46 +01:00
|
|
|
return jsonify(response_object), 400
|
|
|
|
email = post_data.get('email')
|
|
|
|
password = post_data.get('password')
|
|
|
|
try:
|
|
|
|
# check for existing user
|
|
|
|
user = User.query.filter(User.email == email).first()
|
|
|
|
if user and bcrypt.check_password_hash(user.password, password):
|
|
|
|
# generate auth token
|
|
|
|
auth_token = user.encode_auth_token(user.id)
|
|
|
|
response_object = {
|
|
|
|
'status': 'success',
|
|
|
|
'message': 'Successfully logged in.',
|
2019-08-28 13:25:39 +02:00
|
|
|
'auth_token': auth_token.decode(),
|
2017-12-16 21:00:46 +01:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 200
|
|
|
|
else:
|
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'Invalid credentials.',
|
2017-12-16 21:00:46 +01:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 404
|
|
|
|
# handler errors
|
|
|
|
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
|
|
|
db.session.rollback()
|
2017-12-17 09:16:08 +01:00
|
|
|
appLog.error(e)
|
2017-12-16 21:00:46 +01:00
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'Error. Please try again or contact the administrator.',
|
2017-12-16 21:00:46 +01:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 500
|
|
|
|
|
|
|
|
|
|
|
|
@auth_blueprint.route('/auth/logout', methods=['GET'])
|
2017-12-25 17:45:28 +01:00
|
|
|
@authenticate
|
2020-02-08 14:49:37 +01:00
|
|
|
def logout_user(auth_user_id):
|
2019-07-20 14:27:05 +02:00
|
|
|
"""
|
|
|
|
user logout
|
|
|
|
|
|
|
|
**Example request**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
2019-07-20 21:57:35 +02:00
|
|
|
GET /api/auth/logout HTTP/1.1
|
2019-07-20 14:27:05 +02:00
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
**Example responses**:
|
|
|
|
|
|
|
|
- successful logout
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
|
|
|
HTTP/1.1 200 OK
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
{
|
|
|
|
"message": "Successfully logged out.",
|
|
|
|
"status": "success"
|
|
|
|
}
|
|
|
|
|
|
|
|
- error on login
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
|
|
|
HTTP/1.1 401 UNAUTHORIZED
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
{
|
|
|
|
"message": "Provide a valid auth token.",
|
|
|
|
"status": "error"
|
|
|
|
}
|
|
|
|
|
|
|
|
:reqheader Authorization: OAuth 2.0 Bearer Token
|
|
|
|
|
|
|
|
:statuscode 200: Successfully logged out.
|
|
|
|
:statuscode 401: Provide a valid auth token.
|
|
|
|
|
|
|
|
"""
|
2017-12-16 21:00:46 +01:00
|
|
|
# get auth token
|
|
|
|
auth_header = request.headers.get('Authorization')
|
|
|
|
if auth_header:
|
|
|
|
auth_token = auth_header.split(" ")[1]
|
|
|
|
resp = User.decode_auth_token(auth_token)
|
2020-02-08 14:49:37 +01:00
|
|
|
if not isinstance(auth_user_id, str):
|
2017-12-16 21:00:46 +01:00
|
|
|
response_object = {
|
|
|
|
'status': 'success',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'Successfully logged out.',
|
2017-12-16 21:00:46 +01:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 200
|
|
|
|
else:
|
2019-08-28 13:25:39 +02:00
|
|
|
response_object = {'status': 'error', 'message': resp}
|
2017-12-16 21:00:46 +01:00
|
|
|
return jsonify(response_object), 401
|
|
|
|
else:
|
|
|
|
response_object = {
|
|
|
|
'status': 'error',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'Provide a valid auth token.',
|
2017-12-16 21:00:46 +01:00
|
|
|
}
|
2018-05-13 18:36:31 +02:00
|
|
|
return jsonify(response_object), 401
|
2017-12-25 17:45:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
@auth_blueprint.route('/auth/profile', methods=['GET'])
|
|
|
|
@authenticate
|
2020-02-08 14:49:37 +01:00
|
|
|
def get_authenticated_user_profile(auth_user_id):
|
2019-07-20 14:27:05 +02:00
|
|
|
"""
|
|
|
|
get authenticated user info
|
|
|
|
|
|
|
|
**Example request**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
2019-07-20 21:57:35 +02:00
|
|
|
GET /api/auth/profile HTTP/1.1
|
2019-07-20 14:27:05 +02:00
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
**Example response**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
|
|
|
HTTP/1.1 200 OK
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
{
|
|
|
|
"data": {
|
|
|
|
"admin": false,
|
|
|
|
"bio": null,
|
|
|
|
"birth_date": null,
|
|
|
|
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
|
2019-09-01 11:40:39 +02:00
|
|
|
"email": "sam@example.com",
|
2019-07-20 14:27:05 +02:00
|
|
|
"first_name": null,
|
2019-09-16 14:19:21 +02:00
|
|
|
"language": "en",
|
2019-07-20 14:27:05 +02:00
|
|
|
"last_name": null,
|
|
|
|
"location": null,
|
|
|
|
"nb_activities": 6,
|
|
|
|
"nb_sports": 3,
|
|
|
|
"picture": false,
|
2019-09-23 20:01:11 +02:00
|
|
|
"sports_list": [
|
|
|
|
1,
|
|
|
|
4,
|
|
|
|
6
|
|
|
|
],
|
2019-07-20 14:27:05 +02:00
|
|
|
"timezone": "Europe/Paris",
|
|
|
|
"total_distance": 67.895,
|
|
|
|
"total_duration": "6:50:27",
|
2019-09-01 11:40:39 +02:00
|
|
|
"username": "sam",
|
|
|
|
"weekm": false
|
2019-07-20 14:27:05 +02:00
|
|
|
},
|
|
|
|
"status": "success"
|
|
|
|
}
|
|
|
|
|
|
|
|
:reqheader Authorization: OAuth 2.0 Bearer Token
|
|
|
|
|
|
|
|
:statuscode 200: success.
|
2019-07-20 21:57:35 +02:00
|
|
|
:statuscode 401:
|
|
|
|
- Provide a valid auth token.
|
|
|
|
- Signature expired. Please log in again.
|
|
|
|
- Invalid token. Please log in again.
|
2019-07-20 14:27:05 +02:00
|
|
|
|
|
|
|
"""
|
2020-02-08 14:49:37 +01:00
|
|
|
user = User.query.filter_by(id=auth_user_id).first()
|
2019-08-28 13:25:39 +02:00
|
|
|
response_object = {'status': 'success', 'data': user.serialize()}
|
2017-12-25 17:45:28 +01:00
|
|
|
return jsonify(response_object), 200
|
2018-01-01 16:59:46 +01:00
|
|
|
|
|
|
|
|
|
|
|
@auth_blueprint.route('/auth/profile/edit', methods=['POST'])
|
|
|
|
@authenticate
|
2020-02-08 14:49:37 +01:00
|
|
|
def edit_user(auth_user_id):
|
2019-07-20 14:27:05 +02:00
|
|
|
"""
|
|
|
|
edit authenticated user
|
|
|
|
|
|
|
|
**Example request**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
2019-07-20 21:57:35 +02:00
|
|
|
POST /api/auth/profile/edit HTTP/1.1
|
2019-07-20 14:27:05 +02:00
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
**Example response**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
|
|
|
HTTP/1.1 200 OK
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
{
|
|
|
|
"data": {
|
|
|
|
"admin": false,
|
|
|
|
"bio": null,
|
|
|
|
"birth_date": null,
|
|
|
|
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
|
2019-09-01 11:40:39 +02:00
|
|
|
"email": "sam@example.com",
|
2019-07-20 14:27:05 +02:00
|
|
|
"first_name": null,
|
2019-09-16 14:19:21 +02:00
|
|
|
"language": "en",
|
2019-07-20 14:27:05 +02:00
|
|
|
"last_name": null,
|
|
|
|
"location": null,
|
|
|
|
"nb_activities": 6,
|
|
|
|
"nb_sports": 3,
|
|
|
|
"picture": false,
|
2019-09-23 20:01:11 +02:00
|
|
|
"sports_list": [
|
|
|
|
1,
|
|
|
|
4,
|
|
|
|
6
|
|
|
|
],
|
2019-07-20 14:27:05 +02:00
|
|
|
"timezone": "Europe/Paris",
|
|
|
|
"total_distance": 67.895,
|
|
|
|
"total_duration": "6:50:27",
|
|
|
|
"username": "sam"
|
2019-09-01 11:40:39 +02:00
|
|
|
"weekm": true,
|
2019-07-20 14:27:05 +02:00
|
|
|
},
|
2019-09-01 11:40:39 +02:00
|
|
|
"message": "User profile updated.",
|
2019-07-20 14:27:05 +02:00
|
|
|
"status": "success"
|
|
|
|
}
|
|
|
|
|
|
|
|
:<json string first_name: user first name
|
|
|
|
:<json string last_name: user last name
|
|
|
|
:<json string location: user location
|
|
|
|
:<json string bio: user biography
|
|
|
|
:<json string birth_date: user birth date (format: ``%Y-%m-%d``)
|
|
|
|
:<json string password: user password
|
|
|
|
:<json string password_conf: user password confirmation
|
|
|
|
:<json string timezone: user time zone
|
2019-08-31 16:33:46 +02:00
|
|
|
:<json string weekm: does week start on Monday?
|
2019-09-16 14:19:21 +02:00
|
|
|
:<json string language: language preferences
|
2019-07-20 14:27:05 +02:00
|
|
|
|
|
|
|
:reqheader Authorization: OAuth 2.0 Bearer Token
|
|
|
|
|
|
|
|
:statuscode 200: User profile updated.
|
|
|
|
:statuscode 400:
|
|
|
|
- Invalid payload.
|
|
|
|
- Password and password confirmation don't match.
|
2019-07-20 21:57:35 +02:00
|
|
|
:statuscode 401:
|
|
|
|
- Provide a valid auth token.
|
|
|
|
- Signature expired. Please log in again.
|
|
|
|
- Invalid token. Please log in again.
|
2019-07-20 14:27:05 +02:00
|
|
|
:statuscode 500: Error. Please try again or contact the administrator.
|
|
|
|
|
|
|
|
"""
|
2018-01-01 16:59:46 +01:00
|
|
|
# get post data
|
|
|
|
post_data = request.get_json()
|
2019-08-31 16:33:46 +02:00
|
|
|
user_mandatory_data = {
|
|
|
|
'first_name',
|
|
|
|
'last_name',
|
|
|
|
'bio',
|
|
|
|
'birth_date',
|
2019-09-16 14:19:21 +02:00
|
|
|
'language',
|
2019-08-31 16:33:46 +02:00
|
|
|
'location',
|
|
|
|
'timezone',
|
|
|
|
'weekm',
|
|
|
|
}
|
|
|
|
if not post_data or not post_data.keys() >= user_mandatory_data:
|
2019-08-28 13:25:39 +02:00
|
|
|
response_object = {'status': 'error', 'message': 'Invalid payload.'}
|
2018-01-01 16:59:46 +01:00
|
|
|
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')
|
2019-09-16 14:19:21 +02:00
|
|
|
language = post_data.get('language')
|
2018-01-01 16:59:46 +01:00
|
|
|
location = post_data.get('location')
|
2018-01-01 17:50:12 +01:00
|
|
|
password = post_data.get('password')
|
|
|
|
password_conf = post_data.get('password_conf')
|
2018-06-11 15:10:18 +02:00
|
|
|
timezone = post_data.get('timezone')
|
2019-08-31 16:33:46 +02:00
|
|
|
weekm = post_data.get('weekm')
|
2018-01-01 17:50:12 +01:00
|
|
|
|
|
|
|
if password is not None and password != '':
|
2020-05-17 16:42:44 +02:00
|
|
|
message = check_passwords(password, password_conf)
|
|
|
|
if message != '':
|
2019-08-28 14:34:18 +02:00
|
|
|
response_object = {'status': 'error', 'message': message}
|
2018-01-01 17:50:12 +01:00
|
|
|
return jsonify(response_object), 400
|
2020-05-17 16:42:44 +02:00
|
|
|
password = bcrypt.generate_password_hash(
|
|
|
|
password, current_app.config.get('BCRYPT_LOG_ROUNDS')
|
|
|
|
).decode()
|
2018-01-01 17:50:12 +01:00
|
|
|
|
2018-01-01 16:59:46 +01:00
|
|
|
try:
|
2020-02-08 14:49:37 +01:00
|
|
|
user = User.query.filter_by(id=auth_user_id).first()
|
2018-01-01 16:59:46 +01:00
|
|
|
user.first_name = first_name
|
|
|
|
user.last_name = last_name
|
|
|
|
user.bio = bio
|
2019-09-16 14:19:21 +02:00
|
|
|
user.language = language
|
2018-01-01 16:59:46 +01:00
|
|
|
user.location = location
|
|
|
|
user.birth_date = (
|
2018-05-08 18:26:42 +02:00
|
|
|
datetime.datetime.strptime(birth_date, '%Y-%m-%d')
|
2018-01-01 16:59:46 +01:00
|
|
|
if birth_date
|
|
|
|
else None
|
|
|
|
)
|
2018-01-01 17:50:12 +01:00
|
|
|
if password is not None and password != '':
|
|
|
|
user.password = password
|
2018-06-11 15:10:18 +02:00
|
|
|
user.timezone = timezone
|
2019-08-31 16:33:46 +02:00
|
|
|
user.weekm = weekm
|
2018-01-01 16:59:46 +01:00
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
response_object = {
|
|
|
|
'status': 'success',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'User profile updated.',
|
2019-08-31 16:33:46 +02:00
|
|
|
'data': user.serialize(),
|
2018-01-01 16:59:46 +01:00
|
|
|
}
|
|
|
|
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',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'Error. Please try again or contact the administrator.',
|
2018-01-01 16:59:46 +01:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 500
|
2018-01-01 21:54:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
@auth_blueprint.route('/auth/picture', methods=['POST'])
|
|
|
|
@authenticate
|
2020-02-08 14:49:37 +01:00
|
|
|
def edit_picture(auth_user_id):
|
2019-07-20 14:27:05 +02:00
|
|
|
"""
|
|
|
|
update authenticated user picture
|
|
|
|
|
|
|
|
**Example request**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
2019-07-20 21:57:35 +02:00
|
|
|
POST /api/auth/picture HTTP/1.1
|
2019-07-20 14:27:05 +02:00
|
|
|
Content-Type: multipart/form-data
|
|
|
|
|
|
|
|
**Example response**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
|
|
|
HTTP/1.1 200 OK
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
{
|
2019-09-01 11:40:39 +02:00
|
|
|
"message": "User picture updated.",
|
2019-07-20 14:27:05 +02:00
|
|
|
"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.
|
2019-07-20 21:57:35 +02:00
|
|
|
:statuscode 401:
|
|
|
|
- Provide a valid auth token.
|
|
|
|
- Signature expired. Please log in again.
|
|
|
|
- Invalid token. Please log in again.
|
2019-08-31 14:11:00 +02:00
|
|
|
:statuscode 413: Error during picture update: file size exceeds 1.0MB.
|
2019-07-20 14:27:05 +02:00
|
|
|
:statuscode 500: Error during picture update.
|
|
|
|
|
|
|
|
"""
|
2019-08-31 14:11:00 +02:00
|
|
|
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',
|
2019-09-16 17:54:21 +02:00
|
|
|
'message': 'Error during picture update, file size exceeds '
|
2019-08-31 14:11:00 +02:00
|
|
|
f'{display_readable_file_size(max_file_size)}.',
|
|
|
|
}
|
|
|
|
return jsonify(response_object), 413
|
2018-05-01 17:51:38 +02:00
|
|
|
if response_object['status'] != 'success':
|
2019-08-31 14:11:00 +02:00
|
|
|
return jsonify(response_object), response_code
|
2018-01-01 21:54:03 +01:00
|
|
|
|
2018-05-01 17:51:38 +02:00
|
|
|
file = request.files['file']
|
2018-01-01 21:54:03 +01:00
|
|
|
filename = secure_filename(file.filename)
|
|
|
|
dirpath = os.path.join(
|
2020-02-08 14:49:37 +01:00
|
|
|
current_app.config['UPLOAD_FOLDER'], 'pictures', str(auth_user_id)
|
2018-01-01 21:54:03 +01:00
|
|
|
)
|
|
|
|
if not os.path.exists(dirpath):
|
|
|
|
os.makedirs(dirpath)
|
2018-07-04 14:13:19 +02:00
|
|
|
absolute_picture_path = os.path.join(dirpath, filename)
|
2020-02-08 14:49:37 +01:00
|
|
|
relative_picture_path = os.path.join(
|
|
|
|
'pictures', str(auth_user_id), filename
|
|
|
|
)
|
2018-01-01 21:54:03 +01:00
|
|
|
|
|
|
|
try:
|
2020-02-08 14:49:37 +01:00
|
|
|
user = User.query.filter_by(id=auth_user_id).first()
|
2018-07-04 14:13:19 +02:00
|
|
|
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
|
2018-01-01 21:54:03 +01:00
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
response_object = {
|
|
|
|
'status': 'success',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'User picture updated.',
|
2018-01-01 21:54:03 +01:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 200
|
|
|
|
|
|
|
|
except (exc.IntegrityError, ValueError) as e:
|
|
|
|
db.session.rollback()
|
|
|
|
appLog.error(e)
|
|
|
|
response_object = {
|
|
|
|
'status': 'fail',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'Error during picture update.',
|
2018-01-01 21:54:03 +01:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 500
|
|
|
|
|
|
|
|
|
|
|
|
@auth_blueprint.route('/auth/picture', methods=['DELETE'])
|
|
|
|
@authenticate
|
2020-02-08 14:49:37 +01:00
|
|
|
def del_picture(auth_user_id):
|
2019-07-20 14:27:05 +02:00
|
|
|
"""
|
|
|
|
delete authenticated user picture
|
|
|
|
|
|
|
|
**Example request**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
2019-07-20 21:57:35 +02:00
|
|
|
DELETE /api/auth/picture HTTP/1.1
|
2019-07-20 14:27:05 +02:00
|
|
|
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
|
2019-07-20 21:57:35 +02:00
|
|
|
:statuscode 401:
|
|
|
|
- Provide a valid auth token.
|
|
|
|
- Signature expired. Please log in again.
|
|
|
|
- Invalid token. Please log in again.
|
2019-07-20 14:27:05 +02:00
|
|
|
:statuscode 500: Error during picture deletion.
|
|
|
|
|
|
|
|
"""
|
2018-01-01 21:54:03 +01:00
|
|
|
try:
|
2020-02-08 14:49:37 +01:00
|
|
|
user = User.query.filter_by(id=auth_user_id).first()
|
2018-07-04 14:13:19 +02:00
|
|
|
picture_path = get_absolute_file_path(user.picture)
|
|
|
|
if os.path.isfile(picture_path):
|
|
|
|
os.remove(picture_path)
|
2018-01-01 21:54:03 +01:00
|
|
|
user.picture = None
|
|
|
|
db.session.commit()
|
|
|
|
|
2019-08-28 13:25:39 +02:00
|
|
|
response_object = {'status': 'no content'}
|
2018-06-12 12:51:23 +02:00
|
|
|
return jsonify(response_object), 204
|
2018-01-01 21:54:03 +01:00
|
|
|
|
|
|
|
except (exc.IntegrityError, ValueError) as e:
|
|
|
|
db.session.rollback()
|
|
|
|
appLog.error(e)
|
|
|
|
response_object = {
|
|
|
|
'status': 'fail',
|
2019-08-28 13:25:39 +02:00
|
|
|
'message': 'Error during picture deletion.',
|
2018-01-01 21:54:03 +01:00
|
|
|
}
|
|
|
|
return jsonify(response_object), 500
|
2020-05-10 17:08:18 +02:00
|
|
|
|
|
|
|
|
2020-05-17 16:42:44 +02:00
|
|
|
@auth_blueprint.route('/auth/password/reset-request', methods=['POST'])
|
2020-05-10 17:08:18 +02:00
|
|
|
def request_password_reset():
|
|
|
|
"""
|
|
|
|
handle password reset request
|
|
|
|
|
|
|
|
**Example request**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
2020-05-17 16:42:44 +02:00
|
|
|
POST /api/auth/password/reset-request HTTP/1.1
|
2020-05-10 17:08:18 +02:00
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
**Example response**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
|
|
|
HTTP/1.1 200 OK
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
{
|
|
|
|
"message": "Password reset request processed.",
|
|
|
|
"status": "success"
|
|
|
|
}
|
|
|
|
|
|
|
|
:<json string email: user email
|
|
|
|
|
|
|
|
:statuscode 200: Password reset request processed.
|
|
|
|
:statuscode 400: Invalid payload.
|
|
|
|
|
|
|
|
"""
|
|
|
|
post_data = request.get_json()
|
|
|
|
if not post_data or post_data.get('email') is None:
|
|
|
|
response_object = {'status': 'error', 'message': 'Invalid payload.'}
|
|
|
|
return jsonify(response_object), 400
|
|
|
|
email = post_data.get('email')
|
|
|
|
|
|
|
|
user = User.query.filter(User.email == email).first()
|
|
|
|
if user:
|
2020-05-17 16:42:44 +02:00
|
|
|
password_reset_token = user.encode_password_reset_token(user.id)
|
|
|
|
ui_url = current_app.config['UI_URL']
|
|
|
|
email_data = {
|
2020-07-11 19:35:20 +02:00
|
|
|
'expiration_delay': get_readable_duration(
|
|
|
|
current_app.config.get('PASSWORD_TOKEN_EXPIRATION_SECONDS'),
|
|
|
|
'en' if user.language is None else user.language,
|
|
|
|
),
|
2020-05-17 16:42:44 +02:00
|
|
|
'username': user.username,
|
|
|
|
'password_reset_url': (
|
|
|
|
f'{ui_url}/password-reset?token={password_reset_token.decode()}' # noqa
|
|
|
|
),
|
|
|
|
'operating_system': request.user_agent.platform,
|
|
|
|
'browser_name': request.user_agent.browser,
|
|
|
|
}
|
2020-07-14 22:03:56 +02:00
|
|
|
user_data = {
|
|
|
|
'language': user.language if user.language else 'en',
|
|
|
|
'email': user.email,
|
|
|
|
}
|
|
|
|
reset_password_email.send(user_data, email_data)
|
2020-05-10 17:08:18 +02:00
|
|
|
response_object = {
|
|
|
|
'status': 'success',
|
|
|
|
'message': 'Password reset request processed.',
|
|
|
|
}
|
|
|
|
return jsonify(response_object), 200
|
2020-05-17 16:42:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
@auth_blueprint.route('/auth/password/update', methods=['POST'])
|
|
|
|
def update_password():
|
|
|
|
"""
|
|
|
|
update user password
|
|
|
|
|
|
|
|
**Example request**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
|
|
|
POST /api/auth/password/update HTTP/1.1
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
**Example response**:
|
|
|
|
|
|
|
|
.. sourcecode:: http
|
|
|
|
|
|
|
|
HTTP/1.1 200 OK
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
{
|
|
|
|
"message": "Password updated.",
|
|
|
|
"status": "success"
|
|
|
|
}
|
|
|
|
|
|
|
|
:<json string password: password (8 characters required)
|
|
|
|
:<json string password_conf: password confirmation
|
|
|
|
:<json string token: password reset token
|
|
|
|
|
|
|
|
:statuscode 200: Password updated.
|
|
|
|
:statuscode 400: Invalid payload.
|
|
|
|
:statuscode 401: Invalid token.
|
|
|
|
:statuscode 500: Error. Please try again or contact the administrator.
|
|
|
|
|
|
|
|
"""
|
|
|
|
post_data = request.get_json()
|
|
|
|
if (
|
|
|
|
not post_data
|
|
|
|
or post_data.get('password') is None
|
|
|
|
or post_data.get('password_conf') is None
|
|
|
|
or post_data.get('token') is None
|
|
|
|
):
|
|
|
|
response_object = {'status': 'error', 'message': 'Invalid payload.'}
|
|
|
|
return jsonify(response_object), 400
|
|
|
|
password = post_data.get('password')
|
|
|
|
password_conf = post_data.get('password_conf')
|
|
|
|
token = post_data.get('token')
|
|
|
|
|
|
|
|
invalid_token_response_object = {
|
|
|
|
'status': 'error',
|
|
|
|
'message': 'Invalid token. Please request a new token.',
|
|
|
|
}
|
|
|
|
try:
|
|
|
|
user_id = decode_user_token(token)
|
|
|
|
except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
|
|
|
|
return jsonify(invalid_token_response_object), 401
|
|
|
|
|
|
|
|
message = check_passwords(password, password_conf)
|
|
|
|
if message != '':
|
|
|
|
response_object = {'status': 'error', 'message': message}
|
|
|
|
return jsonify(response_object), 400
|
|
|
|
|
|
|
|
user = User.query.filter(User.id == user_id).first()
|
|
|
|
if not user:
|
|
|
|
return jsonify(invalid_token_response_object), 401
|
|
|
|
try:
|
|
|
|
user.password = bcrypt.generate_password_hash(
|
|
|
|
password, current_app.config.get('BCRYPT_LOG_ROUNDS')
|
|
|
|
).decode()
|
|
|
|
db.session.commit()
|
|
|
|
response_object = {
|
|
|
|
'status': 'success',
|
|
|
|
'message': 'Password updated.',
|
|
|
|
}
|
|
|
|
return jsonify(response_object), 200
|
|
|
|
|
|
|
|
except (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
|