def post(self): # get the post data post_data = request.get_json() user = None phone = None email = post_data.get('username') or post_data.get('email') password = post_data.get('password') tfa_token = post_data.get('tfa_token') # First try to match email if email: user = User.query.filter_by(email=email).execution_options(show_all=True).first() # Now try to match the public serial number (comes in under the phone) if not user: public_serial_number_or_phone = post_data.get('phone') user = User.query.filter_by(public_serial_number=public_serial_number_or_phone).execution_options( show_all=True).first() # Now try to match the phone if not user: try: phone = proccess_phone_number(post_data.get('phone'), region=post_data.get('region')) except NumberParseException as e: response_object = {'message': 'Invalid Phone Number: ' + str(e)} return make_response(jsonify(response_object)), 401 if phone: user = User.query.filter_by(phone=phone).execution_options(show_all=True).first() # mobile user doesn't exist so default to creating a new wallet! if user is None and phone: # this is a registration from a mobile device THUS a vendor or recipient. response_object, response_code = UserUtils.proccess_create_or_modify_user_request( dict(phone=phone, deviceInfo=post_data.get('deviceInfo')), is_self_sign_up=True, ) if response_code == 200: db.session.commit() return make_response(jsonify(response_object)), response_code if user and user.is_activated and post_data.get('phone') and (password == ''): # user already exists, is activated. no password provided, thus request PIN screen. # todo: this should check if device exists, if no, resend OTP to verify login is real. response_object = { 'status': 'success', 'login_with_pin': True, 'message': 'Login with PIN' } return make_response(jsonify(response_object)), 200 if not (email or post_data.get('phone')): response_object = { 'status': 'fail', 'message': 'No username supplied' } return make_response(jsonify(response_object)), 401 if post_data.get('phone') and user and user.one_time_code and not user.is_activated: # vendor sign up with one time code or OTP verified if user.one_time_code == password: response_object = { 'status': 'success', 'pin_must_be_set': True, 'message': 'Please set your pin.' } return make_response(jsonify(response_object)), 200 if not user.is_phone_verified: if user.is_self_sign_up: # self sign up, resend phone verification code user.set_pin(None, False) # resets PIN UserUtils.send_one_time_code(phone=phone, user=user) db.session.commit() response_object = {'message': 'Please verify phone number.', 'otp_verify': True} return make_response(jsonify(response_object)), 200 try: if not user or not user.verify_password(password): response_object = { 'status': 'fail', 'message': 'Invalid username or password' } return make_response(jsonify(response_object)), 401 if not user.is_activated: response_object = { 'status': 'fail', 'is_activated': False, 'message': 'Account has not been activated. Please check your emails.' } return make_response(jsonify(response_object)), 401 if post_data.get('deviceInfo'): UserUtils.save_device_info(post_data.get('deviceInfo'), user) auth_token = user.encode_auth_token() if not auth_token: response_object = { 'status': 'fail', 'message': 'Invalid username or password' } return make_response(jsonify(response_object)), 401 # Possible Outcomes: # TFA required, but not set up # TFA enabled, and user does not have valid TFA token # TFA enabled, and user has valid TFA token # TFA not required tfa_response_oject = tfa_logic(user, tfa_token) if tfa_response_oject: tfa_response_oject['auth_token'] = auth_token.decode() return make_response(jsonify(tfa_response_oject)), 401 # Update the last_seen TS for this user user.update_last_seen_ts() response_object = create_user_response_object(user, auth_token, 'Successfully logged in.') db.session.commit() return make_response(jsonify(response_object)), 200 except Exception as e: sentry_sdk.capture_exception(e) raise e
def post(self): # There is an unique case where users are using their mobile number from the App to either login or register # The app uses g.active_organisation to reference user.transfer_account to send an SMS to the User. # This means that g.active_organisation should default to the master_organisation # For admin users, it doesn't matter as this endpoint is unauthed. g.active_organisation = Organisation.master_organisation() post_data = request.get_json() user = None phone = None email = post_data.get('username', '') or post_data.get('email', '') email = email.lower() if email else '' password = post_data.get('password') # Default pin to password as fallback for old android versions pin = post_data.get('pin', password) tfa_token = post_data.get('tfa_token') password_empty = password == '' or password is None pin_empty = pin == '' or pin is None ratelimit_key = email or post_data.get('phone') if ratelimit_key: limit = rate_limit("login_"+ratelimit_key, 25) if limit: response_object = { 'status': 'fail', 'message': f'Please try again in {limit} minutes' } return make_response(jsonify(response_object)), 403 # First try to match email if email: user = User.query.filter(func.lower(User.email)==email).execution_options(show_all=True).first() # Now try to match the public serial number (comes in under the phone) if not user: public_serial_number_or_phone = post_data.get('phone') user = User.query.filter_by(public_serial_number=public_serial_number_or_phone).execution_options( show_all=True).first() # Now try to match the phone if not user: try: phone = proccess_phone_number(post_data.get('phone'), region=post_data.get('region')) except NumberParseException as e: response_object = {'message': 'Invalid Phone Number: ' + str(e)} return make_response(jsonify(response_object)), 401 if phone: user = User.query.filter_by(phone=phone).execution_options(show_all=True).first() # mobile user doesn't exist so default to creating a new wallet! if user is None and phone and current_app.config['ALLOW_SELF_SIGN_UP']: # this is a registration from a mobile device THUS a vendor or recipient. response_object, response_code = UserUtils.proccess_create_or_modify_user_request( dict(phone=phone, deviceInfo=post_data.get('deviceInfo')), is_self_sign_up=True, ) if response_code == 200: db.session.commit() return make_response(jsonify(response_object)), response_code no_password_or_pin_hash = user and not user.password_hash and not user.pin_hash if post_data.get('phone') and user and user.one_time_code and (not user.is_activated or not user.pin_hash): # vendor sign up with one time code or OTP verified if user.one_time_code == pin: response_object = { 'status': 'success', 'pin_must_be_set': True, 'message': 'Please set your pin.' } return make_response(jsonify(attach_host(response_object))), 200 if not user.is_phone_verified or no_password_or_pin_hash: if user.is_self_sign_up: # self sign up, resend phone verification code user.set_pin(None, False) # resets PIN UserUtils.send_one_time_code(phone=phone, user=user) db.session.commit() if not password_empty: # The user provided a password, so probably not going through incremental login # This is a hacky way to get past the incremental-login multi-org split response_object = { 'status': 'fail', 'otp_verify': True, 'message': 'Please verify phone number.', 'error_message': 'Incorrect One Time Code.' } return make_response(jsonify(attach_host(response_object))), 200 response_object = {'message': 'Please verify phone number.', 'otp_verify': True} return make_response(jsonify(attach_host(response_object))), 200 if user and user.is_activated and post_data.get('phone') and (password_empty and pin_empty): # user already exists, is activated. no password or pin provided, thus request PIN screen. # todo: this should check if device exists, if no, resend OTP to verify login is real. response_object = { 'status': 'success', 'login_with_pin': True, 'message': 'Login with PIN' } return make_response(jsonify(attach_host(response_object))), 200 if not (email or post_data.get('phone')): response_object = { 'status': 'fail', 'message': 'No username supplied' } return make_response(jsonify(response_object)), 401 try: if not (user and (pin and user.verify_pin(pin) or password and user.verify_password(password))): response_object = { 'status': 'fail', 'message': 'Invalid username or password' } return make_response(jsonify(response_object)), 401 if not user.is_activated: response_object = { 'status': 'fail', 'is_activated': False, 'message': 'Account has not been activated. Please check your emails.' } return make_response(jsonify(response_object)), 401 if post_data.get('deviceInfo'): deviceInfo = post_data.get('deviceInfo') UserUtils.save_device_info(deviceInfo, user) auth_token = user.encode_auth_token() if not auth_token: response_object = { 'status': 'fail', 'message': 'Invalid username or password' } return make_response(jsonify(response_object)), 401 # Possible Outcomes: # TFA required, but not set up # TFA enabled, and user does not have valid TFA token # TFA enabled, and user has valid TFA token # TFA not required tfa_response_oject = tfa_logic(user, tfa_token) if tfa_response_oject: tfa_response_oject['auth_token'] = auth_token.decode() return make_response(jsonify(tfa_response_oject)), 401 # Update the last_seen TS for this user user.update_last_seen_ts() response_object = create_user_response_object(user, auth_token, 'Successfully logged in.') db.session.commit() return make_response(jsonify(attach_host(response_object))), 200 except Exception as e: sentry_sdk.capture_exception(e) raise e