Ejemplo n.º 1
0
def request_password_reset():
    try:
        response, code = User.request_password_reset(data=request.get_json())
        return make_response(jsonify(response)), code
    except Exception as err:
        print(err)
        system_logging(err, exception=True)
        return make_response(
            jsonify({"message": "Error requesting password reset"})), 401
Ejemplo n.º 2
0
def verify_otp():
    try:
        data = request.get_json()
        # no data sent
        if not data:
            return {'message': 'Data is missing', 'status': -2}, 401

        # get otp
        code = data.get('otp', None)
        if not code:
            return jsonify({'message': 'OTP code is missing'})

        # get phone number
        phone_number = data.get('phone_number', None)
        if not phone_number:
            return jsonify({'message': 'Phone Number is missing'})

        # get password
        password = data.get('password', None)
        if not password:
            return jsonify({'message': 'Password is missing'})

        # Check format of phone number
        message, status = validate_phone_number(phone_number)
        if status:
            return jsonify({'message': message})
        parts = [x.strip() for x in message.split('.')]
        phone_number = parts[1].split(':')[-1].strip()

        user = User.query.filter(User.phoneNumber == phone_number).first()
        if not user:
            return jsonify({'message': 'Phone number not registered'})

        if not OTP.query.filter(OTP.code == code).first():
            return jsonify({'message': 'OTP Code not recognized'})

        otp = OTP.query.filter(OTP.code == code,
                               OTP.user == phone_number).first()
        if not otp:
            return jsonify({'message': 'OTP code not found under user'})
        if otp.is_validated:
            return jsonify({'message': 'OTP already validated'})
        otp.is_validated = True
        status = otp.save()
        if status:
            return jsonify({'message': f'Unable to complete {otp.purpose}'})

        user.is_confirmed = True
        user.set_password(password)
        status = user.save()
        if status:
            return jsonify({'message': 'Unable to set password'})
        return jsonify({'message': 'Success'})
    except Exception as err:
        print(err)
        system_logging(err, exception=True)
        return make_response(jsonify({'message': "Error verifying OTP"})), 401
Ejemplo n.º 3
0
def verify_reset_password_token(token):
    """Verify the reset password token then returns the User instance if correct or None if error"""
    try:
        user_id = jwt.decode(token,
                             app.config['RESET_PASSWORD_KEY'],
                             algorithms=["HS256"])['reset_password']
    except BaseException as err:
        system_logging(err, exception=True)
        return None
    return User.query.filter_by(user_id=user_id).first()
Ejemplo n.º 4
0
def verify_confirm_account_token(token):
    """Verify the user account confirmation token then returns the User instance if correct or None if error"""
    try:
        user_id = jwt.decode(token,
                             app.config['ADMIN_PASSWORD'],
                             algorithms=["HS256"])['confirm_account']
    except BaseException as err:
        system_logging(err, exception=True)
        return None
    return User.query.filter_by(user_id=user_id).first()
Ejemplo n.º 5
0
def token_refresh():
    try:
        current_user = request.headers.get('UserID')
        return make_response(
            jsonify({'auth_token': encode_auth_token(current_user)}))
    except Exception as err:
        print(err)
        system_logging(err, exception=True)
        return make_response(
            jsonify({"error": "Error generating access token"})), 401
Ejemplo n.º 6
0
def create_app(config_name):
    """
    This is the method that initializes modules used in the app
    :param config_name: The key for the configuration to use
    :return: Flask app
    """
    global app
    if config_name not in app_config.keys():
        config_name = 'development'
    app.config.from_object(".".join(["config", app_config[config_name]]))

    db.init_app(app)
    cors.init_app(app)
    mail.init_app(app)
    bcrypt.init_app(app)
    login_manager.init_app(app)
    migrate.init_app(app, db)
    bootstrap.init_app(app)

    login_manager.login_message = "You must be logged in to access this page."

    from app import models, errors, views

    security.init_app(app, datastore=models.user_datastore)

    # enable logging
    errors.system_logging(
        'Monitoring - A web based home monitoring system for assisted living')

    # these are blueprints, they help to organize the projects into smaller logical components based on eg user roles
    from .home import home as home_blueprint
    from .admin import admin as admin_blueprint
    from .patient import patient as patient_blueprint
    from .relative import relative as relative_blueprint
    from .caregiver import caregiver as medical_blueprint

    app.register_blueprint(home_blueprint, url_prefix='/home')
    app.register_blueprint(medical_blueprint, url_prefix='/caregiver')
    app.register_blueprint(admin_blueprint, url_prefix='/admin')
    app.register_blueprint(patient_blueprint, url_prefix='/patient')
    app.register_blueprint(relative_blueprint, url_prefix='/relative')

    # Initialize Redis and RQ
    app.redis = Redis.from_url(app.config['REDIS_URL'])
    # The queue where tasks are submitted
    queue_name = 'monitoring_tasks'
    app.task_queue = rq.Queue(queue_name, connection=app.redis)

    # Instantiate Scheduler for schedule queue
    schedule_name = "monitoring_scheduler"
    app.scheduler = Scheduler(queue_name=schedule_name, connection=app.redis)

    return app
Ejemplo n.º 7
0
def decode_auth_token(auth_token, user_id):
    """
    Decodes the auth token
    :param auth_token: The token to be decoded
    :param user_id: Unique name of user
    :return: integer|string
    """
    try:
        return _decode_token(token=auth_token, user_id=user_id, type_='auth')
    except Exception as err:
        print('DECODE ERROR {}'.format(repr(err)))
        system_logging(err, exception=True)
        return 'Error decoding token'
Ejemplo n.º 8
0
def verify_confirmation_token(token,
                              expiration_time=86400,
                              communication_end='email'):
    """Confirm the token to return the email. Default expiration time is 24 hours (86400 seconds)"""
    if not communication_end or type(communication_end) != str:
        communication_end = 'email'
    serializer = URLSafeTimedSerializer(
        app.config[f'{communication_end.upper()}_CONFIRMATION_KEY'])
    try:
        result = serializer.loads(token,
                                  salt=app.config['SECURITY_PASSWORD_SALT'],
                                  max_age=expiration_time)
    except BaseException as err:
        system_logging(err, exception=True)
        return None
    return result
Ejemplo n.º 9
0
def delete(field):
    """
    Method to delete a field from the database
    If successful, the field is committed and success status code returned
    If unsuccessful, the field is rolled back and failure status code returned
    :param field: The record to be saved
    :return: Status code. 0 -> Success, 1 -> Failure
    """
    try:
        db.session.delete(field)
        db.session.commit()
        return 0
    except Exception as err:
        print(err)
        system_logging(err, exception=True)
        db.session.rollback()
        return 1
Ejemplo n.º 10
0
def encode_refresh_token(user_id):
    """
    This function generates the (utf-8) refresh token to reissue new auth token after expiration of previous token
    :param user_id: The ID of the user from User table.
    :return: Generated JWT token as string or error message
    """
    try:
        time_now = datetime.datetime.now(tz=pytz.timezone('Africa/Nairobi'))
        expiry = time_now + datetime.timedelta(days=30)
        return _encode_jwt_token(expiry=expiry,
                                 current_time=time_now,
                                 user_id=user_id,
                                 type_='refresh')
    except Exception as err:
        print(err)
        system_logging(err, exception=True)
        return "Error generating token."
Ejemplo n.º 11
0
    def request_password_reset(cls, data: dict = None):
        try:
            # no data sent
            if not data:
                return {'message': 'Data is missing', 'status': -2}, 401

            # get phone number
            phone_number = data.get('phone_number', None)
            if not phone_number:
                return {'message': 'Phone Number is missing', 'status': 1}, 202

            # Check format of phone number
            message, status = validate_phone_number(phone_number)
            if status:
                return {'message': message, 'status': 7}, 202
            parts = [x.strip() for x in message.split('.')]
            phone_number = parts[1].split(':')[-1].strip()

            user = cls.query.filter(cls.phoneNumber == phone_number).first()
            if user:
                # Log user out of all devices
                login_sessions = UserLogin.query.filter(
                    UserLogin.user == user.user_id).all()
                if login_sessions:
                    for session in login_sessions:
                        session.logged_in = False
                        session.save()
                # Send SMS if user's status is okay
                if not app.testing and user.active and not user.suspended:
                    user.launch_task('send_reset_password_sms',
                                     'Sending reset password OTP SMS',
                                     phone_number)
            return {
                'message':
                "Check your SMS for instructions to reset your password",
                "status": 0
            }, 200
        except Exception as err:
            print(err)
            system_logging(msg=err, exception=True)
            return {
                'message':
                'Something went wrong. Please contact the administrator',
                'status': -1
            }, 401
Ejemplo n.º 12
0
def encode_auth_token(user_id):
    """
    This function generates the authentication token to be used by user to request data from or add data to the system
    :param user_id: The ID of the user from User table.
    :return: Generated JWT token as string or error message
    """
    try:
        time_now = datetime.datetime.now(tz=pytz.timezone('Africa/Nairobi'))
        if app.config['TESTING']:
            # for testing, token expires 5 seconds after login
            expiry = time_now + datetime.timedelta(days=0, seconds=5)
        else:
            # token expires 15 minutes after login
            expiry = time_now + datetime.timedelta(days=0, minutes=15)
        return _encode_jwt_token(expiry=expiry,
                                 current_time=time_now,
                                 user_id=user_id,
                                 type_='auth')
    except Exception as err:
        print(err)
        system_logging(err, exception=True)
        return "Error generating token."
Ejemplo n.º 13
0
def _decode_token(token, user_id, type_: str = 'auth'):
    """
    Decodes the token received
    :param token: The token to be decoded
    :param user_id: Unique name of user
    :return: integer|string
    """
    try:
        # First check if token has already been blacklisted
        is_blacklisted_token = BlacklistToken.check_blacklist(token)
        # If blacklisted, notify user to re-login
        if is_blacklisted_token:
            return "Token blacklisted. Please log in again"
        else:
            # Decode the valid token
            payload = jwt.decode(token,
                                 app.config.get('JWT_SECRET_KEY'),
                                 algorithms=["HS256"])
            # Check if token provided really belongs to the currently logged in user whose user_id has been given
            if user_id != payload['sub']:
                return 'Incorrect user'
            # Ensure that the token has correct access level eg type_ = 'refresh' for refreshing
            if type_ != payload['type']:
                return 'Incorrect access level'
            if payload['type'] not in ('refresh', 'auth'):
                raise JWTDecodeError("Missing or invalid claim: type")
            # Return user identified by token
            return payload['sub'], 0
    #  Token is used after it’s expired (time specified in the payload’s exp field has expired)
    except jwt.ExpiredSignatureError:
        return 'Signature expired. Please log in again.'
    #  Token supplied is not correct or malformed
    except jwt.InvalidTokenError:
        return 'Invalid token. Please log in again.'
    except Exception as err:
        print('DECODE ERROR {}'.format(repr(err)))
        system_logging(err, exception=True)
        return 'Error decoding token'
Ejemplo n.º 14
0
def logout():
    try:
        response_code = 202
        # Blacklist token
        auth_token = request.headers.get('Authorization').split(" ")[1]
        refresh_token = request.headers.get('Refresh').split(" ")[1]
        message, status = BlacklistToken.blacklist_tokens(
            auth_token, refresh_token)
        # Set response code for unsuccessful blacklisting
        if status:
            response_code = 401
        # log out after successful blacklisting
        else:
            # log user out
            username, uid = request.headers.get('UserID'), request.headers.get(
                "UniqueID")
            login_session = UserLogin.query.filter(
                UserLogin.user == username,
                UserLogin.device_id == uid).first()
            login_session.logged_in = False
            status = login_session.save()
            if not status:
                message, response_code = 'Successful logging out', 200
            else:
                message, status = 'Cannot logout user', 2
        return make_response(jsonify({
            'status': status,
            'message': message
        })), response_code
    except Exception as err:
        print(err)
        system_logging(err, exception=True)
        return make_response(
            jsonify({
                'message': "Error during logging out",
                'status': -2
            })), 401
Ejemplo n.º 15
0
def raise_alert():
    try:
        source = request.headers.get('Source', '')
        if not source:
            source = 'System'

        patient = request.headers.get('Patient', '')
        if not patient:
            system_logging('Patient not provided', exception=True)

        status = Alert(patient=patient, creator=source).save()
        if status:
            system_logging(
                f'Unable to raise alert raised by {source} for patient {patient}',
                exception=True)

        user = User.query.filter(User.user_id == patient).first()
        user.launch_task('send_alert_communication',
                         'Sending alert communication', patient)
        return jsonify({'message': 'done'})
    except Exception as err:
        print(err)
        system_logging(f"Error raising alert {err}", exception=True)
        return ''
Ejemplo n.º 16
0
def register():
    if request.method == 'POST':
        try:
            # Get sent data
            data, message, status, response_code = request.form.to_dict(
            ), '', 0, 202

            # no data sent
            if not data:
                return make_response(
                    jsonify({
                        'message': 'Data is missing',
                        'status': -1
                    })), response_code

            fullname, phone_number, id_number = data['official_names'], data[
                'phone_number'], data['id_number']
            residency, gender = data.get(
                'residency', 'Nairobi'), data.get('gender', '') or ''
            role, relative, caregiver = data['role'], data.get(
                'relative', ''), data.get('caregiver', '')

            # Ensure the key details are not missing
            if not role:
                return make_response(
                    jsonify({
                        'message': 'Role is missing',
                        'status': 1
                    })), response_code
            if not fullname:
                return make_response(
                    jsonify({
                        'message': 'Official names are missing',
                        'status': 1
                    })), response_code
            if not phone_number:
                return make_response(
                    jsonify({
                        'message': 'Phone number is missing',
                        'status': 1
                    })), response_code
            if not id_number:
                return make_response(
                    jsonify({
                        'message': "ID number is missing",
                        'status': 1
                    })), response_code

            # Confirm role given is valid
            if user_datastore.find_role(role=role) is None:
                return make_response(
                    jsonify({
                        'message': 'Non-existent role',
                        'status': -3
                    })), 401

            if role == 'patient':
                if not relative:
                    return jsonify({
                        'message': 'At least 1 relative required',
                        'status': 1
                    }), response_code
                if not User.query.filter(User.user_id == relative).first():
                    return jsonify(
                        {'message':
                         'Relative provided not registered'}), response_code
                if not caregiver:
                    return jsonify({
                        'message': 'At least 1 caregiver required',
                        'status': 1
                    }), response_code
                if not User.query.filter(User.user_id == caregiver).first():
                    return jsonify(
                        {'message':
                         'Caregiver provided not registered'}), response_code

            # Check format of phone number
            message, status = validate_phone_number(phone_number)
            if status:
                return make_response(jsonify({
                    'message': message,
                    'status': 7
                })), 202
            parts = [x.strip() for x in message.split('.')]
            phone_number = parts[1].split(':')[-1].strip()

            # check if user is already registered
            if User.query.filter_by(phoneNumber=phone_number).first():
                return make_response(
                    jsonify({
                        'message': 'Phone Number already registered',
                        'status': 5
                    })), 202

            # receive profile picture
            message, status = upload_file(request.files,
                                          fullname,
                                          file_type='image',
                                          tag="profile_picture")
            if status or type(message) == str:
                return jsonify({'message': message, "status": -4}), 401
            else:
                profile_picture = message['http_url']

            if role == 'caregiver' or role == 'admin':
                # receive ID picture
                message, status = upload_file(request.files,
                                              fullname,
                                              file_type='image',
                                              tag="identification_picture")
                if status or type(message) == str:
                    return jsonify({'message': message, "status": -4}), 401
                else:
                    identification_picture = message['http_url']
            else:
                identification_picture = ''

            # Create user with given role
            user_id = User.generate_user_id(role)
            try:
                user_datastore.create_user(
                    user_id=user_id,
                    fullname=fullname,
                    phoneNumber=phone_number,
                    profile_picture=profile_picture,
                    identification_number=id_number,
                    roles=[role],
                    identification_picture=identification_picture,
                    gender=gender,
                    residence=residency,
                )
                if role == 'patient':
                    db.session.add(
                        PatientRelative(relative=relative,
                                        patient=user_id,
                                        tag='primary'))
                    db.session.add(
                        PatientCaregiver(caregiver=caregiver,
                                         patient=user_id,
                                         tag='primary'))

                db.session.commit()
            except Exception as err:
                print(err)
                db.session.rollback()
                system_logging(msg=err, exception=True)
                return make_response(
                    jsonify({
                        'message': "Error saving user",
                        'status': 9
                    })), 202

            user = User.query.filter(User.phoneNumber == phone_number).first()
            user.launch_task('send_account_confirmation_sms',
                             'Sending account confirmation OTP SMS',
                             phone_number)

            return make_response(
                jsonify({
                    'message': 'Successful user addition',
                    'status': status
                })), 200
        except Exception as err:
            print(err)
            system_logging(err, exception=True)
            message = "Error during user registration"
            return make_response(jsonify({
                'message': message,
                'status': -2
            })), 401
Ejemplo n.º 17
0
def init_db():
    """
    Method creates and initializes the models used
    :return: None
    """
    from app import models, errors
    from .models import user_datastore
    from datetime import datetime

    try:
        # Create any database tables that don't exist yet.
        db.create_all()

        # Create the Roles "admin", "client" and "stylist" -- unless they already exist
        user_datastore.find_or_create_role(name='admin',
                                           description='Administrator')
        user_datastore.find_or_create_role(name='patient',
                                           description='Patient')
        user_datastore.find_or_create_role(name='caregiver',
                                           description='Caregiver')
        user_datastore.find_or_create_role(name='relative',
                                           description='Relative')

        # Add admin user
        if not models.User.query.filter_by(user_id='ad-0').first():
            user_datastore.create_user(
                user_id='ad-0',
                fullname='Monitoring System',
                phoneNumber=app.config['ADMIN_PHONE_NUMBER'],
                profile_picture=app.config['ADMIN_ICON'],
                identification_number='0',
                roles=['admin'],
                identification_picture='',
                residence=app.config['HEADQUARTERS'],
                is_confirmed=True,
                password=app.config['ADMIN_PASSWORD'],
            )

        try:
            # Commit any database changes; the User and Roles must exist before we can add a Role to the User
            db.session.commit()
        except Exception as err:
            errors.system_logging(err, exception=True)
            print(err)
            db.session.rollback()

        # Provide function to be queued as import string to overcome inconvenience of import it on the application side
        func_import_string = 'app.tasks.prune_database'
        # On server startup, schedule that expired blacklisted token are removed
        current_app.scheduler.enqueue_at(datetime.utcnow(), func_import_string)
        # Then run cleaning every day
        pruning_task = models.ScheduledTask.query.filter_by(
            name='prune_database', cancelled=False).first()
        if not pruning_task:
            job_id = current_app.scheduler.cron(
                "0 0 * * *",  # Cron string setting function to run daily at 12:00 AM (UTC) / 3:00 AM (EAT)
                func=func_import_string,  # Function to be queued
                repeat=
                None,  # Repeat this number of times (None means repeat forever)
                use_local_timezone=True,  # Interpret hours in the local timezone
            ).get_id()
            pruning_task = models.ScheduledTask(
                id=job_id,
                name='prune_database',
                interval=24 * 60 * 60,
                description='Cleaning database to remove old tokens and OTPs',
                cancelled=False)
            pruning_task.save()
    except Exception as ex:
        # Log any error that occurs
        errors.system_logging(ex, exception=True)
        print(ex)
Ejemplo n.º 18
0
def login():
    if request.method == 'POST':
        try:
            uid = request.headers.get("UniqueID")
            if not uid:
                return jsonify({
                    'message': 'Device ID not provided',
                    'auth_token': None,
                    'refresh_token': None
                })

            platform, firebase_token = request.headers.get("Platform"), ''
            if platform and (platform == "android" or platform == "ios"):
                firebase_token = request.headers.get("FirebaseToken")

            data, auth_token, refresh_token = request.get_json(), None, None

            if not data:
                return jsonify({
                    'message': "Data missing",
                    'auth_token': None,
                    'refresh_token': None
                })

            phone, password = data['phone_number'], data['password']

            if not phone:
                return jsonify({
                    'message': 'Phone number not provided',
                    'auth_token': None,
                    'refresh_token': None
                })

            if not password:
                return jsonify({
                    'message': 'Password not provided',
                    'auth_token': None,
                    'refresh_token': None
                })

            # Check format of phone number
            message, status = validate_phone_number(phone)
            if status:
                return jsonify({
                    'message': message,
                    'auth_token': None,
                    'refresh_token': None
                })
            parts = [x.strip() for x in message.split('.')]
            phone_number = parts[1].split(':')[-1].strip()

            # Verify credentials
            user = User.query.filter_by(phoneNumber=phone_number).first()
            if not user:
                return jsonify({
                    'message': 'User not registered',
                    'auth_token': None,
                    'refresh_token': None
                })

            # User must have confirmed account creation, else prompt user to confirm account
            if not user.is_confirmed:
                return jsonify({
                    'message': "Enter OTP code sent in SMS to confirm account",
                    'auth_token': None,
                    'refresh_token': None,
                })

            if not user.verify_password(password):
                return jsonify({
                    'message': 'Invalid password',
                    'auth_token': None,
                    'refresh_token': None
                })

            # User must be active, else prompt user to activate account before logging in
            if not user.active:
                return jsonify({
                    'message': 'Activate account before logging in',
                    'auth_token': None,
                    'refresh_token': None
                })

            user_id = user.user_id

            # Get list of user's registered roles
            role_names = [role.name for role in user.roles]
            # Take first role, thus user can only have 1 role
            role = role_names[0]
            # Log user in and thus generate token
            if firebase_token:
                user_device = UserDevice.query.filter_by(user=user_id,
                                                         platform=platform,
                                                         role=role).first()
                if not user_device:
                    user_device = UserDevice(
                        user=user_id,
                        platform=platform,
                        firebase_token=firebase_token,
                        primary_device=True,
                        role=role,
                    )
                    user_device.save()
            login_session = UserLogin.query.filter_by(user=user_id,
                                                      device_id=uid).first()
            if not login_session:
                # Create a user's login session for a particular device installation
                login_session = UserLogin(
                    user=user_id,
                    device_id=uid,
                    logged_in=True,
                    login_date=datetime.now(
                        tz=pytz.timezone('Africa/Nairobi')),
                )
                status = login_session.save()
            else:
                # Update user's login session for particular device installation
                login_session.logged_in = True
                login_session.login_date = datetime.now(
                    tz=pytz.timezone('Africa/Nairobi'))
                status = login_session.save()
            if status:
                return jsonify({
                    'message': 'Error logging in user',
                    'auth_token': None,
                    'refresh_token': None
                })
            tokens = (encode_auth_token(user_id),
                      encode_refresh_token(user_id))
            return jsonify({
                'message': f'Success! ID {user_id}',
                'auth_token': tokens[0],
                'refresh_token': tokens[1],
                'role': role
            })
        except Exception as err:
            print(err)
            system_logging(err, exception=True)
            return jsonify({
                'message': "Error in login. Contact admin",
                'auth_token': None,
                'refresh_token': None
            }), 401