authentik.stages.authenticator_totp.stage
TOTP Setup stage
1"""TOTP Setup stage""" 2 3from urllib.parse import quote 4 5from django.http import HttpRequest, HttpResponse 6from django.http.request import QueryDict 7from django.utils.translation import gettext_lazy as _ 8from rest_framework.fields import CharField 9from rest_framework.serializers import ValidationError 10 11from authentik.flows.challenge import ( 12 Challenge, 13 ChallengeResponse, 14 WithUserInfoChallenge, 15) 16from authentik.flows.stage import ChallengeStageView 17from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage, TOTPDevice 18from authentik.stages.authenticator_totp.settings import OTP_TOTP_ISSUER 19 20SESSION_TOTP_DEVICE = "totp_device" 21 22 23class AuthenticatorTOTPChallenge(WithUserInfoChallenge): 24 """TOTP Setup challenge""" 25 26 config_url = CharField() 27 component = CharField(default="ak-stage-authenticator-totp") 28 29 30class AuthenticatorTOTPChallengeResponse(ChallengeResponse): 31 """TOTP Challenge response, device is set by get_response_instance""" 32 33 device: TOTPDevice 34 35 code = CharField() 36 component = CharField(default="ak-stage-authenticator-totp") 37 38 def validate_code(self, code: str) -> str: 39 """Validate totp code""" 40 if not self.device: 41 raise ValidationError(_("Code does not match")) 42 if not self.device.verify_token(code): 43 self.device.confirmed = False 44 raise ValidationError(_("Code does not match")) 45 return code 46 47 48class AuthenticatorTOTPStageView(ChallengeStageView): 49 """OTP totp Setup stage""" 50 51 response_class = AuthenticatorTOTPChallengeResponse 52 53 def get_challenge(self, *args, **kwargs) -> Challenge: 54 device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE] 55 return AuthenticatorTOTPChallenge( 56 data={ 57 "config_url": device.config_url.replace( 58 OTP_TOTP_ISSUER, quote(self.request.brand.branding_title) 59 ), 60 } 61 ) 62 63 def get_response_instance(self, data: QueryDict) -> ChallengeResponse: 64 response = super().get_response_instance(data) 65 response.device = self.request.session.get(SESSION_TOTP_DEVICE) 66 return response 67 68 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 69 user = self.get_pending_user() 70 if not user.is_authenticated: 71 self.logger.debug("No pending user, continuing") 72 return self.executor.stage_ok() 73 74 stage: AuthenticatorTOTPStage = self.executor.current_stage 75 76 if SESSION_TOTP_DEVICE not in self.request.session: 77 device = TOTPDevice( 78 user=user, confirmed=False, digits=stage.digits, name="TOTP Authenticator" 79 ) 80 81 self.request.session[SESSION_TOTP_DEVICE] = device 82 return super().get(request, *args, **kwargs) 83 84 def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: 85 """TOTP Token is validated by challenge""" 86 device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE] 87 device.confirmed = True 88 device.save() 89 del self.request.session[SESSION_TOTP_DEVICE] 90 return self.executor.stage_ok()
SESSION_TOTP_DEVICE =
'totp_device'
24class AuthenticatorTOTPChallenge(WithUserInfoChallenge): 25 """TOTP Setup challenge""" 26 27 config_url = CharField() 28 component = CharField(default="ak-stage-authenticator-totp")
TOTP Setup challenge
31class AuthenticatorTOTPChallengeResponse(ChallengeResponse): 32 """TOTP Challenge response, device is set by get_response_instance""" 33 34 device: TOTPDevice 35 36 code = CharField() 37 component = CharField(default="ak-stage-authenticator-totp") 38 39 def validate_code(self, code: str) -> str: 40 """Validate totp code""" 41 if not self.device: 42 raise ValidationError(_("Code does not match")) 43 if not self.device.verify_token(code): 44 self.device.confirmed = False 45 raise ValidationError(_("Code does not match")) 46 return code
TOTP Challenge response, device is set by get_response_instance
def
validate_code(self, code: str) -> str:
39 def validate_code(self, code: str) -> str: 40 """Validate totp code""" 41 if not self.device: 42 raise ValidationError(_("Code does not match")) 43 if not self.device.verify_token(code): 44 self.device.confirmed = False 45 raise ValidationError(_("Code does not match")) 46 return code
Validate totp code
49class AuthenticatorTOTPStageView(ChallengeStageView): 50 """OTP totp Setup stage""" 51 52 response_class = AuthenticatorTOTPChallengeResponse 53 54 def get_challenge(self, *args, **kwargs) -> Challenge: 55 device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE] 56 return AuthenticatorTOTPChallenge( 57 data={ 58 "config_url": device.config_url.replace( 59 OTP_TOTP_ISSUER, quote(self.request.brand.branding_title) 60 ), 61 } 62 ) 63 64 def get_response_instance(self, data: QueryDict) -> ChallengeResponse: 65 response = super().get_response_instance(data) 66 response.device = self.request.session.get(SESSION_TOTP_DEVICE) 67 return response 68 69 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 70 user = self.get_pending_user() 71 if not user.is_authenticated: 72 self.logger.debug("No pending user, continuing") 73 return self.executor.stage_ok() 74 75 stage: AuthenticatorTOTPStage = self.executor.current_stage 76 77 if SESSION_TOTP_DEVICE not in self.request.session: 78 device = TOTPDevice( 79 user=user, confirmed=False, digits=stage.digits, name="TOTP Authenticator" 80 ) 81 82 self.request.session[SESSION_TOTP_DEVICE] = device 83 return super().get(request, *args, **kwargs) 84 85 def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: 86 """TOTP Token is validated by challenge""" 87 device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE] 88 device.confirmed = True 89 device.save() 90 del self.request.session[SESSION_TOTP_DEVICE] 91 return self.executor.stage_ok()
OTP totp Setup stage
response_class =
<class 'AuthenticatorTOTPChallengeResponse'>
54 def get_challenge(self, *args, **kwargs) -> Challenge: 55 device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE] 56 return AuthenticatorTOTPChallenge( 57 data={ 58 "config_url": device.config_url.replace( 59 OTP_TOTP_ISSUER, quote(self.request.brand.branding_title) 60 ), 61 } 62 )
Return the challenge that the client should solve
def
get_response_instance( self, data: django.http.request.QueryDict) -> authentik.flows.challenge.ChallengeResponse:
64 def get_response_instance(self, data: QueryDict) -> ChallengeResponse: 65 response = super().get_response_instance(data) 66 response.device = self.request.session.get(SESSION_TOTP_DEVICE) 67 return response
Return the response class type
def
get( self, request: django.http.request.HttpRequest, *args, **kwargs) -> django.http.response.HttpResponse:
69 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 70 user = self.get_pending_user() 71 if not user.is_authenticated: 72 self.logger.debug("No pending user, continuing") 73 return self.executor.stage_ok() 74 75 stage: AuthenticatorTOTPStage = self.executor.current_stage 76 77 if SESSION_TOTP_DEVICE not in self.request.session: 78 device = TOTPDevice( 79 user=user, confirmed=False, digits=stage.digits, name="TOTP Authenticator" 80 ) 81 82 self.request.session[SESSION_TOTP_DEVICE] = device 83 return super().get(request, *args, **kwargs)
Return a challenge for the frontend to solve
def
challenge_valid( self, response: authentik.flows.challenge.ChallengeResponse) -> django.http.response.HttpResponse:
85 def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: 86 """TOTP Token is validated by challenge""" 87 device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE] 88 device.confirmed = True 89 device.save() 90 del self.request.session[SESSION_TOTP_DEVICE] 91 return self.executor.stage_ok()
TOTP Token is validated by challenge