def test_token_expired(self, user, client, security_service,
                           password_resets, outbox, templates):
        security_service.send_reset_password_instructions(user)
        assert len(password_resets) == 1
        token = password_resets[0]['token']

        r = client.get('security_controller.reset_password', token=token)
        assert r.status_code == 302
        assert r.path == url_for('security_controller.forgot_password')

        assert len(outbox) == 2
        # first email is for the valid reset request
        assert templates[0].template.name == \
               'security/email/reset_password_instructions.html'
        assert templates[0].context.get('reset_link')
        # second email is with a new token
        assert templates[1].template.name == \
               'security/email/reset_password_instructions.html'
        assert templates[1].context.get('reset_link')
        assert templates[0].context.get('reset_link') != \
               templates[1].context.get('reset_link')

        # make sure the user is notified of the new email
        r = client.follow_redirects(r)
        assert r.status_code == 200
        assert templates[2].template.name == 'security/forgot_password.html'
        error_msg = 'You did not reset your password within -1 seconds. ' \
                    f'New instructions have been sent to {user.email}'
        assert error_msg in r.html
Exemple #2
0
 def login(self, remote_app):
     provider = getattr(self.oauth, remote_app)
     return provider.authorize(
         callback=url_for('o_auth_controller.authorized',
                          remote_app=remote_app,
                          _external=True,
                          _scheme='https'))
 def test_token_invalid(self, client, templates):
     r = client.get('security_controller.reset_password', token='fail')
     assert r.status_code == 302
     assert r.path == url_for('security_controller.forgot_password')
     r = client.follow_redirects(r)
     assert r.status_code == 200
     assert templates[0].template.name == 'security/forgot_password.html'
     assert 'Invalid reset password token' in r.html
    def test_invalid_token(self, client, user, registrations, confirmations,
                           outbox, templates, security_service):
        security_service.register_user(user)
        assert len(registrations) == 1

        r = client.get(
            url_for('security_controller.confirm_email', token='fail'))
        assert r.status_code == 302
        assert r.path == url_for('security_controller.send_confirmation_email')

        assert len(confirmations) == 0
        assert len(outbox) == len(templates) == 1
        assert templates[0].template.name == 'security/email/welcome.html'

        assert not user.active
        assert not user.confirmed_at
        assert isinstance(current_user._get_current_object(), AnonymousUser)
    def test_authed_user_html_request_redirected(self, client):
        client.login_user()

        @anonymous_user_required
        def method():
            return None

        r = method()
        assert r.status_code == 302
        assert r.location == url_for('SECURITY_POST_LOGIN_REDIRECT_ENDPOINT')
    def test_http_get_redirects_to_frontend_form(self, user, api_client,
                                                 security_service,
                                                 password_resets):
        security_service.send_reset_password_instructions(user)
        assert len(password_resets) == 1
        token = password_resets[0]['token']

        r = api_client.get('security_api.reset_password', token=token)
        assert r.status_code == 302
        assert r.path == url_for(
            'SECURITY_API_RESET_PASSWORD_HTTP_GET_REDIRECT', token=token)
Exemple #7
0
def _process_test_client_args(args, kwargs):
    """
    allow calling client.get, client.post, etc methods with an endpoint name.
    this function forwards the correct kwargs to url_for (as long as they don't
    conflict with the kwarg names of werkzeug.test.EnvironBuilder, in which case
    it would be necessary to use `url_for` in the same way as with FlaskClient)
    """
    endpoint_or_url_or_config_key = args and args[0]
    url_for_kwargs = {}
    for kwarg_name in (set(kwargs) - ENV_BUILDER_KWARGS):
        url_for_kwargs[kwarg_name] = kwargs.pop(kwarg_name)
    url = url_for(endpoint_or_url_or_config_key, **url_for_kwargs)
    return (url, *args[1:]), kwargs
Exemple #8
0
    def register_user(self, user, allow_login=None, send_email=None):
        """
        Service method to register a user.

        Sends signal `user_registered`.

        Returns True if the user has been logged in, False otherwise.
        """
        should_login_user = (not self.security.confirmable
                             or self.security.login_without_confirmation)
        should_login_user = (should_login_user if allow_login is None else
                             allow_login and should_login_user)
        if should_login_user:
            user.active = True

        # confirmation token depends on having user.id set, which requires
        # the user be committed to the database
        self.user_manager.save(user, commit=True)

        confirmation_link, token = None, None
        if self.security.confirmable:
            token = self.security_utils_service.generate_confirmation_token(
                user)
            confirmation_link = url_for('security_controller.confirm_email',
                                        token=token,
                                        _external=True)

        user_registered.send(app._get_current_object(),
                             user=user,
                             confirm_token=token)

        if (send_email
                or (send_email is None
                    and app.config.get('SECURITY_SEND_REGISTER_EMAIL'))):
            self.send_mail(
                _('flask_unchained.bundles.security:email_subject.register'),
                to=user.email,
                template='security/email/welcome.html',
                user=user,
                confirmation_link=confirmation_link)

        if should_login_user:
            return self.login_user(user)
        return False
Exemple #9
0
    def send_reset_password_instructions(self, user):
        """
        Sends the reset password instructions email for the specified user.

        Sends signal `reset_password_instructions_sent`.

        :param user: The user to send the instructions to.
        """
        token = self.security_utils_service.generate_reset_password_token(user)
        reset_link = url_for('security_controller.reset_password',
                             token=token, _external=True)
        self.send_mail(
            _('flask_unchained.bundles.security:email_subject.reset_password_instructions'),
            to=user.email,
            template='security/email/reset_password_instructions.html',
            user=user,
            reset_link=reset_link)
        reset_password_instructions_sent.send(app._get_current_object(),
                                              user=user, token=token)
Exemple #10
0
    def send_email_confirmation_instructions(self, user):
        """
        Sends the confirmation instructions email for the specified user.

        Sends signal `confirm_instructions_sent`.

        :param user: The user to send the instructions to.
        """
        token = self.security_utils_service.generate_confirmation_token(user)
        confirmation_link = url_for('security_controller.confirm_email',
                                    token=token, _external=True)
        self.send_mail(
            _('flask_unchained.bundles.security:email_subject.email_confirmation_instructions'),
            to=user.email,
            template='security/email/email_confirmation_instructions.html',
            user=user,
            confirmation_link=confirmation_link)
        confirm_instructions_sent.send(app._get_current_object(), user=user,
                                       token=token)
    def test_confirm_email(self, client, registrations, confirmations, user,
                           security_service):
        security_service.register_user(user)
        assert len(registrations) == 1
        assert user == registrations[0]['user']
        assert not user.active
        assert not user.confirmed_at

        confirm_token = registrations[0]['confirm_token']
        r = client.get(
            url_for('security_controller.confirm_email', token=confirm_token))
        assert r.status_code == 302
        assert r.path == '/'

        assert len(confirmations) == 1
        assert user in confirmations

        assert user.active
        assert user.confirmed_at
        assert current_user == user
    def test_expired_token(self, client, registrations, confirmations, outbox,
                           templates, user_manager: UserManager,
                           security_service: SecurityService):
        user = self.register(user_manager, security_service)
        assert len(registrations) == 1

        confirm_token = registrations[0]['confirm_token']
        r = client.get('security.confirm_email', token=confirm_token)
        assert r.status_code == 302
        assert r.path == url_for('frontend.resend_confirmation_email')

        assert len(confirmations) == 0
        assert len(outbox) == len(templates) == 2
        assert templates[0].template.name == 'security/email/welcome.html'
        assert templates[
            1].template.name == 'security/email/confirmation_instructions.html'
        assert templates[1].context.get('confirmation_link')

        assert not user.active
        assert not user.confirmed_at
        assert isinstance(current_user._get_current_object(), AnonymousUser)
    def test_confirm_email(self, client, registrations, confirmations,
                           user_manager: UserManager,
                           security_service: SecurityService):
        user = self.register(user_manager, security_service)
        assert len(registrations) == 1
        assert user == registrations[0]['user']
        assert not user.active
        assert not user.confirmed_at

        confirm_token = registrations[0]['confirm_token']
        r = client.get('security.confirm_email', token=confirm_token)
        assert r.status_code == 302
        assert r.path == url_for('frontend.index')
        assert r.query == 'welcome'

        assert len(confirmations) == 1
        assert user in confirmations

        assert user.active
        assert user.confirmed_at
        assert current_user == user
    def test_token_expired(self, user, api_client, security_service,
                           password_resets, outbox, templates):
        security_service.send_reset_password_instructions(user)
        assert len(password_resets) == 1
        token = password_resets[0]['token']

        r = api_client.get('security_api.reset_password', token=token)
        assert r.status_code == 302
        assert r.path == url_for('SECURITY_EXPIRED_RESET_TOKEN_REDIRECT')

        assert len(outbox) == len(templates) == 2
        # first email is for the valid reset request
        assert templates[0].template.name == \
               'security/email/reset_password_instructions.html'
        assert templates[0].context.get('reset_link')
        # second email is with a new token
        assert templates[1].template.name == \
               'security/email/reset_password_instructions.html'
        assert templates[1].context.get('reset_link')
        assert templates[0].context.get(
            'reset_link') != templates[1].context.get('reset_link')
 def test_html_login_with_email(self, client, user):
     r = client.post('admin.login',
                     data=dict(email=user.email, password='******'))
     assert r.status_code == 302
     assert r.path == url_for('admin.index')
     assert current_user == user
Exemple #16
0
class Config(BundleConfig):
    ##########################################################################
    # flask                                                                  #
    ##########################################################################
    DEBUG = get_boolean_env('FLASK_DEBUG', False)
    FLASH_MESSAGES = False
    SECRET_KEY = os.getenv('FLASK_SECRET_KEY', 'not-secret-key')  # FIXME

    app_dirs = AppDirs('flask-unchained-react-spa')
    APP_CACHE_FOLDER = app_dirs.user_cache_dir
    APP_DATA_FOLDER = app_dirs.user_data_dir

    ADMIN_CATEGORY_ICON_CLASSES = {
        'Security': 'glyphicon glyphicon-lock',
        'Mail': 'glyphicon glyphicon-envelope',
    }

    ##########################################################################
    # celery                                                                 #
    ##########################################################################
    CELERY_BROKER_URL = 'redis://{host}:{port}/0'.format(
        host=os.getenv('FLASK_REDIS_HOST', '127.0.0.1'),
        port=os.getenv('FLASK_REDIS_PORT', 6379),
    )
    CELERY_RESULT_BACKEND = CELERY_BROKER_URL

    ##########################################################################
    # mail                                                                   #
    ##########################################################################
    MAIL_ADMINS = ['*****@*****.**']  # FIXME
    MAIL_DEFAULT_SENDER = (
        os.environ.get('FLASK_MAIL_DEFAULT_SENDER_NAME',
                       'Flask Unchained React SPA'),
        os.environ.get(
            'FLASK_MAIL_DEFAULT_SENDER_EMAIL',
            f"noreply@{os.environ.get('FLASK_DOMAIN', 'localhost')}"))

    ##########################################################################
    # session/cookies                                                        #
    ##########################################################################
    SESSION_TYPE = 'redis'
    SESSION_REDIS = redis.Redis(
        host=os.getenv('FLASK_REDIS_HOST', '127.0.0.1'),
        port=int(os.getenv('FLASK_REDIS_PORT', 6379)),
    )
    SESSION_PROTECTION = 'strong'
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SECURE = True
    REMEMBER_COOKIE_HTTPONLY = True

    # SECURITY_TOKEN_MAX_AGE is fixed from time of token generation;
    # it does not update on refresh like a session timeout would. for that,
    # we set (the ironically named) PERMANENT_SESSION_LIFETIME
    PERMANENT_SESSION_LIFETIME = timedelta(minutes=60)

    ##########################################################################
    # security                                                               #
    ##########################################################################
    SECURITY_PASSWORD_SALT = 'security-password-salt'
    SECURITY_CONFIRMABLE = True
    SECURITY_REGISTERABLE = True
    SECURITY_RECOVERABLE = True
    SECURITY_CHANGEABLE = True

    ADMIN_LOGIN_ENDPOINT = 'admin.login'
    ADMIN_LOGOUT_ENDPOINT = 'admin.logout'
    SECURITY_POST_LOGIN_REDIRECT_ENDPOINT = 'admin.index'
    ADMIN_POST_LOGOUT_ENDPOINT = LocalProxy(
        lambda: url_for('frontend.index', _external=True))

    SECURITY_FORGOT_PASSWORD_ENDPOINT = 'frontend.forgot_password'
    SECURITY_API_RESET_PASSWORD_HTTP_GET_REDIRECT = 'frontend.reset_password'
    SECURITY_INVALID_RESET_TOKEN_REDIRECT = LocalProxy(lambda: url_for(
        'frontend.forgot_password', _external=True) + '?invalid')
    SECURITY_EXPIRED_RESET_TOKEN_REDIRECT = LocalProxy(lambda: url_for(
        'frontend.forgot_password', _external=True) + '?expired')
    SECURITY_POST_CONFIRM_REDIRECT_ENDPOINT = LocalProxy(
        lambda: url_for('frontend.index', _external=True) + '?welcome')
    SECURITY_CONFIRM_ERROR_REDIRECT_ENDPOINT = LocalProxy(
        lambda: url_for('frontend.resend_confirmation_email', _external=True))

    ##########################################################################
    # database                                                               #
    ##########################################################################
    SQLALCHEMY_DATABASE_URI = '{engine}://{user}:{pw}@{host}:{port}/{db}'.format(
        engine=os.getenv('FLASK_DATABASE_ENGINE', 'postgresql+psycopg2'),
        user=os.getenv('FLASK_DATABASE_USER', 'flask_api'),
        pw=os.getenv('FLASK_DATABASE_PASSWORD', 'flask_api'),
        host=os.getenv('FLASK_DATABASE_HOST', '127.0.0.1'),
        port=os.getenv('FLASK_DATABASE_PORT', 5432),
        db=os.getenv('FLASK_DATABASE_NAME', 'flask_api'))
Exemple #17
0
class Config(BundleConfig):
    ##########################################################################
    # flask                                                                  #
    ##########################################################################
    DEBUG = get_boolean_env('FLASK_DEBUG', False)
    SECRET_KEY = os.getenv('FLASK_SECRET_KEY', 'not-secret-key')  # FIXME

    APP_ROOT = os.path.abspath(os.path.dirname(__file__))
    PROJECT_ROOT = os.path.abspath(os.path.join(APP_ROOT, os.pardir))

    app_dirs = AppDirs('flask-techan-unchained')

    APP_CACHE_FOLDER = app_dirs.user_cache_dir
    APP_DATA_FOLDER = app_dirs.user_data_dir

    os.makedirs(APP_CACHE_FOLDER, exist_ok=True)
    os.makedirs(APP_DATA_FOLDER, exist_ok=True)

    FLASH_MESSAGES = False
    WTF_CSRF_ENABLED = True

    ##########################################################################
    # celery                                                                 #
    ##########################################################################
    CELERY_BROKER_URL = 'redis://{host}:{port}/0'.format(
        host=os.getenv('FLASK_REDIS_HOST', '127.0.0.1'),
        port=os.getenv('FLASK_REDIS_PORT', 6379),
    )
    CELERY_RESULT_BACKEND = CELERY_BROKER_URL

    ##########################################################################
    # mail                                                                   #
    ##########################################################################
    MAIL_ADMINS = ['*****@*****.**']  # FIXME
    MAIL_DEFAULT_SENDER = (
        os.environ.get('FLASK_MAIL_DEFAULT_SENDER_NAME',
                       'Flask Techan Unchained'),
        os.environ.get(
            'FLASK_MAIL_DEFAULT_SENDER_EMAIL',
            f"noreply@{os.environ.get('FLASK_DOMAIN', 'localhost')}"))

    ##########################################################################
    # session/cookies                                                        #
    ##########################################################################
    SESSION_TYPE = 'redis'
    SESSION_REDIS = redis.Redis(
        host=os.getenv('FLASK_REDIS_HOST', '127.0.0.1'),
        port=int(os.getenv('FLASK_REDIS_PORT', 6379)),
    )
    SESSION_PROTECTION = 'strong'
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SECURE = True
    REMEMBER_COOKIE_HTTPONLY = True

    # SECURITY_TOKEN_MAX_AGE is fixed from time of token generation;
    # it does not update on refresh like a session timeout would. for that,
    # we set (the ironically named) PERMANENT_SESSION_LIFETIME
    PERMANENT_SESSION_LIFETIME = timedelta(minutes=60)

    ##########################################################################
    # security                                                               #
    ##########################################################################
    SECURITY_PASSWORD_SALT = 'security-password-salt'
    SECURITY_CONFIRMABLE = True
    SECURITY_REGISTERABLE = True
    SECURITY_RECOVERABLE = True
    SECURITY_CHANGEABLE = True

    SECURITY_FORGOT_PASSWORD_ENDPOINT = 'frontend.forgot_password'
    SECURITY_API_RESET_PASSWORD_HTTP_GET_REDIRECT = 'frontend.reset_password'
    SECURITY_INVALID_RESET_TOKEN_REDIRECT = LocalProxy(lambda: url_for(
        'frontend.forgot_password', _external=True) + '?invalid')
    SECURITY_EXPIRED_RESET_TOKEN_REDIRECT = LocalProxy(lambda: url_for(
        'frontend.forgot_password', _external=True) + '?expired')
    SECURITY_POST_CONFIRM_REDIRECT_ENDPOINT = LocalProxy(
        lambda: url_for('frontend.index', _external=True) + '?welcome')
    SECURITY_CONFIRM_ERROR_REDIRECT_ENDPOINT = LocalProxy(
        lambda: url_for('frontend.resend_confirmation_email', _external=True))

    ##########################################################################
    # database                                                               #
    ##########################################################################
    SQLALCHEMY_DATABASE_URI = '{engine}://{user}:{pw}@{host}:{port}/{db}'.format(
        engine=os.getenv('FLASK_DATABASE_ENGINE', 'postgresql+psycopg2'),
        user=os.getenv('FLASK_DATABASE_USER', 'fun_techan'),
        pw=os.getenv('FLASK_DATABASE_PASSWORD', 'fun_techan'),
        host=os.getenv('FLASK_DATABASE_HOST', '127.0.0.1'),
        port=os.getenv('FLASK_DATABASE_PORT', 5432),
        db=os.getenv('FLASK_DATABASE_NAME', 'fun_techan'))

    MARKETSTORE_RPC_HOST = os.getenv('MARKETSTORE_RPC_HOST', 'localhost')
    MARKETSTORE_RPC_PORT = os.getenv('MARKETSTORE_RPC_PORT', '5993')
    MARKETSTORE_RPC_PATH = os.getenv('MARKETSTORE_RPC_PATH', '/rpc')
Exemple #18
0
 def _handle_view(self, name, **kwargs):
     if not self.is_accessible():
         if not user.is_authenticated:
             return redirect(
                 url_for('ADMIN_LOGIN_ENDPOINT', next=request.url))
         abort(HTTPStatus.FORBIDDEN)
 def test_html_get(self, client):
     client.login_user()
     r = client.get('security_controller.logout')
     assert r.status_code == 302
     assert r.path == url_for('SECURITY_POST_LOGOUT_REDIRECT_ENDPOINT')
     assert isinstance(current_user._get_current_object(), AnonymousUser)
Exemple #20
0
class Config(AppBundleConfig):
    ##########################################################################
    # flask                                                                  #
    ##########################################################################
    DEBUG = get_boolean_env('FLASK_DEBUG', False)
    SECRET_KEY = os.getenv('FLASK_SECRET_KEY', 'change-me-to-a-secret-key!')
    APP_ROOT = os.path.abspath(os.path.dirname(__file__))
    PROJECT_ROOT = os.path.abspath(os.path.join(APP_ROOT, os.pardir))
    DATA_FOLDER = os.path.join(PROJECT_ROOT, 'data')
    SSH_FOLDER = os.path.join(PROJECT_ROOT, 'ssh')

    app_dirs = AppDirs('zillean')
    APP_CACHE_FOLDER = app_dirs.user_cache_dir
    APP_DATA_FOLDER = app_dirs.user_data_dir
    MAX_CONTENT_LENGTH = 2 * 1024 * 1024 * 1024
    # SERVER_NAME=os.getenv('FLASK_SERVER_NAME', 'backend:5000')

    ##########################################################################
    # celery                                                                 #
    ##########################################################################
    CELERY_BROKER_URL = 'redis://{host}:{port}/0'.format(
        host=os.getenv('FLASK_REDIS_HOST', '127.0.0.1'),
        port=os.getenv('FLASK_REDIS_PORT', 6379),
    )
    CELERY_RESULT_BACKEND = CELERY_BROKER_URL

    ##########################################################################
    # mail                                                                   #
    ##########################################################################
    MAIL_SERVER = os.environ.get('FLASK_MAIL_HOST', 'localhost')
    MAIL_PORT = int(os.environ.get('FLASK_MAIL_PORT', 25))
    MAIL_USE_TLS = get_boolean_env('FLASK_MAIL_USE_TLS', False)
    MAIL_USE_SSL = get_boolean_env('FLASK_MAIL_USE_SSL', False)
    MAIL_USERNAME = os.environ.get('FLASK_MAIL_USERNAME', None)
    MAIL_PASSWORD = os.environ.get('FLASK_MAIL_PASSWORD', None)
    MAIL_ADMINS = [os.environ.get('FLASK_MAIL_ADMIN', 'admin@localhost')]
    MAIL_DEFAULT_SENDER = (
        os.environ.get('FLASK_MAIL_DEFAULT_SENDER_NAME', 'Zillean'),
        os.environ.get('FLASK_MAIL_DEFAULT_SENDER_EMAIL',
                       f"noreply@{os.environ.get('FLASK_DOMAIN', 'zillean.ai')}")
    )

    ##########################################################################
    # session/cookies                                                        #
    ##########################################################################
    SESSION_TYPE = 'redis'
    SESSION_REDIS = redis.Redis(
        host=os.getenv('FLASK_REDIS_HOST', '127.0.0.1'),
        port=int(os.getenv('FLASK_REDIS_PORT', 6379)),
    )
    SESSION_PROTECTION = 'strong'
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SECURE = True
    REMEMBER_COOKIE_HTTPONLY = True

    # SECURITY_TOKEN_MAX_AGE is fixed from time of token generation;
    # it does not update on refresh like a session timeout would. for that,
    # we set (the ironically named) PERMANENT_SESSION_LIFETIME
    PERMANENT_SESSION_LIFETIME = timedelta(minutes=60)

    ##########################################################################
    # security                                                               #
    ##########################################################################
    FLASH_MESSAGES = False
    SECURITY_PASSWORD_SALT = os.environ.get('FLASK_PASSWORD_SALT', 'security-password-salt')
    SECURITY_CONFIRMABLE = True
    SECURITY_REGISTERABLE = True
    SECURITY_RECOVERABLE = True
    SECURITY_CHANGEABLE = True

    ADMIN_LOGIN_ENDPOINT = 'admin.login'
    ADMIN_LOGOUT_ENDPOINT = 'admin.logout'
    ADMIN_POST_LOGOUT_ENDPOINT = LocalProxy(
        lambda: url_for('frontend.index', _external=True))

    SECURITY_FORGOT_PASSWORD_ENDPOINT = 'frontend.forgot_password'
    SECURITY_API_RESET_PASSWORD_HTTP_GET_REDIRECT = 'frontend.reset_password'
    SECURITY_INVALID_RESET_TOKEN_REDIRECT = LocalProxy(
        lambda: url_for('frontend.forgot_password', _external=True) + '?invalid')
    SECURITY_EXPIRED_RESET_TOKEN_REDIRECT = LocalProxy(
        lambda: url_for('frontend.forgot_password', _external=True) + '?expired')
    SECURITY_POST_CONFIRM_REDIRECT_ENDPOINT = LocalProxy(
        lambda: url_for('frontend.index', _external=True) + '?welcome')
    SECURITY_CONFIRM_ERROR_REDIRECT_ENDPOINT = LocalProxy(
        lambda: url_for('frontend.resend_confirmation_email', _external=True))

    ##########################################################################
    # database                                                               #
    ##########################################################################
    SQLALCHEMY_DATABASE_URI = '{engine}://{user}:{pw}@{host}:{port}/{db}'.format(
        engine=os.getenv('FLASK_DATABASE_ENGINE', 'postgresql+psycopg2'),
        user=os.getenv('FLASK_DATABASE_USER', 'flask_api'),
        pw=os.getenv('FLASK_DATABASE_PASSWORD', 'flask_api'),
        host=os.getenv('FLASK_DATABASE_HOST', '127.0.0.1'),
        port=os.getenv('FLASK_DATABASE_PORT', 5432),
        db=os.getenv('FLASK_DATABASE_NAME', 'flask_api'))

    WEBPACK_MANIFEST_PATH = os.path.join('static', 'assets', 'manifest.json')

    OAUTH_REMOTE_APP_GITLAB = dict(
        consumer_key=os.getenv('OAUTH_GITLAB_CONSUMER_KEY', ''),
        consumer_secret=os.getenv('OAUTH_GITLAB_CONSUMER_SECRET', ''),
        base_url='https://gitlab.com/api/v4/user',
        access_token_url='https://gitlab.com/oauth/token',
        access_token_method='POST',
        authorize_url='https://gitlab.com/oauth/authorize',
        request_token_url=None,
        request_token_params={'scope': 'openid read_user'},
    )
 def test_token_invalid(self, api_client):
     r = api_client.get('security_api.reset_password', token='fail')
     assert r.status_code == 302
     assert r.path == url_for('SECURITY_INVALID_RESET_TOKEN_REDIRECT')