use more minimal payload for QR code sake

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer 2023-07-24 23:23:01 +02:00
parent fc5f5ed117
commit cb60fc2c7b
No known key found for this signature in database
5 changed files with 62 additions and 14 deletions

View File

@ -7,12 +7,19 @@ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django_otp.models import Device from django_otp.models import Device
from rest_framework.serializers import BaseSerializer, Serializer from rest_framework.serializers import BaseSerializer, Serializer
from authentik.core.models import ExpiringModel
from authentik.core.types import UserSettingSerializer from authentik.core.types import UserSettingSerializer
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
from authentik.lib.generators import generate_id
from authentik.lib.models import SerializerModel from authentik.lib.models import SerializerModel
def default_token_key():
"""Default token key"""
return generate_id(40)
class AuthenticatorMobileStage(ConfigurableStage, FriendlyNamedStage, Stage): class AuthenticatorMobileStage(ConfigurableStage, FriendlyNamedStage, Stage):
"""Setup Duo authenticator devices""" """Setup Duo authenticator devices"""
@ -70,3 +77,9 @@ class MobileDevice(SerializerModel, Device):
class Meta: class Meta:
verbose_name = _("Mobile Device") verbose_name = _("Mobile Device")
verbose_name_plural = _("Mobile Devices") verbose_name_plural = _("Mobile Devices")
class MobileDeviceToken(ExpiringModel):
device = models.ForeignKey(MobileDevice, on_delete=models.CASCADE, null=True)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
token = models.TextField(default=default_token_key)

View File

@ -2,6 +2,7 @@
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.timezone import now from django.utils.timezone import now
from rest_framework.fields import CharField from rest_framework.fields import CharField
from authentik.core.api.utils import PassiveSerializer
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.flows.challenge import ( from authentik.flows.challenge import (
@ -11,16 +12,22 @@ from authentik.flows.challenge import (
WithUserInfoChallenge, WithUserInfoChallenge,
) )
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_mobile.models import AuthenticatorMobileStage from authentik.stages.authenticator_mobile.models import AuthenticatorMobileStage, MobileDeviceToken
SESSION_KEY_MOBILE_ENROLL = "authentik/stages/authenticator_mobile/enroll" FLOW_PLAN_MOBILE_ENROLL = "authentik/stages/authenticator_mobile/enroll"
class AuthenticatorMobilePayloadChallenge(PassiveSerializer):
"""Payload within the QR code given to the mobile app, hence the short variable names"""
u = CharField(required=False, help_text="Server URL")
s = CharField(required=False, help_text="Stage UUID")
t = CharField(required=False, help_text="Initial Token")
class AuthenticatorMobileChallenge(WithUserInfoChallenge): class AuthenticatorMobileChallenge(WithUserInfoChallenge):
"""Mobile Challenge""" """Mobile Challenge"""
authentik_url = CharField(required=True) payload = AuthenticatorMobilePayloadChallenge(required=True)
stage_uuid = CharField(required=True)
component = CharField(default="ak-stage-authenticator-mobile") component = CharField(default="ak-stage-authenticator-mobile")
@ -35,13 +42,28 @@ class AuthenticatorMobileStageView(ChallengeStageView):
response_class = AuthenticatorMobileChallengeResponse response_class = AuthenticatorMobileChallengeResponse
def prepare(self):
if FLOW_PLAN_MOBILE_ENROLL in self.executor.plan.context:
return
token = MobileDeviceToken.objects.create(
user=self.get_pending_user(),
)
self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL] = token
def get_challenge(self, *args, **kwargs) -> Challenge: def get_challenge(self, *args, **kwargs) -> Challenge:
stage: AuthenticatorMobileStage = self.executor.current_stage stage: AuthenticatorMobileStage = self.executor.current_stage
self.prepare()
payload = AuthenticatorMobilePayloadChallenge(data={
# TODO: use cloud gateway?
"u": self.request.get_host(),
"s": str(stage.stage_uuid),
"t": self.executor.plan[FLOW_PLAN_MOBILE_ENROLL].token,
})
payload.is_valid()
return AuthenticatorMobileChallenge( return AuthenticatorMobileChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"authentik_url": self.request.get_host(), "payload": payload.validated_data,
"stage_uuid": str(stage.stage_uuid),
} }
) )

View File

@ -30183,15 +30183,12 @@ components:
type: string type: string
pending_user_avatar: pending_user_avatar:
type: string type: string
authentik_url: payload:
type: string $ref: '#/components/schemas/AuthenticatorMobilePayloadChallenge'
stage_uuid:
type: string
required: required:
- authentik_url - payload
- pending_user - pending_user
- pending_user_avatar - pending_user_avatar
- stage_uuid
- type - type
AuthenticatorMobileChallengeResponseRequest: AuthenticatorMobileChallengeResponseRequest:
type: object type: object
@ -30201,6 +30198,20 @@ components:
type: string type: string
minLength: 1 minLength: 1
default: ak-stage-authenticator-mobile default: ak-stage-authenticator-mobile
AuthenticatorMobilePayloadChallenge:
type: object
description: Payload within the QR code given to the mobile app, hence the short
variable names
properties:
u:
type: string
description: Server URL
s:
type: string
description: Stage UUID
t:
type: string
description: Initial Token
AuthenticatorMobileStage: AuthenticatorMobileStage:
type: object type: object
description: AuthenticatorMobileStage Serializer description: AuthenticatorMobileStage Serializer

View File

@ -338,7 +338,9 @@ export class FlowExecutor extends Interface implements StageHost {
.challenge=${this.challenge} .challenge=${this.challenge}
></ak-stage-authenticator-duo>`; ></ak-stage-authenticator-duo>`;
case "ak-stage-authenticator-mobile": case "ak-stage-authenticator-mobile":
await import("@goauthentik/flow/stages/authenticator_mobile/AuthenticatorMobileStage"); await import(
"@goauthentik/flow/stages/authenticator_mobile/AuthenticatorMobileStage"
);
return html`<ak-stage-authenticator-mobile return html`<ak-stage-authenticator-mobile
.host=${this as StageHost} .host=${this as StageHost}
.challenge=${this.challenge} .challenge=${this.challenge}

View File

@ -70,7 +70,7 @@ export class AuthenticatorMobileStage extends BaseStage<
</div> </div>
</ak-form-static> </ak-form-static>
<div class="qr-container"> <div class="qr-container">
<qr-code data="${JSON.stringify(this.challenge)}"></qr-code> <qr-code data="${JSON.stringify(this.challenge.payload)}"></qr-code>
</div> </div>
</form> </form>
</div> </div>