Exemplo n.º 1
0
    def validate(self):
        if not super().validate():
            # FIXME-identity
            if (set(self.errors.keys()) - set(
                    self.security_utils_service.get_identity_attributes())):
                return False

        self.user = self.security_utils_service.user_loader(self.email.data)

        if self.user is None:
            self.email.errors.append(
                _('flask_unchained.bundles.security:error.user_does_not_exist')
            )
            return False
        if not self.user.password:
            self.password.errors.append(
                _('flask_unchained.bundles.security:error.password_not_set'))
            return False
        if not self.security_utils_service.verify_and_update_password(
                self.password.data, self.user):
            self.password.errors.append(
                _('flask_unchained.bundles.security:error.invalid_password'))
            return False
        if (not self.security_service.security.login_without_confirmation
                and self.security_service.security.confirmable
                and self.user.confirmed_at is None):
            self.email.errors.append(
                _('flask_unchained.bundles.security:error.confirmation_required'
                  ))
            return False
        if not self.user.active:
            self.email.errors.append(
                _('flask_unchained.bundles.security:error.disabled_account'))
            return False
        return True
Exemplo n.º 2
0
class ChangePasswordForm(BaseForm):
    class Meta:
        model = User
        model_fields = {
            'new_password': '******',
            'new_password_confirm': 'password'
        }

    password = fields.PasswordField(
        _('flask_unchained.bundles.security:form_field.password'),
        validators=[password_required])
    new_password = fields.PasswordField(
        _('flask_unchained.bundles.security:form_field.new_password'),
        validators=[password_required])
    new_password_confirm = fields.PasswordField(
        _('flask_unchained.bundles.security:form_field.retype_password'),
        validators=[new_password_equal, password_required])

    submit = fields.SubmitField(
        _('flask_unchained.bundles.security:form_submit.change_password'))

    def validate(self):
        result = super().validate()

        if not security_utils_service.verify_password(current_user,
                                                      self.password.data):
            self.password.errors.append(
                _('flask_unchained.bundles.security:error.invalid_password'))
            return False
        elif self.password.data == self.new_password.data:
            self.new_password.errors.append(
                _('flask_unchained.bundles.security:error.password_is_the_same'
                  ))
            return False
        return result
Exemplo n.º 3
0
    def process_login_errors(self, form):
        """
        An opportunity to modify the login form's error messages before returning
        the response to the user. The idea is to try not to leak excess account info
        without being too unfriendly to actually-valid-users.

        :param form: An instance of the config option `SECURITY_LOGIN_FORM` class.
        """
        account_disabled = _(
            'flask_unchained.bundles.security:error.disabled_account')
        confirmation_required = _(
            'flask_unchained.bundles.security:error.confirmation_required')
        if account_disabled in form.errors.get('email', []):
            error = account_disabled
        elif confirmation_required in form.errors.get('email', []):
            error = confirmation_required
        else:
            identity_attrs = app.config.get(
                'SECURITY_USER_IDENTITY_ATTRIBUTES')
            error = f"Invalid {', '.join(identity_attrs)} and/or password."

        # wipe out all individual field errors, we just want a single form-level error
        form._errors = {'_error': [error]}
        for field in form._fields.values():
            field.errors = None
        return form
Exemplo n.º 4
0
class PasswordFormMixin:
    password = fields.PasswordField(
        _('flask_unchained.bundles.security:form_field.password'),
        validators=[password_required])
    password_confirm = fields.PasswordField(
        _('flask_unchained.bundles.security:form_field.retype_password'),
        validators=[password_equal, password_required])
Exemplo n.º 5
0
class ForgotPasswordForm(BaseForm):
    class Meta:
        model = User

    user = None
    email = StringField(_('flask_unchained.bundles.security:form_field.email'),
                        validators=[valid_user_email])
    submit = fields.SubmitField(
        _('flask_unchained.bundles.security:form_submit.recover_password'))
Exemplo n.º 6
0
    def reset_password(self, token):
        """
        View function verify a users reset password token from the email we sent to them.
        It also handles the form for them to set a new password.
        Supports html and json requests.
        """
        expired, invalid, user = \
            self.security_utils_service.reset_password_token_status(token)
        if invalid:
            self.flash(_(
                'flask_unchained.bundles.security:flash.invalid_reset_password_token'
            ),
                       category='error')
            return self.redirect('SECURITY_INVALID_RESET_TOKEN_REDIRECT')
        elif expired:
            self.security_service.send_reset_password_instructions(user)
            self.flash(_(
                'flask_unchained.bundles.security:flash.password_reset_expired',
                email=user.email,
                within=app.config.get('SECURITY_RESET_PASSWORD_WITHIN')),
                       category='error')
            return self.redirect('SECURITY_EXPIRED_RESET_TOKEN_REDIRECT')

        spa_redirect = app.config.get(
            'SECURITY_API_RESET_PASSWORD_HTTP_GET_REDIRECT')
        if request.method == 'GET' and spa_redirect:
            return self.redirect(spa_redirect, token=token, _external=True)

        form = self._get_form('SECURITY_RESET_PASSWORD_FORM')
        if form.validate_on_submit():
            self.security_service.reset_password(user, form.password.data)
            self.security_service.login_user(user)
            self.after_this_request(self._commit)
            self.flash(
                _('flask_unchained.bundles.security:flash.password_reset'),
                category='success')
            if request.is_json:
                return self.jsonify({
                    'token': user.get_auth_token(),
                    'user': user
                })
            return self.redirect('SECURITY_POST_RESET_REDIRECT_ENDPOINT',
                                 'SECURITY_POST_LOGIN_REDIRECT_ENDPOINT')

        elif form.errors and request.is_json:
            return self.errors(form.errors)

        return self.render('reset_password',
                           reset_password_form=form,
                           reset_password_token=token,
                           **self.security.run_ctx_processor('reset_password'))
Exemplo n.º 7
0
    def validate(self):
        result = super().validate()

        if not self.security_utils_service.verify_and_update_password(
                self.password.data, current_user):
            self.password.errors.append(
                _('flask_unchained.bundles.security:error.invalid_password'))
            return False
        if self.password.data == self.new_password.data:
            self.new_password.errors.append(
                _('flask_unchained.bundles.security:error.password_is_the_same'
                  ))
            return False
        return result
Exemplo n.º 8
0
    def authorized(self, remote_app):
        provider = getattr(self.oauth, remote_app)
        resp = provider.authorized_response()
        if resp is None or resp.get('access_token') is None:
            abort(
                HTTPStatus.UNAUTHORIZED,
                'errorCode={error} error={description}'.format(
                    error=request.args['error'],
                    description=request.args['error_description'],
                ))

        session['oauth_token'] = resp['access_token']

        email, data = self.oauth_service.get_user_details(provider)
        user, created = self.user_manager.get_or_create(email=email,
                                                        defaults=data,
                                                        commit=True)
        if created:
            self.security_service.register_user(
                user, _force_login_without_confirmation=True)
        else:
            self.security_service.login_user(user, force=True)

        self.oauth_service.on_authorized(provider)
        self.flash(_('flask_unchained.bundles.security:flash.login'),
                   category='success')
        return self.redirect('SECURITY_POST_LOGIN_REDIRECT_ENDPOINT')
Exemplo n.º 9
0
    def login(self):
        """
        View function to log a user in. Supports html and json requests.
        """
        form = self._get_form('SECURITY_LOGIN_FORM')
        if (form.validate_on_submit() and self.security_service.login_user(
                form.user, form.remember.data)):
            self.after_this_request(self._commit)
            if request.is_json:
                return self.jsonify({
                    'token': form.user.get_auth_token(),
                    'user': form.user
                })
            self.flash(_('flask_unchained.bundles.security:flash.login'),
                       category='success')
            return self.redirect('SECURITY_POST_LOGIN_REDIRECT_ENDPOINT')

        elif form.errors:
            form = self.security_service.process_login_errors(form)
            if request.is_json:
                return self.jsonify({'error': form.errors.get('_error')[0]},
                                    code=HTTPStatus.UNAUTHORIZED)

        return self.render('login',
                           login_user_form=form,
                           **self.security.run_ctx_processor('login'))
Exemplo n.º 10
0
    def change_password(self):
        """
        View function for a user to change their password.
        Supports html and json requests.
        """
        form = self._get_form('SECURITY_CHANGE_PASSWORD_FORM')
        if form.validate_on_submit():
            self.security_service.change_password(
                current_user._get_current_object(), form.new_password.data)
            self.after_this_request(self._commit)
            self.flash(
                _('flask_unchained.bundles.security:flash.password_change'),
                category='success')
            if request.is_json:
                return self.jsonify({'token': current_user.get_auth_token()})
            return self.redirect('SECURITY_POST_CHANGE_REDIRECT_ENDPOINT',
                                 'SECURITY_POST_LOGIN_REDIRECT_ENDPOINT')

        elif form.errors and request.is_json:
            return self.errors(form.errors)

        return self.render(
            'change_password',
            change_password_form=form,
            **self.security.run_ctx_processor('change_password'))
Exemplo n.º 11
0
class ResetPasswordForm(BaseForm, PasswordFormMixin):
    class Meta:
        model = User
        model_fields = {'password_confirm': 'password'}

    submit = SubmitField(
        _('flask_unchained.bundles.security:form_submit.reset_password'))
Exemplo n.º 12
0
 def validate(self):
     if not super(SendConfirmationForm, self).validate():
         return False
     if self.user.confirmed_at is not None:
         self.email.errors.append(
             _('flask_unchained.bundles.security:error.already_confirmed'))
         return False
     return True
Exemplo n.º 13
0
class LoginForm(BaseForm, NextFormMixin):
    """The default login form"""
    class Meta:
        model = User

    email = fields.StringField(
        _('flask_unchained.bundles.security:form_field.email'))
    password = fields.PasswordField(
        _('flask_unchained.bundles.security:form_field.password'))
    remember = fields.BooleanField(
        _('flask_unchained.bundles.security:form_field.remember_me'))
    submit = fields.SubmitField(
        _('flask_unchained.bundles.security:form_submit.login'))

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.user = None

        if not self.next.data:
            self.next.data = request.args.get('next', '')
        self.remember.default = app.config.SECURITY_DEFAULT_REMEMBER_ME

    def validate(self):
        if not super().validate():
            # FIXME-identity
            if (set(self.errors.keys()) -
                    set(security_utils_service.get_identity_attributes())):
                return False

        self.user = security_utils_service.user_loader(self.email.data)

        if self.user is None:
            self.email.errors.append(
                _('flask_unchained.bundles.security:error.user_does_not_exist')
            )
            return False
        elif not self.password.data:
            self.password.errors.append(
                _('flask_unchained.bundles.security:password_required'))
            return False
        elif not security_utils_service.verify_password(
                self.user, self.password.data):
            self.password.errors.append(
                _('flask_unchained.bundles.security:error.invalid_password'))
            return False
        return True
Exemplo n.º 14
0
    def confirm_email(self, token):
        """
        View function to confirm a user's token from the confirmation email send to them.
        Supports html and json requests.
        """
        expired, invalid, user = \
            self.security_utils_service.confirm_email_token_status(token)
        if not user or invalid:
            invalid = True
            self.flash(_(
                'flask_unchained.bundles.security:flash.invalid_confirmation_token'
            ),
                       category='error')

        already_confirmed = user is not None and user.confirmed_at is not None
        if expired and not already_confirmed:
            self.security_service.send_email_confirmation_instructions(user)
            self.flash(_(
                'flask_unchained.bundles.security:flash.confirmation_expired',
                email=user.email,
                within=app.config.SECURITY_CONFIRM_EMAIL_WITHIN),
                       category='error')

        if invalid or (expired and not already_confirmed):
            return self.redirect(
                'SECURITY_CONFIRM_ERROR_REDIRECT_ENDPOINT',
                'security_controller.send_confirmation_email')

        if self.security_service.confirm_user(user):
            self.after_this_request(self._commit)
            self.flash(
                _('flask_unchained.bundles.security:flash.email_confirmed'),
                category='success')
        else:
            self.flash(
                _('flask_unchained.bundles.security:flash.already_confirmed'),
                category='info')

        if user != current_user:
            self.security_service.logout_user()
            self.security_service.login_user(user)

        return self.redirect('SECURITY_POST_CONFIRM_REDIRECT_ENDPOINT',
                             'SECURITY_POST_LOGIN_REDIRECT_ENDPOINT')
Exemplo n.º 15
0
class RegisterForm(BaseForm, PasswordFormMixin, NextFormMixin):
    class Meta:
        model = User

    email = StringField(_('flask_unchained.bundles.security:form_field.email'),
                        validators=[unique_user_email])

    submit = SubmitField(
        _('flask_unchained.bundles.security:form_submit.register'))

    field_order = ('email', 'password', 'password_confirm', 'submit')

    def to_dict(self):
        def is_field_and_user_attr(member):
            return isinstance(member, Field) and hasattr(
                self.Meta.model, member.name)

        fields = inspect.getmembers(self, is_field_and_user_attr)
        return dict((key, value.data) for key, value in fields)
Exemplo n.º 16
0
class ChangePasswordForm(BaseForm):
    class Meta:
        model = User
        model_fields = {
            'new_password': '******',
            'new_password_confirm': 'password'
        }

    password = fields.PasswordField(
        _('flask_unchained.bundles.security:form_field.password'))
    new_password = fields.PasswordField(
        _('flask_unchained.bundles.security:form_field.new_password'))
    new_password_confirm = fields.PasswordField(
        _('flask_unchained.bundles.security:form_field.retype_password'),
        validators=[new_password_equal])

    submit = fields.SubmitField(
        _('flask_unchained.bundles.security:form_submit.change_password'))

    def __init__(self,
                 *args,
                 security_utils_service: SecurityUtilsService = injectable,
                 **kwargs):
        super().__init__(*args, **kwargs)
        self.security_utils_service = security_utils_service

    def validate(self):
        result = super().validate()

        if not self.security_utils_service.verify_and_update_password(
                self.password.data, current_user):
            self.password.errors.append(
                _('flask_unchained.bundles.security:error.invalid_password'))
            return False
        if self.password.data == self.new_password.data:
            self.new_password.errors.append(
                _('flask_unchained.bundles.security:error.password_is_the_same'
                  ))
            return False
        return result
Exemplo n.º 17
0
    def logout(self):
        """
        View function to log a user out. Supports html and json requests.
        """
        if current_user.is_authenticated:
            self.security_service.logout_user()

        if request.is_json:
            return '', HTTPStatus.NO_CONTENT

        self.flash(_('flask_unchained.bundles.security:flash.logout'),
                   category='success')
        return self.redirect('SECURITY_POST_LOGOUT_REDIRECT_ENDPOINT')
Exemplo n.º 18
0
class SendConfirmationForm(BaseForm):
    class Meta:
        model = User

    user = None
    email = StringField(_('flask_unchained.bundles.security:form_field.email'),
                        validators=[valid_user_email])
    submit = SubmitField(
        _('flask_unchained.bundles.security:form_submit.send_confirmation'))

    def __init__(self, *args, **kwargs):
        super(SendConfirmationForm, self).__init__(*args, **kwargs)
        if request.method == 'GET':
            self.email.data = request.args.get('email', None)

    def validate(self):
        if not super(SendConfirmationForm, self).validate():
            return False
        if self.user.confirmed_at is not None:
            self.email.errors.append(
                _('flask_unchained.bundles.security:error.already_confirmed'))
            return False
        return True
Exemplo n.º 19
0
 def _get_login_manager(
     self,
     app: FlaskUnchained,
     anonymous_user: AnonymousUser,
 ) -> LoginManager:
     """
     Get an initialized instance of Flask Login's
     :class:`~flask_login.LoginManager`.
     """
     login_manager = LoginManager()
     login_manager.anonymous_user = anonymous_user or AnonymousUser
     login_manager.localize_callback = _
     login_manager.request_loader(self._request_loader)
     login_manager.user_loader(
         lambda *a, **kw: self.security_utils_service.user_loader(*a, **kw))
     login_manager.login_view = 'security_controller.login'
     login_manager.login_message = _(
         'flask_unchained.bundles.security:error.login_required')
     login_manager.login_message_category = 'info'
     login_manager.needs_refresh_message = _(
         'flask_unchained.bundles.security:error.fresh_login_required')
     login_manager.needs_refresh_message_category = 'info'
     login_manager.init_app(app)
     return login_manager
Exemplo n.º 20
0
    def validate(self):
        if not super().validate():
            # FIXME-identity
            if (set(self.errors.keys()) -
                    set(security_utils_service.get_identity_attributes())):
                return False

        self.user = security_utils_service.user_loader(self.email.data)

        if self.user is None:
            self.email.errors.append(
                _('flask_unchained.bundles.security:error.user_does_not_exist')
            )
            return False
        elif not self.password.data:
            self.password.errors.append(
                _('flask_unchained.bundles.security:password_required'))
            return False
        elif not security_utils_service.verify_password(
                self.user, self.password.data):
            self.password.errors.append(
                _('flask_unchained.bundles.security:error.invalid_password'))
            return False
        return True
Exemplo n.º 21
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
Exemplo n.º 22
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)
Exemplo n.º 23
0
    def __call__(self, value):
        super().__call__(value)
        if not value:
            return

        message = self.msg
        if message is None:
            message = _('flask_unchained.bundles.security:email_invalid')

        if not value or '@' not in value:
            raise ValidationError(message)

        user_part, domain_part = value.rsplit('@', 1)

        if not self.user_regex.match(user_part):
            raise ValidationError(message)

        if not self.validate_hostname(domain_part):
            raise ValidationError(message)
Exemplo n.º 24
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)
Exemplo n.º 25
0
    def reset_password(self, user, password):
        """
        Service method to reset a user's password. The same as :meth:`change_password`
        except we this method sends a different notification email.

        Sends signal `password_reset`.

        :param user:
        :param password:
        :return:
        """
        user.password = password
        self.user_manager.save(user)
        if app.config.SECURITY_SEND_PASSWORD_RESET_NOTICE_EMAIL:
            self.send_mail(
                _('flask_unchained.bundles.security:email_subject.password_reset_notice'),
                to=user.email,
                template='security/email/password_reset_notice.html',
                user=user)
        password_reset.send(app._get_current_object(), user=user)
Exemplo n.º 26
0
    def forgot_password(self):
        """
        View function to request a password recovery email with a reset token.
        Supports html and json requests.
        """
        form = self._get_form('SECURITY_FORGOT_PASSWORD_FORM')
        if form.validate_on_submit():
            self.security_service.send_reset_password_instructions(form.user)
            self.flash(_(
                'flask_unchained.bundles.security:flash.password_reset_request',
                email=form.user.email),
                       category='info')
            if request.is_json:
                return '', HTTPStatus.NO_CONTENT

        elif form.errors and request.is_json:
            return self.errors(form.errors)

        return self.render(
            'forgot_password',
            forgot_password_form=form,
            **self.security.run_ctx_processor('forgot_password'))
Exemplo n.º 27
0
    def send_confirmation_email(self):
        """
        View function which sends confirmation token and instructions to a user.
        """
        form = self._get_form('SECURITY_SEND_CONFIRMATION_FORM')
        if form.validate_on_submit():
            self.security_service.send_email_confirmation_instructions(
                form.user)
            self.flash(_(
                'flask_unchained.bundles.security:flash.confirmation_request',
                email=form.user.email),
                       category='info')
            if request.is_json:
                return '', HTTPStatus.NO_CONTENT

        elif form.errors and request.is_json:
            return self.errors(form.errors)

        return self.render(
            'send_confirmation_email',
            send_confirmation_form=form,
            **self.security.run_ctx_processor('send_confirmation_email'))
Exemplo n.º 28
0
    def change_password(self, user, password, send_email=None):
        """
        Service method to change a user's password.

        Sends signal `password_changed`.

        :param user: The :class:`User`'s password to change.
        :param password: The new password.
        :param send_email: Whether or not to override the config option
                           ``SECURITY_SEND_PASSWORD_CHANGED_EMAIL`` and force
                           either sending or not sending an email.
        """
        user.password = password
        self.user_manager.save(user)
        if send_email or (app.config.SECURITY_SEND_PASSWORD_CHANGED_EMAIL
                          and send_email is None):
            self.send_mail(
                _('flask_unchained.bundles.security:email_subject.password_changed_notice'),
                to=user.email,
                template='security/email/password_changed_notice.html',
                user=user)
        password_changed.send(app._get_current_object(), user=user)
Exemplo n.º 29
0
    def _get_validators(cls, column_name):
        rv = []
        col = cls.__table__.c.get(column_name)
        validators = cls.__validators__.get(column_name, [])
        for validator in validators:
            if isinstance(validator, str) and hasattr(cls, validator):
                rv.append(getattr(cls, validator))
            else:
                if inspect.isclass(validator):
                    validator = validator()
                rv.append(validator)

        if col is not None:
            not_null = not col.primary_key and not col.nullable
            required_msg = col.info and col.info.get('required', None)
            if not_null or required_msg:
                if isinstance(required_msg, bool):
                    required_msg = None
                elif isinstance(required_msg, str):
                    required_msg = _(required_msg)
                rv.append(Required(required_msg or None))
        return rv
Exemplo n.º 30
0
    def login(self):
        """
        View function to log a user in. Supports html and json requests.
        """
        form = self._get_form('SECURITY_LOGIN_FORM')
        if form.validate_on_submit():
            try:
                self.security_service.login_user(form.user, form.remember.data)
            except AuthenticationError as e:
                form._errors = {'_error': [str(e)]}
            else:
                self.after_this_request(self._commit)
                if request.is_json:
                    return self.jsonify({
                        'token': form.user.get_auth_token(),
                        'user': form.user
                    })
                self.flash(_('flask_unchained.bundles.security:flash.login'),
                           category='success')
                return self.redirect('SECURITY_POST_LOGIN_REDIRECT_ENDPOINT')
        else:
            # FIXME-identity
            identity_attrs = app.config.SECURITY_USER_IDENTITY_ATTRIBUTES
            msg = f"Invalid {', '.join(identity_attrs)} and/or password."

            # we just want a single top-level form error
            form._errors = {'_error': [msg]}
            for field in form._fields.values():
                field.errors = None

        if form.errors and request.is_json:
            return self.jsonify({'error': form.errors.get('_error')[0]},
                                code=HTTPStatus.UNAUTHORIZED)

        return self.render('login',
                           login_user_form=form,
                           **self.security.run_ctx_processor('login'))