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
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
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()
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()
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
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
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'
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
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
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."
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
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."
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'
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
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 ''
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
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)
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