def test_tfa_logic_error(test_client, init_database, authed_sempo_admin_user, no_token, tfa_enabled, invalid_token, invalid_user_id, message): user = authed_sempo_admin_user user.set_held_role('ADMIN', 'superadmin') user.TFA_enabled = tfa_enabled auth = get_complete_auth_token(authed_sempo_admin_user) tfa_token = None if no_token else auth.split('|')[1] if invalid_token: tfa_token = tfa_token + 'foo' if invalid_user_id: # build a fake tfa token with incorrect user ID payload = { 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1, seconds=30), 'iat': datetime.datetime.utcnow(), 'id': 123 } return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256') tfa = tfa_logic(user=user, tfa_token=tfa_token) assert tfa['message'] == message
def post(self): # get the post data post_data = request.get_json() email = post_data.get('email') or post_data.get('username') password = post_data.get('password') phone = post_data.get('phone') referral_code = post_data.get('referral_code') if phone is not None: # this is a registration from a mobile device THUS a vendor or recipient. response_object, response_code = UserUtils.proccess_create_or_modify_user_request( post_data, is_self_sign_up=True, ) if response_code == 200: db.session.commit() return make_response(jsonify(response_object)), response_code email_ok = False whitelisted_emails = EmailWhitelist.query\ .filter_by(referral_code=referral_code, used=False) \ .execution_options(show_all=True).all() selected_whitelist_item = None exact_match = False tier = None sempoadmin_emails = current_app.config['SEMPOADMIN_EMAILS'] if sempoadmin_emails != [''] and email in sempoadmin_emails: email_ok = True tier = 'sempoadmin' for whitelisted in whitelisted_emails: if whitelisted.allow_partial_match and whitelisted.email in email: email_ok = True tier = whitelisted.tier selected_whitelist_item = whitelisted exact_match = False continue elif whitelisted.email == email: email_ok = True whitelisted.used = True tier = whitelisted.tier selected_whitelist_item = whitelisted exact_match = True continue if not email_ok: response_object = { 'status': 'fail', 'message': 'Invalid email domain.', } return make_response(jsonify(response_object)), 403 if len(password) < 7: response_object = { 'status': 'fail', 'message': 'Password must be at least 6 characters long', } return make_response(jsonify(response_object)), 403 # check if user already exists user = User.query.filter_by(email=email).execution_options(show_all=True).first() if user: response_object = { 'status': 'fail', 'message': 'User already exists. Please Log in.', } return make_response(jsonify(response_object)), 403 if tier is None: tier = 'subadmin' if selected_whitelist_item: organisation = selected_whitelist_item.organisation else: organisation = Organisation.master_organisation() user = User(blockchain_address=organisation.primary_blockchain_address) user.create_admin_auth(email, password, tier, organisation) # insert the user db.session.add(user) db.session.flush() if exact_match: user.is_activated = True auth_token = user.encode_auth_token() # Possible Outcomes: # TFA required, but not set up # TFA not required tfa_response_oject = tfa_logic(user, tfa_token=None) if tfa_response_oject: tfa_response_oject['auth_token'] = auth_token.decode() db.session.commit() # need this here to commit a created user to the db 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 activated.') db.session.commit() return make_response(jsonify(response_object)), 201 activation_token = user.encode_single_use_JWS('A') send_activation_email(activation_token, email) db.session.commit() # generate the auth token response_object = { 'status': 'success', 'message': 'Successfully registered. You must activate your email.', } return make_response(jsonify(response_object)), 201
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): # get the post data post_data = request.get_json() activation_token = post_data.get('activation_token') if activation_token and activation_token != 'null': auth_token = activation_token.split(" ")[0] else: auth_token = '' if auth_token: validity_check = User.decode_single_use_JWS(activation_token, 'A') if not validity_check['success']: response_object = { 'status': 'fail', 'message': validity_check['message'] } return make_response(jsonify(response_object)), 401 user = validity_check['user'] if user.is_activated: response_object = { 'status': 'fail', 'message': 'Already activated.' } return make_response(jsonify(response_object)), 401 user.is_activated = True auth_token = user.encode_auth_token() db.session.flush() # Possible Outcomes: # TFA required, but not set up # TFA not required tfa_response_oject = tfa_logic(user, tfa_token=None) if tfa_response_oject: tfa_response_oject['auth_token'] = auth_token.decode() db.session.commit() # need to commit here so that is_activated = True 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 activated.') db.session.commit() return make_response(jsonify(response_object)), 201 else: response_object = { 'status': 'fail', 'message': 'Provide a valid auth token.' } return make_response(jsonify(response_object)), 401
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
def post(self): # get the post data post_data = request.get_json() user = 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).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).first() #Now try to match the phone if not user: phone = proccess_phone_number(post_data.get('phone')) if phone: user = User.query.filter_by(phone=phone).first() if not (email or post_data.get('phone')): responseObject = { 'status': 'fail', 'message': 'No username supplied' } return make_response(jsonify(responseObject)), 401 if post_data.get( 'phone' ) and user and user.one_time_code and not user.is_activated: if user.one_time_code == password: responseObject = { 'status': 'success', 'pin_must_be_set': True, 'message': 'Please set your pin.' } return make_response(jsonify(responseObject)), 200 try: if not user or not user.verify_password(post_data.get('password')): responseObject = { 'status': 'fail', 'message': 'Invalid username or password' } return make_response(jsonify(responseObject)), 401 if not user.is_activated: responseObject = { 'status': 'fail', 'is_activated': False, 'message': 'Account has not been activated. Please check your emails.' } return make_response(jsonify(responseObject)), 401 if post_data.get('deviceInfo'): save_device_info(post_data.get('deviceInfo'), user) db.session.commit() auth_token = user.encode_auth_token() if not auth_token: responseObject = { 'status': 'fail', 'message': 'Invalid username or password' } return make_response(jsonify(responseObject)), 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() responseObject = create_user_response_object( user, auth_token, 'Successfully logged in.') return make_response(jsonify(responseObject)), 200 except Exception as e: sentry.captureException() raise e