Source code for org.auth

import morepath

from onegov.core.utils import relative_url
from onegov.org import _, log
from onegov.user.collections import TANCollection
from sedate import utcnow


from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from onegov.org.app import OrgApp
    from onegov.org.request import OrgRequest
    from webob import Response


[docs] class MTANAuth: """ Defines a model for mTAN authentication views. This is similar in functionality to :class:`onegov.user.auth.core.Auth` but it is not tied to a specific user, instead we just remember whether or not we're still authenticated using the browser session. Even with multiple active sessions, logically we treat all sessions for the same phone number as one session, i.e. access limits apply to all browser sessions that are tied to that specific number. """ def __init__(self, app: 'OrgApp', to: str = '/'):
[docs] self.app = app
[docs] self.session = app.session()
[docs] self.application_id = app.application_id
[docs] self.to = relative_url(to)
[docs] def send_mtan(self, request: 'OrgRequest', number: str) -> 'Response': # we are already authenticated just redirect to the page we wanted if request.active_mtan_session: return morepath.redirect(request.transform(self.to)) collection = TANCollection(self.session, scope='mtan_access') client = request.client_addr or 'unknown' obj = collection.add( client=client, mobile_number=number, redirect_to=self.to ) authenticate_url = request.link(self, 'auth') self.app.send_sms(number, request.translate(_( '${mtan} - mTAN for ${organisation}.' '\n' 'Or continue here: ${url}', mapping={ 'organisation': self.app.org.title, 'mtan': obj.tan, # keep the url in the sms short 'url': authenticate_url.rsplit('?', 1)[0] + f'?tan={obj.tan}' } ))) request.info(_( 'We sent an mTAN to the specified number. ' 'Please enter it below or follow the instructions in the SMS.' )) return morepath.redirect(authenticate_url)
[docs] def authenticate( self, request: 'OrgRequest', tan: str, ) -> str | None: # we are already authenticated if request.active_mtan_session: return self.to collection = TANCollection(self.session, scope='mtan_access') result = collection.by_tan(tan) if result is None or 'mobile_number' not in result.meta: client = request.client_addr or 'unknown' log.info(f'Failed login by {client} (mTAN)') return None # record date and number in session request.browser_session.mtan_verified = utcnow() request.browser_session.mtan_number = result.meta['mobile_number'] # expire the tan we just used result.expire() return result.meta.get('redirect_to', self.to)