diff --git a/authentik/api/v2/urls.py b/authentik/api/v2/urls.py
index 51fd9bbb8..6dce33066 100644
--- a/authentik/api/v2/urls.py
+++ b/authentik/api/v2/urls.py
@@ -64,7 +64,11 @@ from authentik.sources.oauth.api.source_connection import (
)
from authentik.sources.plex.api import PlexSourceViewSet
from authentik.sources.saml.api import SAMLSourceViewSet
-from authentik.stages.authenticator_duo.api import AuthenticatorDuoStageViewSet
+from authentik.stages.authenticator_duo.api import (
+ AuthenticatorDuoStageViewSet,
+ DuoAdminDeviceViewSet,
+ DuoDeviceViewSet,
+)
from authentik.stages.authenticator_static.api import (
AuthenticatorStaticStageViewSet,
StaticAdminDeviceViewSet,
@@ -159,9 +163,15 @@ router.register("propertymappings/ldap", LDAPPropertyMappingViewSet)
router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
router.register("propertymappings/scope", ScopeMappingViewSet)
+router.register("authenticators/duo", DuoDeviceViewSet)
router.register("authenticators/static", StaticDeviceViewSet)
router.register("authenticators/totp", TOTPDeviceViewSet)
router.register("authenticators/webauthn", WebAuthnDeviceViewSet)
+router.register(
+ "authenticators/admin/duo",
+ DuoAdminDeviceViewSet,
+ basename="admin-duodevice",
+)
router.register(
"authenticators/admin/static",
StaticAdminDeviceViewSet,
diff --git a/authentik/stages/authenticator_duo/models.py b/authentik/stages/authenticator_duo/models.py
index 25a938aaa..7edd1bda5 100644
--- a/authentik/stages/authenticator_duo/models.py
+++ b/authentik/stages/authenticator_duo/models.py
@@ -15,7 +15,7 @@ from authentik.flows.models import ConfigurableStage, Stage
class AuthenticatorDuoStage(ConfigurableStage, Stage):
- """Duo stage"""
+ """Setup Duo authenticator devices"""
client_id = models.TextField()
client_secret = models.TextField()
diff --git a/schema.yml b/schema.yml
index b5c1abaae..a3ba86cb1 100644
--- a/schema.yml
+++ b/schema.yml
@@ -167,6 +167,82 @@ paths:
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
+ /api/v2beta/authenticators/admin/duo/:
+ get:
+ operationId: authenticators_admin_duo_list
+ description: Viewset for Duo authenticator devices (for admins)
+ parameters:
+ - in: query
+ name: name
+ schema:
+ type: string
+ - name: ordering
+ required: false
+ in: query
+ description: Which field to use when ordering the results.
+ schema:
+ type: string
+ - name: page
+ required: false
+ in: query
+ description: A page number within the paginated result set.
+ schema:
+ type: integer
+ - name: page_size
+ required: false
+ in: query
+ description: Number of results to return per page.
+ schema:
+ type: integer
+ - name: search
+ required: false
+ in: query
+ description: A search term.
+ schema:
+ type: string
+ tags:
+ - authenticators
+ security:
+ - authentik: []
+ - cookieAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PaginatedDuoDeviceList'
+ description: ''
+ '400':
+ $ref: '#/components/schemas/ValidationError'
+ '403':
+ $ref: '#/components/schemas/GenericError'
+ /api/v2beta/authenticators/admin/duo/{id}/:
+ get:
+ operationId: authenticators_admin_duo_retrieve
+ description: Viewset for Duo authenticator devices (for admins)
+ parameters:
+ - in: path
+ name: id
+ schema:
+ type: integer
+ description: A unique integer value identifying this Duo Device.
+ required: true
+ tags:
+ - authenticators
+ security:
+ - authentik: []
+ - cookieAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DuoDevice'
+ description: ''
+ '400':
+ $ref: '#/components/schemas/ValidationError'
+ '403':
+ $ref: '#/components/schemas/GenericError'
/api/v2beta/authenticators/admin/static/:
get:
operationId: authenticators_admin_static_list
@@ -395,6 +471,179 @@ paths:
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
+ /api/v2beta/authenticators/duo/:
+ get:
+ operationId: authenticators_duo_list
+ description: Viewset for Duo authenticator devices
+ parameters:
+ - in: query
+ name: name
+ schema:
+ type: string
+ - name: ordering
+ required: false
+ in: query
+ description: Which field to use when ordering the results.
+ schema:
+ type: string
+ - name: page
+ required: false
+ in: query
+ description: A page number within the paginated result set.
+ schema:
+ type: integer
+ - name: page_size
+ required: false
+ in: query
+ description: Number of results to return per page.
+ schema:
+ type: integer
+ - name: search
+ required: false
+ in: query
+ description: A search term.
+ schema:
+ type: string
+ tags:
+ - authenticators
+ security:
+ - authentik: []
+ - cookieAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PaginatedDuoDeviceList'
+ description: ''
+ '400':
+ $ref: '#/components/schemas/ValidationError'
+ '403':
+ $ref: '#/components/schemas/GenericError'
+ /api/v2beta/authenticators/duo/{id}/:
+ get:
+ operationId: authenticators_duo_retrieve
+ description: Viewset for Duo authenticator devices
+ parameters:
+ - in: path
+ name: id
+ schema:
+ type: integer
+ description: A unique integer value identifying this Duo Device.
+ required: true
+ tags:
+ - authenticators
+ security:
+ - authentik: []
+ - cookieAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DuoDevice'
+ description: ''
+ '400':
+ $ref: '#/components/schemas/ValidationError'
+ '403':
+ $ref: '#/components/schemas/GenericError'
+ put:
+ operationId: authenticators_duo_update
+ description: Viewset for Duo authenticator devices
+ parameters:
+ - in: path
+ name: id
+ schema:
+ type: integer
+ description: A unique integer value identifying this Duo Device.
+ required: true
+ tags:
+ - authenticators
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DuoDeviceRequest'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/DuoDeviceRequest'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/DuoDeviceRequest'
+ required: true
+ security:
+ - authentik: []
+ - cookieAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DuoDevice'
+ description: ''
+ '400':
+ $ref: '#/components/schemas/ValidationError'
+ '403':
+ $ref: '#/components/schemas/GenericError'
+ patch:
+ operationId: authenticators_duo_partial_update
+ description: Viewset for Duo authenticator devices
+ parameters:
+ - in: path
+ name: id
+ schema:
+ type: integer
+ description: A unique integer value identifying this Duo Device.
+ required: true
+ tags:
+ - authenticators
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PatchedDuoDeviceRequest'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/PatchedDuoDeviceRequest'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/PatchedDuoDeviceRequest'
+ security:
+ - authentik: []
+ - cookieAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DuoDevice'
+ description: ''
+ '400':
+ $ref: '#/components/schemas/ValidationError'
+ '403':
+ $ref: '#/components/schemas/GenericError'
+ delete:
+ operationId: authenticators_duo_destroy
+ description: Viewset for Duo authenticator devices
+ parameters:
+ - in: path
+ name: id
+ schema:
+ type: integer
+ description: A unique integer value identifying this Duo Device.
+ required: true
+ tags:
+ - authenticators
+ security:
+ - authentik: []
+ - cookieAuth: []
+ responses:
+ '204':
+ description: No response body
+ '400':
+ $ref: '#/components/schemas/ValidationError'
+ '403':
+ $ref: '#/components/schemas/GenericError'
/api/v2beta/authenticators/static/:
get:
operationId: authenticators_static_list
@@ -16316,6 +16565,31 @@ components:
$ref: '#/components/schemas/FlowRequest'
required:
- name
+ DuoDevice:
+ type: object
+ description: Serializer for Duo authenticator devices
+ properties:
+ pk:
+ type: integer
+ readOnly: true
+ title: ID
+ name:
+ type: string
+ description: The human-readable name of this device.
+ maxLength: 64
+ required:
+ - name
+ - pk
+ DuoDeviceRequest:
+ type: object
+ description: Serializer for Duo authenticator devices
+ properties:
+ name:
+ type: string
+ description: The human-readable name of this device.
+ maxLength: 64
+ required:
+ - name
EmailChallenge:
type: object
description: Email challenge
@@ -18940,6 +19214,41 @@ components:
required:
- pagination
- results
+ PaginatedDuoDeviceList:
+ type: object
+ properties:
+ pagination:
+ type: object
+ properties:
+ next:
+ type: number
+ previous:
+ type: number
+ count:
+ type: number
+ current:
+ type: number
+ total_pages:
+ type: number
+ start_index:
+ type: number
+ end_index:
+ type: number
+ required:
+ - next
+ - previous
+ - count
+ - current
+ - total_pages
+ - start_index
+ - end_index
+ results:
+ type: array
+ items:
+ $ref: '#/components/schemas/DuoDevice'
+ required:
+ - pagination
+ - results
PaginatedEmailStageList:
type: object
properties:
@@ -21421,6 +21730,14 @@ components:
type: array
items:
$ref: '#/components/schemas/FlowRequest'
+ PatchedDuoDeviceRequest:
+ type: object
+ description: Serializer for Duo authenticator devices
+ properties:
+ name:
+ type: string
+ description: The human-readable name of this device.
+ maxLength: 64
PatchedEmailStageRequest:
type: object
description: EmailStage Serializer
diff --git a/web/src/flows/FlowExecutor.ts b/web/src/flows/FlowExecutor.ts
index b141e8a37..44bfc9429 100644
--- a/web/src/flows/FlowExecutor.ts
+++ b/web/src/flows/FlowExecutor.ts
@@ -100,7 +100,6 @@ export class FlowExecutor extends LitElement implements StageHost {
submit(payload: FlowChallengeResponseRequest): Promise