Source code for swissvotes.views.auth

from morepath import redirect
from onegov.core.security import Personal
from onegov.core.security import Public
from onegov.core.templates import render_template
from onegov.core.utils import relative_url
from onegov.swissvotes import _
from onegov.swissvotes import log
from onegov.swissvotes import SwissvotesApp
from onegov.swissvotes.layouts import DefaultLayout
from onegov.swissvotes.layouts import MailLayout
from onegov.user import Auth
from onegov.user import UserCollection
from onegov.user.auth.second_factor import TOTPFactor
from onegov.user.forms import LoginForm
from onegov.user.forms import PasswordResetForm
from onegov.user.forms import RequestPasswordResetForm
from onegov.user.forms import TOTPForm
from onegov.user.utils import password_reset_url
from webob import exc


from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from onegov.core.types import RenderData
    from onegov.swissvotes.request import SwissvotesRequest
    from webob import Response


@SwissvotesApp.form(
    model=Auth,
    name='login',
    template='login.pt',
    form=LoginForm,
    permission=Public
)
[docs] def handle_login( self: Auth, request: 'SwissvotesRequest', form: LoginForm ) -> 'RenderData | Response': """ Handles the login requests. """ layout = DefaultLayout(self, request) if form.submitted(request): self.to = relative_url(layout.homepage_url) response = self.login_to(request=request, **form.login_data) form.error_message = _('Wrong username or password') # type:ignore else: response = None return response or { 'layout': layout, 'title': _('Login'), 'form': form, 'password_reset_link': request.link( Auth.from_request(request), name='request-password' ), 'button_text': _('Submit'), }
@SwissvotesApp.html( model=Auth, name='logout', permission=Personal )
[docs] def view_logout(self: Auth, request: 'SwissvotesRequest') -> 'Response': """ Handles the logout requests. """ return self.logout_to(request)
@SwissvotesApp.form( model=Auth, name='request-password', template='form.pt', form=RequestPasswordResetForm, permission=Public )
[docs] def handle_password_reset_request( self: Auth, request: 'SwissvotesRequest', form: RequestPasswordResetForm ) -> 'RenderData | Response': """ Handles the password reset requests. """ layout = DefaultLayout(self, request) if form.submitted(request): assert form.email.data is not None users = UserCollection(request.session) user = users.by_username(form.email.data) if user: url = password_reset_url( user, request, request.link(self, name='reset-password') ) assert request.app.mail is not None request.app.send_transactional_email( subject=request.translate(_('Password reset')), receivers=(user.username, ), reply_to=request.app.mail['transactional']['sender'], content=render_template( 'mail_password_reset.pt', request, { 'title': request.translate(_('Password reset')), 'model': None, 'url': url, 'layout': MailLayout(self, request) } ) ) else: log.info( 'Failed password reset attempt by {}'.format( request.client_addr ) ) message = _( 'A password reset link has been sent to ${email}, provided an ' 'account exists for this email address.', mapping={'email': form.email.data} ) request.message(message, 'success') return request.redirect(layout.homepage_url) return { 'layout': layout, 'title': _('Reset password'), 'form': form, 'button_text': _('Submit'), }
@SwissvotesApp.form( model=Auth, name='reset-password', template='form.pt', form=PasswordResetForm, permission=Public )
[docs] def handle_password_reset( self: Auth, request: 'SwissvotesRequest', form: PasswordResetForm ) -> 'RenderData | Response': """ Handles password reset requests. """ layout = DefaultLayout(self, request) if form.submitted(request): if form.update_password(request): request.message(_('Password changed.'), 'success') return request.redirect(layout.login_url or layout.homepage_url) else: form.error_message = _( # type:ignore[attr-defined] 'Wrong username or password reset link not valid any more.' ) log.info( 'Failed password reset attempt by {}'.format( request.client_addr ) ) if isinstance((token := request.params.get('token')), str): form.token.data = token return { 'layout': layout, 'title': _('Reset password'), 'form': form, 'button_text': _('Submit'), }
@SwissvotesApp.form( model=Auth, name='totp', template='form.pt', permission=Public, form=TOTPForm )
[docs] def handle_totp_second_factor( self: Auth, request: 'SwissvotesRequest', form: TOTPForm ) -> 'RenderData | Response': if not request.app.totp_enabled: raise exc.HTTPNotFound() @request.after def respond_with_no_index(response: 'Response') -> None: response.headers['X-Robots-Tag'] = 'noindex' users = UserCollection(request.session) username = request.browser_session.get('pending_username') user = users.by_username(username) if username else None if user is None: if request.is_logged_in: # redirect already logged in users to the redirect_to return self.redirect(request, self.to) request.alert( _('Failed to continue login, please ensure cookies are allowed.') ) return redirect(request.link(self, name='login')) if form.submitted(request): assert form.totp.data is not None factor = self.factors['totp'] assert isinstance(factor, TOTPFactor) if factor.is_valid(request, user, form.totp.data): del request.browser_session['pending_username'] return self.complete_login(user, request) else: request.alert(_('Invalid or expired TOTP provided.')) client = request.client_addr or 'unknown' log.info(f'Failed login by {client} (TOTP)') else: request.info( _('Please enter the six digit code from your authenticator app') ) return { 'layout': DefaultLayout(self, request), 'title': _('Enter TOTP'), 'form': form, 'form_width': 'small' }