stages/authenticator_duo: revamp duo enroll status API

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#3288
This commit is contained in:
Jens Langhammer 2022-08-08 20:38:06 +02:00
parent 2858682866
commit 54c16129ea
4 changed files with 61 additions and 25 deletions

View File

@ -1,10 +1,16 @@
"""AuthenticatorDuoStage API Views"""
from django_filters.rest_framework.backends import DjangoFilterBackend
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from drf_spectacular.utils import (
OpenApiParameter,
OpenApiResponse,
extend_schema,
inline_serializer,
)
from guardian.shortcuts import get_objects_for_user
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.fields import ChoiceField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
@ -57,8 +63,18 @@ class AuthenticatorDuoStageViewSet(UsedByMixin, ModelViewSet):
@extend_schema(
request=OpenApiTypes.NONE,
responses={
204: OpenApiResponse(description="Enrollment successful"),
420: OpenApiResponse(description="Enrollment pending/failed"),
200: inline_serializer(
"DuoDeviceEnrollmentStatusSerializer",
{
"duo_response": ChoiceField(
(
("success", "Success"),
("waiting", "Waiting"),
("invalid", "Invalid"),
)
)
},
),
},
)
@action(methods=["POST"], detail=True, permission_classes=[])
@ -70,11 +86,9 @@ class AuthenticatorDuoStageViewSet(UsedByMixin, ModelViewSet):
user_id = self.request.session.get(SESSION_KEY_DUO_USER_ID)
activation_code = self.request.session.get(SESSION_KEY_DUO_ACTIVATION_CODE)
if not user_id or not activation_code:
return Response(status=420)
return Response(status=400)
status = client.enroll_status(user_id, activation_code)
if status == "success":
return Response(status=204)
return Response(status=420)
return Response({"duo_response": status})
@permission_required(
"", ["authentik_stages_authenticator_duo.add_duodevice", "authentik_core.view_user"]

View File

@ -94,5 +94,5 @@ class AuthenticatorDuoStageView(ChallengeStageView):
return self.executor.stage_ok()
def cleanup(self):
self.request.session.pop(SESSION_KEY_DUO_USER_ID)
self.request.session.pop(SESSION_KEY_DUO_ACTIVATION_CODE)
self.request.session.pop(SESSION_KEY_DUO_USER_ID, None)
self.request.session.pop(SESSION_KEY_DUO_ACTIVATION_CODE, None)

View File

@ -15076,10 +15076,12 @@ paths:
security:
- authentik: []
responses:
'204':
description: Enrollment successful
'420':
description: Enrollment pending/failed
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/DuoDeviceEnrollmentStatus'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
@ -21877,6 +21879,13 @@ components:
required:
- name
- pk
DuoDeviceEnrollmentStatus:
type: object
properties:
duo_response:
$ref: '#/components/schemas/DuoResponseEnum'
required:
- duo_response
DuoDeviceRequest:
type: object
description: Serializer for Duo authenticator devices
@ -21888,6 +21897,12 @@ components:
maxLength: 64
required:
- name
DuoResponseEnum:
enum:
- success
- waiting
- invalid
type: string
EmailChallenge:
type: object
description: Email challenge

View File

@ -21,6 +21,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import {
AuthenticatorDuoChallenge,
AuthenticatorDuoChallengeResponseRequest,
DuoResponseEnum,
StagesApi,
} from "@goauthentik/api";
@ -35,23 +36,29 @@ export class AuthenticatorDuoStage extends BaseStage<
firstUpdated(): void {
const i = setInterval(() => {
this.checkEnrollStatus().then(() => {
clearInterval(i);
this.checkEnrollStatus().then((shouldStop) => {
if (shouldStop) {
clearInterval(i);
}
});
}, 3000);
}
checkEnrollStatus(): Promise<void> {
return new StagesApi(DEFAULT_CONFIG)
.stagesAuthenticatorDuoEnrollmentStatusCreate({
stageUuid: this.challenge?.stageUuid || "",
})
.then(() => {
async checkEnrollStatus(): Promise<boolean> {
const status = await new StagesApi(
DEFAULT_CONFIG,
).stagesAuthenticatorDuoEnrollmentStatusCreate({
stageUuid: this.challenge?.stageUuid || "",
});
console.debug(`authentik/flows/duo: Enrollment status: ${status.duoResponse}`);
switch (status.duoResponse) {
case DuoResponseEnum.Success:
this.host?.submit({});
})
.catch(() => {
console.debug("authentik/flows/duo: Waiting for auth status");
});
return true;
case DuoResponseEnum.Waiting:
break;
}
return false;
}
render(): TemplateResult {