예제 #1
0
    def post(self):
        request_data = request.get_json()
        user = g.user
        otp_token = request_data.get('otp')
        otp_expiry_interval = request_data.get('otp_expiry_interval')

        malformed_otp = False
        try:
            int(otp_token)
        except ValueError:
            malformed_otp = True

        if not isinstance(otp_token, str) or len(otp_token) != 6:
            malformed_otp = True

        if malformed_otp:
            response_object = {
                'status': "Failed",
                'message': "OTP must be a 6 digit numeric string"
            }

            return make_response(jsonify(attach_host(response_object))), 400

        if user.validate_OTP(otp_token):
            tfa_auth_token = user.encode_TFA_token(otp_expiry_interval)
            user.TFA_enabled = True

            db.session.commit()

            if tfa_auth_token:
                auth_token = g.user.encode_auth_token()

                response_object = create_user_response_object(
                    user, auth_token, 'Successfully logged in.')

                response_object['tfa_auth_token'] = tfa_auth_token.decode()

                return make_response(jsonify(
                    attach_host(response_object))), 200

        response_object = {
            'status': "Failed",
            'message': "Validation failed. Please try again."
        }

        return make_response(jsonify(response_object)), 400
예제 #2
0
    def get(self):
        try:

            auth_token = g.user.encode_auth_token()

            response_object = create_user_response_object(g.user, auth_token, 'Token refreshed successfully.')

            # Update the last_seen TS for this user
            g.user.update_last_seen_ts()

            return make_response(jsonify(response_object)), 200

        except Exception as e:

            response_object = {
                'status': 'fail',
                'message': 'Some error occurred. Please try again.'
            }

            return make_response(jsonify(response_object)), 403
예제 #3
0
    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
예제 #4
0
    def post(self):

        # get the post data
        post_data = request.get_json()

        old_password = post_data.get('old_password')
        new_password = post_data.get('new_password')
        phone = proccess_phone_number(phone_number=post_data.get('phone'), region=post_data.get('region'))
        one_time_code = post_data.get('one_time_code')

        auth_header = request.headers.get('Authorization')

        # Check authorisation using a one time code
        if phone and one_time_code:
            card = phone[-6:]
            user = (User.query.filter_by(phone=phone).execution_options(show_all=True).first() or
                    User.query.filter_by(public_serial_number=card).execution_options(show_all=True).first()
                    )

            if not user:
                response_object = {
                    'status': 'fail',
                    'message': 'User not found'
                }

                return make_response(jsonify(response_object)), 401

            if user.is_activated:
                response_object = {
                    'status': 'fail',
                    'message': 'Account already activated'
                }

                return make_response(jsonify(response_object)), 401

            if str(one_time_code) != user.one_time_code:
                response_object = {
                    'status': 'fail',
                    'message': 'One time code not valid'
                }

                return make_response(jsonify(response_object)), 401

            user.hash_password(new_password)

            user.is_phone_verified = True
            user.is_activated = True
            user.one_time_code = None

            auth_token = user.encode_auth_token()

            response_object = create_user_response_object(user, auth_token, 'Successfully set pin')

            db.session.commit()

            return make_response(jsonify(response_object)), 200

        # Check authorisation using regular auth
        elif auth_header and auth_header != 'null' and old_password:
            auth_token = auth_header.split(" ")[0]

            resp = User.decode_auth_token(auth_token)

            if isinstance(resp, str):
                response_object = {
                    'status': 'fail',
                    'message': 'Invalid auth token'
                }

                return make_response(jsonify(response_object)), 401

            user = User.query.filter_by(id=resp.get('user_id')).execution_options(show_all=True).first()

            if not user:
                response_object = {
                    'status': 'fail',
                    'message': 'User not found'
                }

                return make_response(jsonify(response_object)), 401

            if not user.verify_password(old_password):
                response_object = {
                    'status': 'fail',
                    'message': 'invalid password'
                }

                return make_response(jsonify(response_object)), 401

        # Check authorisation using a reset token provided via email
        else:

            reset_password_token = post_data.get('reset_password_token')

            if not reset_password_token:
                response_object = {
                    'status': 'fail',
                    'message': 'Missing token.'
                }

                return make_response(jsonify(response_object)), 401

            reset_password_token = reset_password_token.split(" ")[0]

            validity_check = User.decode_single_use_JWS(reset_password_token, 'R')

            if not validity_check['success']:
                response_object = {
                    'status': 'fail',
                    'message': validity_check['message']
                }

                return make_response(jsonify(response_object)), 401

            user = validity_check['user']

            reuse_check = user.check_reset_token_already_used(
                reset_password_token)

            
            if not reuse_check:
                response_object = {
                    'status': 'fail',
                    'message': 'Token already used'
                }

                return make_response(jsonify(response_object)), 401

        if not new_password or len(new_password) < 6:
            response_object = {
                'status': 'fail',
                'message': 'Password must be at least 6 characters long'
            }

            return make_response(jsonify(response_object)), 401

        user.hash_password(new_password)
        user.delete_password_reset_tokens()
        db.session.commit()

        response_object = {
            'status': 'success',
            'message': 'Password changed, please log in'
        }

        return make_response(jsonify(response_object)), 200
예제 #5
0
    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
예제 #6
0
    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
예제 #7
0
    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