enterprise/providers/oauth2: dynamic client registration (#24225)

* I think I'm happy with this structure

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fixup

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add api

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing engine mode

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add ui

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* pull in the requests machinery

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fixup and gen

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* gen

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-wire things up

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-add more web stuff

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix unrelated test issue

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix mixin

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* gen

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* plumb in flows

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update ui

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* initial web

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix a couple things

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix api tests & schema

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* better pick request flow

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix perms

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* change method

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix perms

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix web lint

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* explicit perms

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix migrations

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* concrete m2m

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix perms

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add some default config

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix permission check -> requesting user checks against rule bindings' policies

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix unrelated

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix api client

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix styling

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix more

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add banner

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* attributes mixin prep for object attrs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix typo

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix unrelated test failure

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* ensure no self-approval

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* configurable expiry

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add meta info to requestable target

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add more events

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* only unique apps

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* initial app card impl

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add entitlement modal

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* respect filters

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-migrate

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* improve ux

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix child bindings?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* better ux for rule bindings

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix type check

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* initial dcr

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-migrate

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* handle error

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing through

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* oops

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Clean up checkbox item types.

* Fix grant type translation.

* fix conflicting urls

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix duplicate url encoding

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* always create app

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rename options to function as override

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* copy pb

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update ui

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix title

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* copy bindings from app if none set

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* show policy bindings

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix copy & tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix unknown case

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
This commit is contained in:
Jens L.
2026-07-30 13:02:37 +01:00
committed by GitHub
parent d357370837
commit c213711211
35 changed files with 2920 additions and 67 deletions

View File

@@ -40,6 +40,8 @@ SCOPE_OPENID_PROFILE = "profile"
SCOPE_OPENID_EMAIL = "email"
SCOPE_OFFLINE_ACCESS = "offline_access"
SCOPE_BOUND_KEY = "bound_key"
SCOPE_AUTHENTIK_API = "goauthentik.io/api"
SCOPE_AUTHENTIK_DCR = "goauthentik.io/oidc/dcr"
UI_LOCALES = "ui_locales"
@@ -50,8 +52,6 @@ PKCE_METHOD_S256 = "S256"
TOKEN_TYPE = "Bearer" # nosec
JWT_TYPE_DPOP_ID_TOKEN = "dpop+id_token"
SCOPE_AUTHENTIK_API = "goauthentik.io/api"
# URI schemes that are forbidden for redirect URIs
FORBIDDEN_URI_SCHEMES = {"javascript", "data", "vbscript"}

View File

@@ -0,0 +1,38 @@
"""OAuth2 Dynamic Client Registration API"""
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.utils import ModelSerializer
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.providers.oauth2.models import (
OAuth2DynamicClientRegistration,
)
class OAuth2DynamicClientRegistrationSerializer(EnterpriseRequiredMixin, ModelSerializer):
"""Serializer for OAuth2DynamicClientRegistration"""
class Meta:
model = OAuth2DynamicClientRegistration
fields = [
"pbm_uuid",
"provider",
"default_application_group",
"override_authorization_flow",
"override_invalidation_flow",
"override_property_mappings",
"access_token_validity",
"refresh_token_validity",
"allowed_grant_types",
"policy_engine_mode",
]
class OAuth2DynamicClientRegistrationViewSet(ModelViewSet):
"""OAuth2 Dynamic Client Registration configuration ViewSet"""
queryset = OAuth2DynamicClientRegistration.objects.all()
serializer_class = OAuth2DynamicClientRegistrationSerializer
filterset_fields = ["provider"]
search_fields = ["provider__name"]
ordering = ["provider__name"]

View File

@@ -0,0 +1,10 @@
from authentik.enterprise.apps import EnterpriseConfig
class AuthentikEnterpriseProviderOAuth2Config(EnterpriseConfig):
name = "authentik.enterprise.providers.oauth2"
label = "authentik_enterprise_providers_oauth2"
verbose_name = "authentik Enterprise.Providers.OAuth2"
default = True
mountpoint = "application/o/"

View File

@@ -0,0 +1,344 @@
"""Tests for OAuth2 Dynamic Client Registration (RFC 7591)"""
import json
from datetime import timedelta
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from authentik.blueprints.tests import apply_blueprint
from authentik.common.oauth.constants import SCOPE_AUTHENTIK_DCR
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_flow, create_test_user
from authentik.lib.generators import generate_id
from authentik.policies.dummy.models import DummyPolicy
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.models import (
AccessToken,
ClientType,
GrantType,
OAuth2DynamicClientRegistration,
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,
ScopeMapping,
)
class TestDynamicClientRegistration(TestCase):
"""RFC 7591 Dynamic Client Registration tests"""
@apply_blueprint("system/providers-oauth2.yaml")
def setUp(self):
self.flow = create_test_flow()
self.provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=self.flow,
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")],
)
self.app = Application.objects.create(
name=generate_id(),
slug=generate_id(),
provider=self.provider,
)
self.dcr = OAuth2DynamicClientRegistration.objects.create(
provider=self.provider,
override_authorization_flow=self.flow,
)
self.register_url = reverse(
"authentik_enterprise_providers_oauth2:dynamic-client-registration",
kwargs={"application_slug": self.app.slug},
)
def _access_token(self, scope: str = SCOPE_AUTHENTIK_DCR, **kwargs) -> AccessToken:
return AccessToken.objects.create(
user=kwargs.pop("user", None) or create_test_user(),
provider=kwargs.pop("provider", None) or self.provider,
token=generate_id(),
auth_time=timezone.now(),
_scope=scope,
_id_token=json.dumps({}),
**kwargs,
)
def _post(self, body: dict, token: str | None = None) -> object:
headers = {"content_type": "application/json"}
if token:
headers["HTTP_AUTHORIZATION"] = f"Bearer {token}"
return self.client.post(self.register_url, json.dumps(body), **headers)
def test_registration_success(self):
"""Basic registration creates a new OAuth2Provider and Application."""
token = self._access_token()
response = self._post(
{"redirect_uris": ["https://example.com/callback"], "client_name": "Test Client"},
token=token.token,
)
self.assertEqual(response.status_code, 201)
body = json.loads(response.content)
self.assertIn("client_id", body)
self.assertIn("client_secret", body)
self.assertEqual(body["redirect_uris"], ["https://example.com/callback"])
self.assertEqual(body["client_name"], "Test Client")
# Provider created
self.assertTrue(OAuth2Provider.objects.filter(client_id=body["client_id"]).exists())
# Application created
provider = OAuth2Provider.objects.get(client_id=body["client_id"])
self.assertIsNotNone(provider.application)
def test_requires_redirect_uris(self):
"""redirect_uris is required."""
token = self._access_token()
response = self._post({"client_name": "No URIs"}, token=token.token)
self.assertEqual(response.status_code, 400)
body = json.loads(response.content)
self.assertEqual(body["error"], "invalid_redirect_uri")
def test_invalid_json(self):
"""Non-JSON body is rejected."""
token = self._access_token()
response = self.client.post(
self.register_url,
"not-json",
content_type="application/json",
HTTP_AUTHORIZATION=f"Bearer {token.token}",
)
self.assertEqual(response.status_code, 400)
body = json.loads(response.content)
self.assertEqual(body["error"], "invalid_client_metadata")
def test_requires_access_token(self):
"""Requests without a bearer token are rejected."""
response = self._post({"redirect_uris": ["https://example.com/cb"]})
self.assertEqual(response.status_code, 401)
body = json.loads(response.content)
self.assertEqual(body["error"], "invalid_token")
def test_access_token_missing_scope_rejected(self):
"""An access token without the DCR scope is rejected."""
token = self._access_token(scope="")
response = self._post(
{"redirect_uris": ["https://example.com/cb"]},
token=token.token,
)
self.assertEqual(response.status_code, 401)
body = json.loads(response.content)
self.assertEqual(body["error"], "invalid_token")
def test_valid_access_token(self):
"""A valid access token with the DCR scope grants registration."""
token = self._access_token()
response = self._post(
{"redirect_uris": ["https://example.com/cb"]},
token=token.token,
)
self.assertEqual(response.status_code, 201)
def test_expired_access_token_rejected(self):
"""An expired access token cannot be used to register clients."""
token = self._access_token(expires=timezone.now() - timedelta(hours=1))
response = self._post(
{"redirect_uris": ["https://example.com/cb"]},
token=token.token,
)
self.assertEqual(response.status_code, 401)
def test_grant_type_restriction(self):
"""Grant types not in allowed_grant_types are filtered out."""
self.dcr.allowed_grant_types = [GrantType.AUTHORIZATION_CODE]
self.dcr.save()
token = self._access_token()
response = self._post(
{
"redirect_uris": ["https://example.com/cb"],
"grant_types": [GrantType.AUTHORIZATION_CODE, GrantType.CLIENT_CREDENTIALS],
},
token=token.token,
)
self.assertEqual(response.status_code, 201)
body = json.loads(response.content)
self.assertNotIn(GrantType.CLIENT_CREDENTIALS, body["grant_types"])
self.assertIn(GrantType.AUTHORIZATION_CODE, body["grant_types"])
def test_public_client_no_secret(self):
"""Public client (auth_method=none) does not receive a client_secret."""
token = self._access_token()
response = self._post(
{
"redirect_uris": ["https://example.com/cb"],
"token_endpoint_auth_method": "none",
},
token=token.token,
)
self.assertEqual(response.status_code, 201)
body = json.loads(response.content)
self.assertNotIn("client_secret", body)
provider = OAuth2Provider.objects.get(client_id=body["client_id"])
self.assertEqual(provider.client_type, ClientType.PUBLIC)
def test_policy_access_denied(self):
"""A failing policy binding on the application rejects registration with 403."""
policy = DummyPolicy.objects.create(name=generate_id(), result=False)
PolicyBinding.objects.create(target=self.app, policy=policy, order=0)
token = self._access_token()
response = self._post(
{"redirect_uris": ["https://example.com/cb"]},
token=token.token,
)
self.assertEqual(response.status_code, 403)
body = json.loads(response.content)
self.assertEqual(body["error"], "access_denied")
def test_override_authorization_flow_applied(self):
"""override_authorization_flow is applied to the registered provider."""
override_flow = create_test_flow()
self.dcr.override_authorization_flow = override_flow
self.dcr.save()
token = self._access_token()
response = self._post(
{"redirect_uris": ["https://example.com/cb"]},
token=token.token,
)
self.assertEqual(response.status_code, 201)
body = json.loads(response.content)
provider = OAuth2Provider.objects.get(client_id=body["client_id"])
self.assertEqual(provider.authorization_flow, override_flow)
def test_default_application_group_applied(self):
"""default_application_group is applied to the created application."""
group = generate_id()
self.dcr.default_application_group = group
self.dcr.save()
token = self._access_token()
response = self._post(
{"redirect_uris": ["https://example.com/cb"]},
token=token.token,
)
self.assertEqual(response.status_code, 201)
body = json.loads(response.content)
provider = OAuth2Provider.objects.get(client_id=body["client_id"])
self.assertEqual(provider.application.group, group)
def test_all_grant_types_allowed_when_unrestricted(self):
"""With an empty allowed_grant_types, all requested grant types are kept."""
token = self._access_token()
response = self._post(
{
"redirect_uris": ["https://example.com/cb"],
"grant_types": [GrantType.AUTHORIZATION_CODE, GrantType.CLIENT_CREDENTIALS],
},
token=token.token,
)
self.assertEqual(response.status_code, 201)
body = json.loads(response.content)
self.assertIn(GrantType.AUTHORIZATION_CODE, body["grant_types"])
self.assertIn(GrantType.CLIENT_CREDENTIALS, body["grant_types"])
def test_override_property_mappings_applied(self):
"""override_property_mappings are applied to the registered provider."""
mapping = ScopeMapping.objects.create(
name=generate_id(), scope_name=generate_id(), expression="return {}"
)
self.dcr.override_property_mappings.set([mapping])
token = self._access_token()
response = self._post(
{"redirect_uris": ["https://example.com/cb"]},
token=token.token,
)
self.assertEqual(response.status_code, 201)
body = json.loads(response.content)
provider = OAuth2Provider.objects.get(client_id=body["client_id"])
self.assertTrue(provider.property_mappings.filter(pk=mapping.pk).exists())
def test_policy_bindings_copied_from_dcr(self):
"""Policy bindings on the DCR config are copied onto the new application."""
policy = DummyPolicy.objects.create(name=generate_id(), result=True)
source_binding = PolicyBinding.objects.create(target=self.dcr, policy=policy, order=0)
token = self._access_token()
response = self._post(
{"redirect_uris": ["https://example.com/cb"]},
token=token.token,
)
self.assertEqual(response.status_code, 201)
body = json.loads(response.content)
provider = OAuth2Provider.objects.get(client_id=body["client_id"])
copied = PolicyBinding.objects.filter(target=provider.application, policy=policy)
self.assertEqual(copied.count(), 1)
# The copy has its own primary key, not the source binding's.
self.assertNotEqual(copied.first().pk, source_binding.pk)
def test_policy_bindings_copied_from_application(self):
"""When the DCR config has no bindings, the base application's bindings are copied."""
policy = DummyPolicy.objects.create(name=generate_id(), result=True)
PolicyBinding.objects.create(target=self.app, policy=policy, order=0)
token = self._access_token()
response = self._post(
{"redirect_uris": ["https://example.com/cb"]},
token=token.token,
)
self.assertEqual(response.status_code, 201)
body = json.loads(response.content)
provider = OAuth2Provider.objects.get(client_id=body["client_id"])
self.assertTrue(
PolicyBinding.objects.filter(target=provider.application, policy=policy).exists()
)
def test_registration_endpoint_in_openid_config(self):
"""registration_endpoint is advertised in .well-known/openid-configuration."""
response = self.client.get(
reverse(
"authentik_providers_oauth2:provider-info",
kwargs={"application_slug": self.app.slug},
)
)
self.assertEqual(response.status_code, 200)
config = json.loads(response.content)
self.assertIn("registration_endpoint", config)
self.assertIn("/register/", config["registration_endpoint"])
def test_no_registration_endpoint_when_dcr_disabled(self):
"""registration_endpoint is absent when DCR is not configured."""
# Create a separate provider/app without DCR
provider2 = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=self.flow,
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")],
)
app2 = Application.objects.create(
name=generate_id(),
slug=generate_id(),
provider=provider2,
)
response = self.client.get(
reverse(
"authentik_providers_oauth2:provider-info",
kwargs={"application_slug": app2.slug},
)
)
self.assertEqual(response.status_code, 200)
config = json.loads(response.content)
self.assertNotIn("registration_endpoint", config)
def test_register_endpoint_404_without_dcr(self):
"""Registration endpoint returns 404 when DCR is not configured on that provider."""
provider2 = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=self.flow,
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")],
)
app2_slug = generate_id().lower()
app2 = Application.objects.create(
name=generate_id(),
slug=app2_slug,
provider=provider2,
)
response = self.client.post(
reverse(
"authentik_enterprise_providers_oauth2:dynamic-client-registration",
kwargs={"application_slug": app2.slug},
),
json.dumps({"redirect_uris": ["https://x.com/cb"]}),
content_type="application/json",
)
self.assertEqual(response.status_code, 404)

View File

@@ -0,0 +1,18 @@
"""OAuth2 Dynamic Client Registration URLs"""
from django.urls import path
from authentik.enterprise.providers.oauth2.api import OAuth2DynamicClientRegistrationViewSet
from authentik.enterprise.providers.oauth2.views.dcr import DynamicClientRegistrationView
urlpatterns = [
path(
"<slug:application_slug>/register/",
DynamicClientRegistrationView.as_view(),
name="dynamic-client-registration",
),
]
api_urlpatterns = [
("providers/oauth2-dcr", OAuth2DynamicClientRegistrationViewSet),
]

View File

@@ -0,0 +1,236 @@
"""authentik OAuth2 Dynamic Client Registration (RFC 7591)"""
import json
import time
from typing import Any
from django.db import transaction
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from django.utils.text import slugify
from django.views import View
from structlog.stdlib import get_logger
from authentik.api.authentication import validate_auth
from authentik.common.oauth.constants import SCOPE_AUTHENTIK_DCR
from authentik.core.apps import AppAccessWithoutBindings
from authentik.core.models import Application, User
from authentik.lib.generators import generate_id
from authentik.policies.engine import PolicyEngine
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.models import (
AccessToken,
ClientType,
GrantType,
OAuth2DynamicClientRegistration,
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,
generate_client_secret,
)
LOGGER = get_logger()
AUTH_METHOD_TO_CLIENT_TYPE = {
"client_secret_basic": ClientType.CONFIDENTIAL,
"client_secret_post": ClientType.CONFIDENTIAL,
"none": ClientType.PUBLIC,
}
def _dcr_error(error: str, description: str, status: int = 400) -> JsonResponse:
return JsonResponse({"error": error, "error_description": description}, status=status)
class DynamicClientRegistrationView(View):
"""RFC 7591 Dynamic Client Registration endpoint.
POST /application/o/<application_slug>/register/
"""
dcr: OAuth2DynamicClientRegistration
application: Application
provider: OAuth2Provider
def _authenticate_access_token(self, request: HttpRequest) -> AccessToken | None:
"""Authenticate the request via a Bearer `AccessToken` carrying the
`goauthentik.io/oidc/dcr` scope, mirroring how `SCOPE_AUTHENTIK_API`
authenticates access to authentik's own API."""
raw_token = validate_auth(request.headers.get("Authorization", "").encode())
if not raw_token:
return None
access_token = AccessToken.objects.filter(
token=raw_token, _scope__icontains=SCOPE_AUTHENTIK_DCR
).first()
if not access_token or SCOPE_AUTHENTIK_DCR not in access_token.scope:
return None
return access_token
def _check_policy_access(self, request: HttpRequest, user: User) -> bool:
engine = PolicyEngine(self.application, user, request)
engine.empty_result = AppAccessWithoutBindings.get()
engine.use_cache = False
engine.mode = self.application.policy_engine_mode
engine.build()
return engine.result.passing
def _unique_app_slug(self, base: str) -> str:
slug = slugify(base)[:200] or "client"
candidate = slug
counter = 1
while Application.objects.filter(slug=candidate).exists():
candidate = f"{slug}-{counter}"
counter += 1
return candidate
@transaction.atomic
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:
# --- Access control -----------------------------------------------
access_token = self._authenticate_access_token(request)
if access_token is None:
return _dcr_error(
"invalid_token",
"A valid access token with the "
f"'{SCOPE_AUTHENTIK_DCR}' scope is required to register clients.",
status=401,
)
if not self._check_policy_access(request, access_token.user):
LOGGER.info("DCR registration rejected by policy", slug=application_slug)
return _dcr_error(
"access_denied", "Policy check failed for client registration.", status=403
)
# --- Parse request body ------------------------------------------
try:
metadata: dict[str, Any] = json.loads(request.body)
except json.JSONDecodeError, UnicodeDecodeError:
return _dcr_error("invalid_client_metadata", "Request body must be valid JSON.")
if not isinstance(metadata, dict):
return _dcr_error("invalid_client_metadata", "Request body must be a JSON object.")
raw_uris = metadata.get("redirect_uris")
if not raw_uris or not isinstance(raw_uris, list):
return _dcr_error(
"invalid_redirect_uri", "redirect_uris is required and must be a non-empty list."
)
if not all(isinstance(u, str) for u in raw_uris):
return _dcr_error("invalid_redirect_uri", "All redirect_uris must be strings.")
redirect_uris = [
RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, url=uri) for uri in raw_uris
]
# --- Resolve settings --------------------------------------------
auth_method = metadata.get("token_endpoint_auth_method", "client_secret_basic")
client_type = AUTH_METHOD_TO_CLIENT_TYPE.get(auth_method, ClientType.CONFIDENTIAL)
requested_grants = metadata.get("grant_types", [GrantType.AUTHORIZATION_CODE])
if not isinstance(requested_grants, list):
return _dcr_error("invalid_client_metadata", "grant_types must be an array.")
if self.dcr.allowed_grant_types:
requested_grants = [g for g in requested_grants if g in self.dcr.allowed_grant_types]
grant_types = [g for g in requested_grants if g in GrantType.values] or [
GrantType.AUTHORIZATION_CODE
]
client_name = metadata.get("client_name", "")
# --- Create OAuth2Provider ---------------------------------------
provider = OAuth2Provider(
name=client_name or generate_id(),
client_id=generate_id(),
client_secret=generate_client_secret(),
client_type=client_type,
grant_types=grant_types,
authorization_flow=self.dcr.override_authorization_flow
or self.provider.authorization_flow,
invalidation_flow=self.dcr.override_invalidation_flow
or self.provider.invalidation_flow,
access_token_validity=self.dcr.access_token_validity
or self.provider.access_token_validity,
refresh_token_validity=self.dcr.refresh_token_validity
or self.provider.refresh_token_validity,
)
provider.redirect_uris = redirect_uris
provider.save()
if self.dcr.override_property_mappings.exists():
provider.property_mappings.set(self.dcr.override_property_mappings.all())
else:
provider.property_mappings.set(self.provider.property_mappings.all())
app_slug = self._unique_app_slug(client_name or provider.client_id)
app = Application.objects.create(
name=client_name or provider.client_id,
slug=app_slug,
provider=provider,
group=self.dcr.default_application_group,
policy_engine_mode=self.dcr.policy_engine_mode,
)
bindings = PolicyBinding.objects.filter(target=self.dcr)
if not bindings.exists():
bindings = PolicyBinding.objects.filter(target=self.application)
new_bindings = []
for binding in bindings:
new_bindings.append(
PolicyBinding(
# Copy over all fields except the target and primary key
**{
k.name: getattr(binding, k.name)
for k in PolicyBinding._meta.concrete_fields
if k.name != "target" and not k.primary_key
},
target=app,
)
)
PolicyBinding.objects.bulk_create(new_bindings)
LOGGER.info(
"DCR: registered new client",
client_id=provider.client_id,
client_name=provider.name,
registered_by=access_token.user.username,
)
# --- RFC 7591 §3.2.1 response -------------------------------------
response_data: dict[str, Any] = {
"client_id": provider.client_id,
"client_id_issued_at": int(time.time()),
"redirect_uris": raw_uris,
"grant_types": grant_types,
"token_endpoint_auth_method": (
auth_method if auth_method in AUTH_METHOD_TO_CLIENT_TYPE else "client_secret_basic"
),
}
if client_type == ClientType.CONFIDENTIAL:
response_data["client_secret"] = provider.client_secret
response_data["client_secret_expires_at"] = 0
if client_name:
response_data["client_name"] = client_name
return JsonResponse(response_data, status=201)
def dispatch(
self, request: HttpRequest, application_slug: str, *args: Any, **kwargs: Any
) -> HttpResponse:
self.application = get_object_or_404(Application, slug=application_slug)
self.provider = get_object_or_404(OAuth2Provider, pk=self.application.provider_id)
try:
self.dcr = OAuth2DynamicClientRegistration.objects.get(provider=self.provider)
except OAuth2DynamicClientRegistration.DoesNotExist:
return JsonResponse(
{
"error": "not_found",
"error_description": (
"Dynamic client registration is not enabled for this provider."
),
},
status=404,
)
return super().dispatch(request, application_slug, *args, **kwargs)

View File

@@ -9,6 +9,7 @@ TENANT_APPS = [
"authentik.enterprise.policies.unique_password",
"authentik.enterprise.providers.google_workspace",
"authentik.enterprise.providers.microsoft_entra",
"authentik.enterprise.providers.oauth2",
"authentik.enterprise.providers.radius",
"authentik.enterprise.providers.scim",
"authentik.enterprise.providers.ssf",

View File

@@ -0,0 +1,169 @@
# Generated by Django 5.2.15 on 2026-07-29 17:10
import authentik.lib.models
import authentik.lib.utils.time
import django.contrib.postgres.fields
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0031_alter_flow_layout"),
("authentik_policies", "0012_alter_policybinding_managers_policybinding_expires_and_more"),
("authentik_providers_oauth2", "0035_oauth2providerjwtfederationprovider_and_more"),
]
operations = [
migrations.CreateModel(
name="DynamicClientRegistrationPropertyMapping",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"property_mapping",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_providers_oauth2.scopemapping",
),
),
],
options={
"verbose_name": "Dynamic Client Registration Property Mapping",
"verbose_name_plural": "Dynamic Client Registration Property Mappings",
},
bases=(models.Model, authentik.lib.models.InternallyManagedMixin),
),
migrations.CreateModel(
name="OAuth2DynamicClientRegistration",
fields=[
(
"policybindingmodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_policies.policybindingmodel",
),
),
(
"default_application_group",
models.TextField(
blank=True,
default="",
help_text="Group to assign to automatically created applications.",
verbose_name="Default application group",
),
),
(
"access_token_validity",
models.TextField(
default="hours=1",
help_text="Maximum access token validity for registered clients (Format: hours=1;minutes=2;seconds=3).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
verbose_name="Access token validity",
),
),
(
"refresh_token_validity",
models.TextField(
default="days=30",
help_text="Maximum refresh token validity for registered clients (Format: hours=1;minutes=2;seconds=3).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
verbose_name="Refresh token validity",
),
),
(
"allowed_grant_types",
django.contrib.postgres.fields.ArrayField(
base_field=models.TextField(
choices=[
("authorization_code", "Authorization Code"),
("implicit", "Implicit"),
("hybrid", "Hybrid"),
("refresh_token", "Refresh Token"),
("client_credentials", "Client Credentials"),
("password", "Password"),
("urn:ietf:params:oauth:grant-type:device_code", "Device Code"),
(
"urn:ietf:params:oauth:grant-type:token-exchange",
"Token Exchange",
),
]
),
blank=True,
default=list,
help_text="If empty, all grant types are allowed.",
size=None,
verbose_name="Allowed grant types",
),
),
(
"override_authorization_flow",
models.ForeignKey(
blank=True,
help_text="Authorization flow applied to dynamically registered clients.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="dcr_override_authorization_flow",
to="authentik_flows.flow",
verbose_name="Override authorization flow",
),
),
(
"override_invalidation_flow",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="dcr_override_invalidation_flow",
to="authentik_flows.flow",
verbose_name="Override invalidation flow",
),
),
(
"override_property_mappings",
models.ManyToManyField(
blank=True,
help_text="Scope mappings applied to dynamically registered clients.",
through="authentik_providers_oauth2.DynamicClientRegistrationPropertyMapping",
to="authentik_providers_oauth2.scopemapping",
verbose_name="Override property mappings",
),
),
(
"provider",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="dcr_configuration",
to="authentik_providers_oauth2.oauth2provider",
verbose_name="Provider",
),
),
],
options={
"verbose_name": "OAuth2 Dynamic Client Registration",
"verbose_name_plural": "OAuth2 Dynamic Client Registrations",
},
bases=("authentik_policies.policybindingmodel", models.Model),
),
migrations.AddField(
model_name="dynamicclientregistrationpropertymapping",
name="dynamic_client_registration",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_providers_oauth2.oauth2dynamicclientregistration",
),
),
migrations.AlterUniqueTogether(
name="dynamicclientregistrationpropertymapping",
unique_together={("property_mapping", "dynamic_client_registration")},
),
]

View File

@@ -63,6 +63,7 @@ from authentik.lib.models import (
SimpleThroughModel,
)
from authentik.lib.utils.time import timedelta_string_validator
from authentik.policies.models import PolicyBindingModel
from authentik.sources.oauth.models import OAuthSource
if TYPE_CHECKING:
@@ -739,3 +740,107 @@ class DeviceToken(InternallyManagedMixin, ExpiringModel):
def __str__(self):
return f"Device Token for {self.provider_id}"
class OAuth2DynamicClientRegistration(SerializerModel, PolicyBindingModel):
"""Configuration for Dynamic Client Registration (RFC 7591) on an OAuth2Provider."""
provider = models.OneToOneField(
OAuth2Provider,
on_delete=models.CASCADE,
related_name="dcr_configuration",
verbose_name=_("Provider"),
)
default_application_group = models.TextField(
blank=True,
default="",
verbose_name=_("Default application group"),
help_text=_("Group to assign to automatically created applications."),
)
override_authorization_flow = models.ForeignKey(
"authentik_flows.Flow",
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name=_("Override authorization flow"),
help_text=_("Authorization flow applied to dynamically registered clients."),
related_name="dcr_override_authorization_flow",
)
override_invalidation_flow = models.ForeignKey(
"authentik_flows.Flow",
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name=_("Override invalidation flow"),
related_name="dcr_override_invalidation_flow",
)
override_property_mappings = models.ManyToManyField(
ScopeMapping,
blank=True,
verbose_name=_("Override property mappings"),
help_text=_("Scope mappings applied to dynamically registered clients."),
through="DynamicClientRegistrationPropertyMapping",
)
access_token_validity = models.TextField(
default="hours=1",
validators=[timedelta_string_validator],
verbose_name=_("Access token validity"),
help_text=_(
"Maximum access token validity for registered clients "
"(Format: hours=1;minutes=2;seconds=3)."
),
)
refresh_token_validity = models.TextField(
default="days=30",
validators=[timedelta_string_validator],
verbose_name=_("Refresh token validity"),
help_text=_(
"Maximum refresh token validity for registered clients "
"(Format: hours=1;minutes=2;seconds=3)."
),
)
allowed_grant_types = ArrayField(
models.TextField(choices=GrantType.choices),
default=list,
blank=True,
verbose_name=_("Allowed grant types"),
help_text=_("If empty, all grant types are allowed."),
)
@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.oauth2.api import (
OAuth2DynamicClientRegistrationSerializer,
)
return OAuth2DynamicClientRegistrationSerializer
def __str__(self):
return f"DCR Configuration for {self.provider_id}"
class Meta:
verbose_name = _("OAuth2 Dynamic Client Registration")
verbose_name_plural = _("OAuth2 Dynamic Client Registrations")
class DynamicClientRegistrationPropertyMapping(SimpleThroughModel):
property_mapping = models.ForeignKey(ScopeMapping, on_delete=models.CASCADE)
dynamic_client_registration = models.ForeignKey(
OAuth2DynamicClientRegistration, on_delete=models.CASCADE
)
class Meta:
unique_together = (("property_mapping", "dynamic_client_registration"),)
verbose_name = _("Dynamic Client Registration Property Mapping")
verbose_name_plural = _("Dynamic Client Registration Property Mappings")
def __str__(self):
return (
"DynamicClientRegistrationPropertyMapping for DCR "
f"{self.dynamic_client_registration_id} and PropertyMapping "
f"{self.property_mapping_id}."
)

View File

@@ -2,6 +2,7 @@
from typing import Any
from django.apps import apps
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, reverse
from django.views import View
@@ -24,6 +25,7 @@ from authentik.core.expression.exceptions import PropertyMappingExpressionExcept
from authentik.core.models import Application
from authentik.providers.oauth2.dpop import DPOP_SUPPORTED_ALGS
from authentik.providers.oauth2.models import (
OAuth2DynamicClientRegistration,
OAuth2Provider,
ResponseMode,
ResponseTypes,
@@ -123,6 +125,17 @@ class ProviderInfoView(View):
if provider.encryption_key:
config["id_token_encryption_alg_values_supported"] = ["RSA-OAEP-256"]
config["id_token_encryption_enc_values_supported"] = ["A256CBC-HS512"]
try:
_ = provider.dcr_configuration
if apps.get_app_config("authentik_enterprise").enabled():
config["registration_endpoint"] = self.request.build_absolute_uri(
reverse(
"authentik_enterprise_providers_oauth2:dynamic-client-registration",
kwargs={"application_slug": provider.application.slug},
)
)
except OAuth2DynamicClientRegistration.DoesNotExist:
pass
return config
def get_claims(self, provider: OAuth2Provider) -> list[str]:

View File

@@ -212,6 +212,8 @@ SPECTACULAR_SETTINGS = {
"TaskStatusEnum": "django_dramatiq_postgres.models.TaskState",
"TransportModeEnum": "authentik.events.models.TransportMode",
"RequestStatus": "authentik.enterprise.requests.models.RequestStatus",
"ClientTypeEnum": "authentik.providers.oauth2.models.ClientType",
"GrantTypeEnum": "authentik.providers.oauth2.models.GrantType",
"UserTypeEnum": "authentik.core.models.UserTypes",
"UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification",
"WebAuthnHintEnum": "authentik.stages.authenticator_webauthn.models.WebAuthnHint",

View File

@@ -2376,6 +2376,46 @@
}
}
},
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_providers_oauth2.oauth2dynamicclientregistration"
},
"id": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"absent",
"created",
"must_created",
"present"
],
"default": "present"
},
"conditions": {
"type": "array",
"items": {
"type": "boolean"
}
},
"permissions": {
"$ref": "#/$defs/model_authentik_providers_oauth2.oauth2dynamicclientregistration_permissions"
},
"attrs": {
"$ref": "#/$defs/model_authentik_providers_oauth2.oauth2dynamicclientregistration"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_providers_oauth2.oauth2dynamicclientregistration"
}
}
},
{
"type": "object",
"required": [
@@ -6308,6 +6348,8 @@
"authentik_providers_oauth2.add_accesstoken",
"authentik_providers_oauth2.add_authorizationcode",
"authentik_providers_oauth2.add_devicetoken",
"authentik_providers_oauth2.add_dynamicclientregistrationpropertymapping",
"authentik_providers_oauth2.add_oauth2dynamicclientregistration",
"authentik_providers_oauth2.add_oauth2provider",
"authentik_providers_oauth2.add_oauth2providerjwtfederationprovider",
"authentik_providers_oauth2.add_oauth2providerjwtfederationsource",
@@ -6316,6 +6358,8 @@
"authentik_providers_oauth2.change_accesstoken",
"authentik_providers_oauth2.change_authorizationcode",
"authentik_providers_oauth2.change_devicetoken",
"authentik_providers_oauth2.change_dynamicclientregistrationpropertymapping",
"authentik_providers_oauth2.change_oauth2dynamicclientregistration",
"authentik_providers_oauth2.change_oauth2provider",
"authentik_providers_oauth2.change_oauth2providerjwtfederationprovider",
"authentik_providers_oauth2.change_oauth2providerjwtfederationsource",
@@ -6324,6 +6368,8 @@
"authentik_providers_oauth2.delete_accesstoken",
"authentik_providers_oauth2.delete_authorizationcode",
"authentik_providers_oauth2.delete_devicetoken",
"authentik_providers_oauth2.delete_dynamicclientregistrationpropertymapping",
"authentik_providers_oauth2.delete_oauth2dynamicclientregistration",
"authentik_providers_oauth2.delete_oauth2provider",
"authentik_providers_oauth2.delete_oauth2providerjwtfederationprovider",
"authentik_providers_oauth2.delete_oauth2providerjwtfederationsource",
@@ -6332,6 +6378,8 @@
"authentik_providers_oauth2.view_accesstoken",
"authentik_providers_oauth2.view_authorizationcode",
"authentik_providers_oauth2.view_devicetoken",
"authentik_providers_oauth2.view_dynamicclientregistrationpropertymapping",
"authentik_providers_oauth2.view_oauth2dynamicclientregistration",
"authentik_providers_oauth2.view_oauth2provider",
"authentik_providers_oauth2.view_oauth2providerjwtfederationprovider",
"authentik_providers_oauth2.view_oauth2providerjwtfederationsource",
@@ -9882,6 +9930,7 @@
"authentik.enterprise.policies.unique_password",
"authentik.enterprise.providers.google_workspace",
"authentik.enterprise.providers.microsoft_entra",
"authentik.enterprise.providers.oauth2",
"authentik.enterprise.providers.radius",
"authentik.enterprise.providers.scim",
"authentik.enterprise.providers.ssf",
@@ -9939,6 +9988,7 @@
"authentik_providers_ldap.ldapprovider",
"authentik_providers_oauth2.scopemapping",
"authentik_providers_oauth2.oauth2provider",
"authentik_providers_oauth2.oauth2dynamicclientregistration",
"authentik_providers_proxy.proxyprovider",
"authentik_providers_rac.racprovider",
"authentik_providers_rac.endpoint",
@@ -10876,6 +10926,107 @@
}
}
},
"model_authentik_providers_oauth2.oauth2dynamicclientregistration": {
"type": "object",
"properties": {
"provider": {
"type": "integer",
"title": "Provider"
},
"default_application_group": {
"type": "string",
"title": "Default application group",
"description": "Group to assign to automatically created applications."
},
"override_authorization_flow": {
"type": "string",
"format": "uuid",
"title": "Override authorization flow",
"description": "Authorization flow applied to dynamically registered clients."
},
"override_invalidation_flow": {
"type": "string",
"format": "uuid",
"title": "Override invalidation flow"
},
"override_property_mappings": {
"type": "array",
"items": {
"type": "string",
"format": "uuid",
"description": "Scope mappings applied to dynamically registered clients."
},
"title": "Override property mappings",
"description": "Scope mappings applied to dynamically registered clients."
},
"access_token_validity": {
"type": "string",
"minLength": 1,
"title": "Access token validity",
"description": "Maximum access token validity for registered clients (Format: hours=1;minutes=2;seconds=3)."
},
"refresh_token_validity": {
"type": "string",
"minLength": 1,
"title": "Refresh token validity",
"description": "Maximum refresh token validity for registered clients (Format: hours=1;minutes=2;seconds=3)."
},
"allowed_grant_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"authorization_code",
"implicit",
"hybrid",
"refresh_token",
"client_credentials",
"password",
"urn:ietf:params:oauth:grant-type:device_code",
"urn:ietf:params:oauth:grant-type:token-exchange"
],
"title": "Allowed grant types"
},
"title": "Allowed grant types",
"description": "If empty, all grant types are allowed."
},
"policy_engine_mode": {
"type": "string",
"enum": [
"all",
"any"
],
"title": "Policy engine mode"
}
},
"required": []
},
"model_authentik_providers_oauth2.oauth2dynamicclientregistration_permissions": {
"type": "array",
"items": {
"type": "object",
"required": [
"permission"
],
"properties": {
"permission": {
"type": "string",
"enum": [
"add_oauth2dynamicclientregistration",
"change_oauth2dynamicclientregistration",
"delete_oauth2dynamicclientregistration",
"view_oauth2dynamicclientregistration"
]
},
"user": {
"type": "integer"
},
"role": {
"type": "string"
}
}
}
},
"model_authentik_providers_oauth2.oauth2provider": {
"type": "object",
"properties": {
@@ -12577,6 +12728,8 @@
"authentik_providers_oauth2.add_accesstoken",
"authentik_providers_oauth2.add_authorizationcode",
"authentik_providers_oauth2.add_devicetoken",
"authentik_providers_oauth2.add_dynamicclientregistrationpropertymapping",
"authentik_providers_oauth2.add_oauth2dynamicclientregistration",
"authentik_providers_oauth2.add_oauth2provider",
"authentik_providers_oauth2.add_oauth2providerjwtfederationprovider",
"authentik_providers_oauth2.add_oauth2providerjwtfederationsource",
@@ -12585,6 +12738,8 @@
"authentik_providers_oauth2.change_accesstoken",
"authentik_providers_oauth2.change_authorizationcode",
"authentik_providers_oauth2.change_devicetoken",
"authentik_providers_oauth2.change_dynamicclientregistrationpropertymapping",
"authentik_providers_oauth2.change_oauth2dynamicclientregistration",
"authentik_providers_oauth2.change_oauth2provider",
"authentik_providers_oauth2.change_oauth2providerjwtfederationprovider",
"authentik_providers_oauth2.change_oauth2providerjwtfederationsource",
@@ -12593,6 +12748,8 @@
"authentik_providers_oauth2.delete_accesstoken",
"authentik_providers_oauth2.delete_authorizationcode",
"authentik_providers_oauth2.delete_devicetoken",
"authentik_providers_oauth2.delete_dynamicclientregistrationpropertymapping",
"authentik_providers_oauth2.delete_oauth2dynamicclientregistration",
"authentik_providers_oauth2.delete_oauth2provider",
"authentik_providers_oauth2.delete_oauth2providerjwtfederationprovider",
"authentik_providers_oauth2.delete_oauth2providerjwtfederationsource",
@@ -12601,6 +12758,8 @@
"authentik_providers_oauth2.view_accesstoken",
"authentik_providers_oauth2.view_authorizationcode",
"authentik_providers_oauth2.view_devicetoken",
"authentik_providers_oauth2.view_dynamicclientregistrationpropertymapping",
"authentik_providers_oauth2.view_oauth2dynamicclientregistration",
"authentik_providers_oauth2.view_oauth2provider",
"authentik_providers_oauth2.view_oauth2providerjwtfederationprovider",
"authentik_providers_oauth2.view_oauth2providerjwtfederationsource",

View File

@@ -78,6 +78,17 @@ entries:
# This scope grants the application the ability to access the authentik API
# on behalf of the authorizing user
return {}
- identifiers:
managed: goauthentik.io/providers/oauth2/scope-dcr
model: authentik_providers_oauth2.scopemapping
attrs:
name: "authentik default OAuth Mapping: authentik Dynamic Client Registration"
scope_name: goauthentik.io/oidc/dcr
description: "authentik Dynamic Client Registration (RFC 7591)"
expression: |
# This scope grants the application the ability to register new
# OAuth2 clients via Dynamic Client Registration (RFC 7591)
return {}
- identifiers:
managed: goauthentik.io/providers/oauth2/scope-bound_key
model: authentik_providers_oauth2.scopemapping

View File

@@ -66,6 +66,14 @@ import {
type MicrosoftEntraProviderUserRequest,
MicrosoftEntraProviderUserRequestToJSON,
} from "../models/MicrosoftEntraProviderUserRequest";
import {
type OAuth2DynamicClientRegistration,
OAuth2DynamicClientRegistrationFromJSON,
} from "../models/OAuth2DynamicClientRegistration";
import {
type OAuth2DynamicClientRegistrationRequest,
OAuth2DynamicClientRegistrationRequestToJSON,
} from "../models/OAuth2DynamicClientRegistrationRequest";
import { type OAuth2Provider, OAuth2ProviderFromJSON } from "../models/OAuth2Provider";
import {
type OAuth2ProviderRequest,
@@ -103,6 +111,10 @@ import {
type PaginatedMicrosoftEntraProviderUserList,
PaginatedMicrosoftEntraProviderUserListFromJSON,
} from "../models/PaginatedMicrosoftEntraProviderUserList";
import {
type PaginatedOAuth2DynamicClientRegistrationList,
PaginatedOAuth2DynamicClientRegistrationListFromJSON,
} from "../models/PaginatedOAuth2DynamicClientRegistrationList";
import {
type PaginatedOAuth2ProviderList,
PaginatedOAuth2ProviderListFromJSON,
@@ -159,6 +171,10 @@ import {
type PatchedMicrosoftEntraProviderRequest,
PatchedMicrosoftEntraProviderRequestToJSON,
} from "../models/PatchedMicrosoftEntraProviderRequest";
import {
type PatchedOAuth2DynamicClientRegistrationRequest,
PatchedOAuth2DynamicClientRegistrationRequestToJSON,
} from "../models/PatchedOAuth2DynamicClientRegistrationRequest";
import {
type PatchedOAuth2ProviderRequest,
PatchedOAuth2ProviderRequestToJSON,
@@ -508,6 +524,36 @@ export interface ProvidersOauth2CreateRequest {
oAuth2ProviderRequest: OAuth2ProviderRequest;
}
export interface ProvidersOauth2DcrCreateRequest {
oAuth2DynamicClientRegistrationRequest: OAuth2DynamicClientRegistrationRequest;
}
export interface ProvidersOauth2DcrDestroyRequest {
pbmUuid: string;
}
export interface ProvidersOauth2DcrListRequest {
ordering?: string;
page?: number;
pageSize?: number;
provider?: number;
search?: string;
}
export interface ProvidersOauth2DcrPartialUpdateRequest {
pbmUuid: string;
patchedOAuth2DynamicClientRegistrationRequest?: PatchedOAuth2DynamicClientRegistrationRequest;
}
export interface ProvidersOauth2DcrRetrieveRequest {
pbmUuid: string;
}
export interface ProvidersOauth2DcrUpdateRequest {
pbmUuid: string;
oAuth2DynamicClientRegistrationRequest: OAuth2DynamicClientRegistrationRequest;
}
export interface ProvidersOauth2DestroyRequest {
id: number;
}
@@ -4544,6 +4590,430 @@ export class ProvidersApi extends runtime.BaseAPI {
return await response.value();
}
/**
* Creates request options for providersOauth2DcrCreate without sending the request
*/
async providersOauth2DcrCreateRequestOpts(
requestParameters: ProvidersOauth2DcrCreateRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["oAuth2DynamicClientRegistrationRequest"] == null) {
throw new runtime.RequiredError(
"oAuth2DynamicClientRegistrationRequest",
'Required parameter "oAuth2DynamicClientRegistrationRequest" was null or undefined when calling providersOauth2DcrCreate().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters["Content-Type"] = "application/json";
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/providers/oauth2-dcr/`;
return {
path: urlPath,
method: "POST",
headers: headerParameters,
query: queryParameters,
body: OAuth2DynamicClientRegistrationRequestToJSON(
requestParameters["oAuth2DynamicClientRegistrationRequest"],
),
};
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrCreateRaw(
requestParameters: ProvidersOauth2DcrCreateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<OAuth2DynamicClientRegistration>> {
const requestOptions = await this.providersOauth2DcrCreateRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) =>
OAuth2DynamicClientRegistrationFromJSON(jsonValue),
);
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrCreate(
requestParameters: ProvidersOauth2DcrCreateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<OAuth2DynamicClientRegistration> {
const response = await this.providersOauth2DcrCreateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Creates request options for providersOauth2DcrDestroy without sending the request
*/
async providersOauth2DcrDestroyRequestOpts(
requestParameters: ProvidersOauth2DcrDestroyRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["pbmUuid"] == null) {
throw new runtime.RequiredError(
"pbmUuid",
'Required parameter "pbmUuid" was null or undefined when calling providersOauth2DcrDestroy().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/providers/oauth2-dcr/{pbm_uuid}/`;
urlPath = urlPath.replace(
"{pbm_uuid}",
encodeURIComponent(String(requestParameters["pbmUuid"])),
);
return {
path: urlPath,
method: "DELETE",
headers: headerParameters,
query: queryParameters,
};
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrDestroyRaw(
requestParameters: ProvidersOauth2DcrDestroyRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<void>> {
const requestOptions = await this.providersOauth2DcrDestroyRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrDestroy(
requestParameters: ProvidersOauth2DcrDestroyRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<void> {
await this.providersOauth2DcrDestroyRaw(requestParameters, initOverrides);
}
/**
* Creates request options for providersOauth2DcrList without sending the request
*/
async providersOauth2DcrListRequestOpts(
requestParameters: ProvidersOauth2DcrListRequest,
): Promise<runtime.RequestOpts> {
const queryParameters: any = {};
if (requestParameters["ordering"] != null) {
queryParameters["ordering"] = requestParameters["ordering"];
}
if (requestParameters["page"] != null) {
queryParameters["page"] = requestParameters["page"];
}
if (requestParameters["pageSize"] != null) {
queryParameters["page_size"] = requestParameters["pageSize"];
}
if (requestParameters["provider"] != null) {
queryParameters["provider"] = requestParameters["provider"];
}
if (requestParameters["search"] != null) {
queryParameters["search"] = requestParameters["search"];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/providers/oauth2-dcr/`;
return {
path: urlPath,
method: "GET",
headers: headerParameters,
query: queryParameters,
};
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrListRaw(
requestParameters: ProvidersOauth2DcrListRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<PaginatedOAuth2DynamicClientRegistrationList>> {
const requestOptions = await this.providersOauth2DcrListRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) =>
PaginatedOAuth2DynamicClientRegistrationListFromJSON(jsonValue),
);
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrList(
requestParameters: ProvidersOauth2DcrListRequest = {},
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<PaginatedOAuth2DynamicClientRegistrationList> {
const response = await this.providersOauth2DcrListRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Creates request options for providersOauth2DcrPartialUpdate without sending the request
*/
async providersOauth2DcrPartialUpdateRequestOpts(
requestParameters: ProvidersOauth2DcrPartialUpdateRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["pbmUuid"] == null) {
throw new runtime.RequiredError(
"pbmUuid",
'Required parameter "pbmUuid" was null or undefined when calling providersOauth2DcrPartialUpdate().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters["Content-Type"] = "application/json";
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/providers/oauth2-dcr/{pbm_uuid}/`;
urlPath = urlPath.replace(
"{pbm_uuid}",
encodeURIComponent(String(requestParameters["pbmUuid"])),
);
return {
path: urlPath,
method: "PATCH",
headers: headerParameters,
query: queryParameters,
body: PatchedOAuth2DynamicClientRegistrationRequestToJSON(
requestParameters["patchedOAuth2DynamicClientRegistrationRequest"],
),
};
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrPartialUpdateRaw(
requestParameters: ProvidersOauth2DcrPartialUpdateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<OAuth2DynamicClientRegistration>> {
const requestOptions =
await this.providersOauth2DcrPartialUpdateRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) =>
OAuth2DynamicClientRegistrationFromJSON(jsonValue),
);
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrPartialUpdate(
requestParameters: ProvidersOauth2DcrPartialUpdateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<OAuth2DynamicClientRegistration> {
const response = await this.providersOauth2DcrPartialUpdateRaw(
requestParameters,
initOverrides,
);
return await response.value();
}
/**
* Creates request options for providersOauth2DcrRetrieve without sending the request
*/
async providersOauth2DcrRetrieveRequestOpts(
requestParameters: ProvidersOauth2DcrRetrieveRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["pbmUuid"] == null) {
throw new runtime.RequiredError(
"pbmUuid",
'Required parameter "pbmUuid" was null or undefined when calling providersOauth2DcrRetrieve().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/providers/oauth2-dcr/{pbm_uuid}/`;
urlPath = urlPath.replace(
"{pbm_uuid}",
encodeURIComponent(String(requestParameters["pbmUuid"])),
);
return {
path: urlPath,
method: "GET",
headers: headerParameters,
query: queryParameters,
};
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrRetrieveRaw(
requestParameters: ProvidersOauth2DcrRetrieveRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<OAuth2DynamicClientRegistration>> {
const requestOptions = await this.providersOauth2DcrRetrieveRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) =>
OAuth2DynamicClientRegistrationFromJSON(jsonValue),
);
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrRetrieve(
requestParameters: ProvidersOauth2DcrRetrieveRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<OAuth2DynamicClientRegistration> {
const response = await this.providersOauth2DcrRetrieveRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Creates request options for providersOauth2DcrUpdate without sending the request
*/
async providersOauth2DcrUpdateRequestOpts(
requestParameters: ProvidersOauth2DcrUpdateRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["pbmUuid"] == null) {
throw new runtime.RequiredError(
"pbmUuid",
'Required parameter "pbmUuid" was null or undefined when calling providersOauth2DcrUpdate().',
);
}
if (requestParameters["oAuth2DynamicClientRegistrationRequest"] == null) {
throw new runtime.RequiredError(
"oAuth2DynamicClientRegistrationRequest",
'Required parameter "oAuth2DynamicClientRegistrationRequest" was null or undefined when calling providersOauth2DcrUpdate().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters["Content-Type"] = "application/json";
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/providers/oauth2-dcr/{pbm_uuid}/`;
urlPath = urlPath.replace(
"{pbm_uuid}",
encodeURIComponent(String(requestParameters["pbmUuid"])),
);
return {
path: urlPath,
method: "PUT",
headers: headerParameters,
query: queryParameters,
body: OAuth2DynamicClientRegistrationRequestToJSON(
requestParameters["oAuth2DynamicClientRegistrationRequest"],
),
};
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrUpdateRaw(
requestParameters: ProvidersOauth2DcrUpdateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<OAuth2DynamicClientRegistration>> {
const requestOptions = await this.providersOauth2DcrUpdateRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) =>
OAuth2DynamicClientRegistrationFromJSON(jsonValue),
);
}
/**
* OAuth2 Dynamic Client Registration configuration ViewSet
*/
async providersOauth2DcrUpdate(
requestParameters: ProvidersOauth2DcrUpdateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<OAuth2DynamicClientRegistration> {
const response = await this.providersOauth2DcrUpdateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Creates request options for providersOauth2Destroy without sending the request
*/

View File

@@ -89,6 +89,7 @@ export const AppEnum = {
AuthentikEnterprisePoliciesUniquePassword: "authentik.enterprise.policies.unique_password",
AuthentikEnterpriseProvidersGoogleWorkspace: "authentik.enterprise.providers.google_workspace",
AuthentikEnterpriseProvidersMicrosoftEntra: "authentik.enterprise.providers.microsoft_entra",
AuthentikEnterpriseProvidersOauth2: "authentik.enterprise.providers.oauth2",
AuthentikEnterpriseProvidersRadius: "authentik.enterprise.providers.radius",
AuthentikEnterpriseProvidersScim: "authentik.enterprise.providers.scim",
AuthentikEnterpriseProvidersSsf: "authentik.enterprise.providers.ssf",

View File

@@ -16,7 +16,7 @@
*
* @export
*/
export const GrantTypesEnum = {
export const GrantTypeEnum = {
AuthorizationCode: "authorization_code",
Implicit: "implicit",
Hybrid: "hybrid",
@@ -27,12 +27,12 @@ export const GrantTypesEnum = {
UrnIetfParamsOauthGrantTypeTokenExchange: "urn:ietf:params:oauth:grant-type:token-exchange",
UnknownDefaultOpenApi: "11184809",
} as const;
export type GrantTypesEnum = (typeof GrantTypesEnum)[keyof typeof GrantTypesEnum];
export type GrantTypeEnum = (typeof GrantTypeEnum)[keyof typeof GrantTypeEnum];
export function instanceOfGrantTypesEnum(value: any): boolean {
for (const key in GrantTypesEnum) {
if (Object.prototype.hasOwnProperty.call(GrantTypesEnum, key)) {
if (GrantTypesEnum[key as keyof typeof GrantTypesEnum] === value) {
export function instanceOfGrantTypeEnum(value: any): boolean {
for (const key in GrantTypeEnum) {
if (Object.prototype.hasOwnProperty.call(GrantTypeEnum, key)) {
if (GrantTypeEnum[key as keyof typeof GrantTypeEnum] === value) {
return true;
}
}
@@ -40,24 +40,18 @@ export function instanceOfGrantTypesEnum(value: any): boolean {
return false;
}
export function GrantTypesEnumFromJSON(json: any): GrantTypesEnum {
return GrantTypesEnumFromJSONTyped(json, false);
export function GrantTypeEnumFromJSON(json: any): GrantTypeEnum {
return GrantTypeEnumFromJSONTyped(json, false);
}
export function GrantTypesEnumFromJSONTyped(
json: any,
ignoreDiscriminator: boolean,
): GrantTypesEnum {
return json as GrantTypesEnum;
export function GrantTypeEnumFromJSONTyped(json: any, ignoreDiscriminator: boolean): GrantTypeEnum {
return json as GrantTypeEnum;
}
export function GrantTypesEnumToJSON(value?: GrantTypesEnum | null): any {
export function GrantTypeEnumToJSON(value?: GrantTypeEnum | null): any {
return value as any;
}
export function GrantTypesEnumToJSONTyped(
value: any,
ignoreDiscriminator: boolean,
): GrantTypesEnum {
return value as GrantTypesEnum;
export function GrantTypeEnumToJSONTyped(value: any, ignoreDiscriminator: boolean): GrantTypeEnum {
return value as GrantTypeEnum;
}

View File

@@ -57,6 +57,8 @@ export const ModelEnum = {
AuthentikProvidersLdapLdapprovider: "authentik_providers_ldap.ldapprovider",
AuthentikProvidersOauth2Scopemapping: "authentik_providers_oauth2.scopemapping",
AuthentikProvidersOauth2Oauth2provider: "authentik_providers_oauth2.oauth2provider",
AuthentikProvidersOauth2Oauth2dynamicclientregistration:
"authentik_providers_oauth2.oauth2dynamicclientregistration",
AuthentikProvidersProxyProxyprovider: "authentik_providers_proxy.proxyprovider",
AuthentikProvidersRacRacprovider: "authentik_providers_rac.racprovider",
AuthentikProvidersRacEndpoint: "authentik_providers_rac.endpoint",

View File

@@ -0,0 +1,182 @@
/* tslint:disable */
/* eslint-disable */
/**
* authentik
* Making authentication simple.
*
* The version of the OpenAPI document: 2026.8.0-rc1
* Contact: hello@goauthentik.io
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { GrantTypeEnum } from "./GrantTypeEnum";
import { GrantTypeEnumFromJSON, GrantTypeEnumToJSON } from "./GrantTypeEnum";
import type { PolicyEngineMode } from "./PolicyEngineMode";
import { PolicyEngineModeFromJSON, PolicyEngineModeToJSON } from "./PolicyEngineMode";
/**
* Serializer for OAuth2DynamicClientRegistration
* @export
* @interface OAuth2DynamicClientRegistration
*/
export interface OAuth2DynamicClientRegistration {
/**
*
* @type {string}
* @memberof OAuth2DynamicClientRegistration
*/
readonly pbmUuid: string;
/**
*
* @type {number}
* @memberof OAuth2DynamicClientRegistration
*/
provider: number;
/**
* Group to assign to automatically created applications.
* @type {string}
* @memberof OAuth2DynamicClientRegistration
*/
defaultApplicationGroup?: string;
/**
* Authorization flow applied to dynamically registered clients.
* @type {string}
* @memberof OAuth2DynamicClientRegistration
*/
overrideAuthorizationFlow?: string | null;
/**
*
* @type {string}
* @memberof OAuth2DynamicClientRegistration
*/
overrideInvalidationFlow?: string | null;
/**
* Scope mappings applied to dynamically registered clients.
* @type {Array<string>}
* @memberof OAuth2DynamicClientRegistration
*/
overridePropertyMappings?: Array<string>;
/**
* Maximum access token validity for registered clients (Format: hours=1;minutes=2;seconds=3).
* @type {string}
* @memberof OAuth2DynamicClientRegistration
*/
accessTokenValidity?: string;
/**
* Maximum refresh token validity for registered clients (Format: hours=1;minutes=2;seconds=3).
* @type {string}
* @memberof OAuth2DynamicClientRegistration
*/
refreshTokenValidity?: string;
/**
* If empty, all grant types are allowed.
* @type {Array<GrantTypeEnum>}
* @memberof OAuth2DynamicClientRegistration
*/
allowedGrantTypes?: Array<GrantTypeEnum>;
/**
*
* @type {PolicyEngineMode}
* @memberof OAuth2DynamicClientRegistration
*/
policyEngineMode?: PolicyEngineMode;
}
/**
* Check if a given object implements the OAuth2DynamicClientRegistration interface.
*/
export function instanceOfOAuth2DynamicClientRegistration(
value: object,
): value is OAuth2DynamicClientRegistration {
if (
(!("pbmUuid" in (value as Record<string, any>)) &&
!("pbm_uuid" in (value as Record<string, any>))) ||
((value as Record<string, any>)["pbmUuid"] === undefined &&
(value as Record<string, any>)["pbm_uuid"] === undefined)
)
return false;
if (!("provider" in value) || value["provider"] === undefined) return false;
return true;
}
export function OAuth2DynamicClientRegistrationFromJSON(
json: any,
): OAuth2DynamicClientRegistration {
return OAuth2DynamicClientRegistrationFromJSONTyped(json, false);
}
export function OAuth2DynamicClientRegistrationFromJSONTyped(
json: any,
ignoreDiscriminator: boolean,
): OAuth2DynamicClientRegistration {
if (json == null) {
return json;
}
return {
pbmUuid: json["pbm_uuid"],
provider: json["provider"],
defaultApplicationGroup:
json["default_application_group"] == null
? undefined
: json["default_application_group"],
overrideAuthorizationFlow:
json["override_authorization_flow"] === undefined
? undefined
: json["override_authorization_flow"] === null
? null
: json["override_authorization_flow"],
overrideInvalidationFlow:
json["override_invalidation_flow"] === undefined
? undefined
: json["override_invalidation_flow"] === null
? null
: json["override_invalidation_flow"],
overridePropertyMappings:
json["override_property_mappings"] == null
? undefined
: json["override_property_mappings"],
accessTokenValidity:
json["access_token_validity"] == null ? undefined : json["access_token_validity"],
refreshTokenValidity:
json["refresh_token_validity"] == null ? undefined : json["refresh_token_validity"],
allowedGrantTypes:
json["allowed_grant_types"] == null
? undefined
: (json["allowed_grant_types"] as Array<any>).map(GrantTypeEnumFromJSON),
policyEngineMode:
json["policy_engine_mode"] == null
? undefined
: PolicyEngineModeFromJSON(json["policy_engine_mode"]),
};
}
export function OAuth2DynamicClientRegistrationToJSON(json: any): OAuth2DynamicClientRegistration {
return OAuth2DynamicClientRegistrationToJSONTyped(json, false);
}
export function OAuth2DynamicClientRegistrationToJSONTyped(
value?: Omit<OAuth2DynamicClientRegistration, "pbmUuid"> | null,
ignoreDiscriminator: boolean = false,
): any {
if (value == null) {
return value;
}
return {
provider: value["provider"],
default_application_group: value["defaultApplicationGroup"],
override_authorization_flow: value["overrideAuthorizationFlow"],
override_invalidation_flow: value["overrideInvalidationFlow"],
override_property_mappings: value["overridePropertyMappings"],
access_token_validity: value["accessTokenValidity"],
refresh_token_validity: value["refreshTokenValidity"],
allowed_grant_types:
value["allowedGrantTypes"] == null
? undefined
: (value["allowedGrantTypes"] as Array<any>).map(GrantTypeEnumToJSON),
policy_engine_mode: PolicyEngineModeToJSON(value["policyEngineMode"]),
};
}

View File

@@ -0,0 +1,170 @@
/* tslint:disable */
/* eslint-disable */
/**
* authentik
* Making authentication simple.
*
* The version of the OpenAPI document: 2026.8.0-rc1
* Contact: hello@goauthentik.io
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { GrantTypeEnum } from "./GrantTypeEnum";
import { GrantTypeEnumFromJSON, GrantTypeEnumToJSON } from "./GrantTypeEnum";
import type { PolicyEngineMode } from "./PolicyEngineMode";
import { PolicyEngineModeFromJSON, PolicyEngineModeToJSON } from "./PolicyEngineMode";
/**
* Serializer for OAuth2DynamicClientRegistration
* @export
* @interface OAuth2DynamicClientRegistrationRequest
*/
export interface OAuth2DynamicClientRegistrationRequest {
/**
*
* @type {number}
* @memberof OAuth2DynamicClientRegistrationRequest
*/
provider: number;
/**
* Group to assign to automatically created applications.
* @type {string}
* @memberof OAuth2DynamicClientRegistrationRequest
*/
defaultApplicationGroup?: string;
/**
* Authorization flow applied to dynamically registered clients.
* @type {string}
* @memberof OAuth2DynamicClientRegistrationRequest
*/
overrideAuthorizationFlow?: string | null;
/**
*
* @type {string}
* @memberof OAuth2DynamicClientRegistrationRequest
*/
overrideInvalidationFlow?: string | null;
/**
* Scope mappings applied to dynamically registered clients.
* @type {Array<string>}
* @memberof OAuth2DynamicClientRegistrationRequest
*/
overridePropertyMappings?: Array<string>;
/**
* Maximum access token validity for registered clients (Format: hours=1;minutes=2;seconds=3).
* @type {string}
* @memberof OAuth2DynamicClientRegistrationRequest
*/
accessTokenValidity?: string;
/**
* Maximum refresh token validity for registered clients (Format: hours=1;minutes=2;seconds=3).
* @type {string}
* @memberof OAuth2DynamicClientRegistrationRequest
*/
refreshTokenValidity?: string;
/**
* If empty, all grant types are allowed.
* @type {Array<GrantTypeEnum>}
* @memberof OAuth2DynamicClientRegistrationRequest
*/
allowedGrantTypes?: Array<GrantTypeEnum>;
/**
*
* @type {PolicyEngineMode}
* @memberof OAuth2DynamicClientRegistrationRequest
*/
policyEngineMode?: PolicyEngineMode;
}
/**
* Check if a given object implements the OAuth2DynamicClientRegistrationRequest interface.
*/
export function instanceOfOAuth2DynamicClientRegistrationRequest(
value: object,
): value is OAuth2DynamicClientRegistrationRequest {
if (!("provider" in value) || value["provider"] === undefined) return false;
return true;
}
export function OAuth2DynamicClientRegistrationRequestFromJSON(
json: any,
): OAuth2DynamicClientRegistrationRequest {
return OAuth2DynamicClientRegistrationRequestFromJSONTyped(json, false);
}
export function OAuth2DynamicClientRegistrationRequestFromJSONTyped(
json: any,
ignoreDiscriminator: boolean,
): OAuth2DynamicClientRegistrationRequest {
if (json == null) {
return json;
}
return {
provider: json["provider"],
defaultApplicationGroup:
json["default_application_group"] == null
? undefined
: json["default_application_group"],
overrideAuthorizationFlow:
json["override_authorization_flow"] === undefined
? undefined
: json["override_authorization_flow"] === null
? null
: json["override_authorization_flow"],
overrideInvalidationFlow:
json["override_invalidation_flow"] === undefined
? undefined
: json["override_invalidation_flow"] === null
? null
: json["override_invalidation_flow"],
overridePropertyMappings:
json["override_property_mappings"] == null
? undefined
: json["override_property_mappings"],
accessTokenValidity:
json["access_token_validity"] == null ? undefined : json["access_token_validity"],
refreshTokenValidity:
json["refresh_token_validity"] == null ? undefined : json["refresh_token_validity"],
allowedGrantTypes:
json["allowed_grant_types"] == null
? undefined
: (json["allowed_grant_types"] as Array<any>).map(GrantTypeEnumFromJSON),
policyEngineMode:
json["policy_engine_mode"] == null
? undefined
: PolicyEngineModeFromJSON(json["policy_engine_mode"]),
};
}
export function OAuth2DynamicClientRegistrationRequestToJSON(
json: any,
): OAuth2DynamicClientRegistrationRequest {
return OAuth2DynamicClientRegistrationRequestToJSONTyped(json, false);
}
export function OAuth2DynamicClientRegistrationRequestToJSONTyped(
value?: OAuth2DynamicClientRegistrationRequest | null,
ignoreDiscriminator: boolean = false,
): any {
if (value == null) {
return value;
}
return {
provider: value["provider"],
default_application_group: value["defaultApplicationGroup"],
override_authorization_flow: value["overrideAuthorizationFlow"],
override_invalidation_flow: value["overrideInvalidationFlow"],
override_property_mappings: value["overridePropertyMappings"],
access_token_validity: value["accessTokenValidity"],
refresh_token_validity: value["refreshTokenValidity"],
allowed_grant_types:
value["allowedGrantTypes"] == null
? undefined
: (value["allowedGrantTypes"] as Array<any>).map(GrantTypeEnumToJSON),
policy_engine_mode: PolicyEngineModeToJSON(value["policyEngineMode"]),
};
}

View File

@@ -14,8 +14,8 @@
import type { ClientTypeEnum } from "./ClientTypeEnum";
import { ClientTypeEnumFromJSON, ClientTypeEnumToJSON } from "./ClientTypeEnum";
import type { GrantTypesEnum } from "./GrantTypesEnum";
import { GrantTypesEnumFromJSON, GrantTypesEnumToJSON } from "./GrantTypesEnum";
import type { GrantTypeEnum } from "./GrantTypeEnum";
import { GrantTypeEnumFromJSON, GrantTypeEnumToJSON } from "./GrantTypeEnum";
import type { IssuerModeEnum } from "./IssuerModeEnum";
import { IssuerModeEnumFromJSON, IssuerModeEnumToJSON } from "./IssuerModeEnum";
import type { OAuth2ProviderLogoutMethodEnum } from "./OAuth2ProviderLogoutMethodEnum";
@@ -126,10 +126,10 @@ export interface OAuth2Provider {
clientType?: ClientTypeEnum;
/**
*
* @type {Array<GrantTypesEnum>}
* @type {Array<GrantTypeEnum>}
* @memberof OAuth2Provider
*/
grantTypes?: Array<GrantTypesEnum>;
grantTypes?: Array<GrantTypeEnum>;
/**
*
* @type {string}
@@ -344,7 +344,7 @@ export function OAuth2ProviderFromJSONTyped(
grantTypes:
json["grant_types"] == null
? undefined
: (json["grant_types"] as Array<any>).map(GrantTypesEnumFromJSON),
: (json["grant_types"] as Array<any>).map(GrantTypeEnumFromJSON),
clientId: json["client_id"] == null ? undefined : json["client_id"],
clientSecret: json["client_secret"] == null ? undefined : json["client_secret"],
accessCodeValidity:
@@ -420,7 +420,7 @@ export function OAuth2ProviderToJSONTyped(
grant_types:
value["grantTypes"] == null
? undefined
: (value["grantTypes"] as Array<any>).map(GrantTypesEnumToJSON),
: (value["grantTypes"] as Array<any>).map(GrantTypeEnumToJSON),
client_id: value["clientId"],
client_secret: value["clientSecret"],
access_code_validity: value["accessCodeValidity"],

View File

@@ -14,8 +14,8 @@
import type { ClientTypeEnum } from "./ClientTypeEnum";
import { ClientTypeEnumFromJSON, ClientTypeEnumToJSON } from "./ClientTypeEnum";
import type { GrantTypesEnum } from "./GrantTypesEnum";
import { GrantTypesEnumFromJSON, GrantTypesEnumToJSON } from "./GrantTypesEnum";
import type { GrantTypeEnum } from "./GrantTypeEnum";
import { GrantTypeEnumFromJSON, GrantTypeEnumToJSON } from "./GrantTypeEnum";
import type { IssuerModeEnum } from "./IssuerModeEnum";
import { IssuerModeEnumFromJSON, IssuerModeEnumToJSON } from "./IssuerModeEnum";
import type { OAuth2ProviderLogoutMethodEnum } from "./OAuth2ProviderLogoutMethodEnum";
@@ -72,10 +72,10 @@ export interface OAuth2ProviderRequest {
clientType?: ClientTypeEnum;
/**
*
* @type {Array<GrantTypesEnum>}
* @type {Array<GrantTypeEnum>}
* @memberof OAuth2ProviderRequest
*/
grantTypes?: Array<GrantTypesEnum>;
grantTypes?: Array<GrantTypeEnum>;
/**
*
* @type {string}
@@ -230,7 +230,7 @@ export function OAuth2ProviderRequestFromJSONTyped(
grantTypes:
json["grant_types"] == null
? undefined
: (json["grant_types"] as Array<any>).map(GrantTypesEnumFromJSON),
: (json["grant_types"] as Array<any>).map(GrantTypeEnumFromJSON),
clientId: json["client_id"] == null ? undefined : json["client_id"],
clientSecret: json["client_secret"] == null ? undefined : json["client_secret"],
accessCodeValidity:
@@ -295,7 +295,7 @@ export function OAuth2ProviderRequestToJSONTyped(
grant_types:
value["grantTypes"] == null
? undefined
: (value["grantTypes"] as Array<any>).map(GrantTypesEnumToJSON),
: (value["grantTypes"] as Array<any>).map(GrantTypeEnumToJSON),
client_id: value["clientId"],
client_secret: value["clientSecret"],
access_code_validity: value["accessCodeValidity"],

View File

@@ -0,0 +1,100 @@
/* tslint:disable */
/* eslint-disable */
/**
* authentik
* Making authentication simple.
*
* The version of the OpenAPI document: 2026.8.0-rc1
* Contact: hello@goauthentik.io
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { OAuth2DynamicClientRegistration } from "./OAuth2DynamicClientRegistration";
import {
OAuth2DynamicClientRegistrationFromJSON,
OAuth2DynamicClientRegistrationToJSON,
} from "./OAuth2DynamicClientRegistration";
import type { Pagination } from "./Pagination";
import { PaginationFromJSON, PaginationToJSON } from "./Pagination";
/**
*
* @export
* @interface PaginatedOAuth2DynamicClientRegistrationList
*/
export interface PaginatedOAuth2DynamicClientRegistrationList {
/**
*
* @type {Pagination}
* @memberof PaginatedOAuth2DynamicClientRegistrationList
*/
pagination: Pagination;
/**
*
* @type {Array<OAuth2DynamicClientRegistration>}
* @memberof PaginatedOAuth2DynamicClientRegistrationList
*/
results: Array<OAuth2DynamicClientRegistration>;
/**
*
* @type {{ [key: string]: any; }}
* @memberof PaginatedOAuth2DynamicClientRegistrationList
*/
autocomplete: { [key: string]: any };
}
/**
* Check if a given object implements the PaginatedOAuth2DynamicClientRegistrationList interface.
*/
export function instanceOfPaginatedOAuth2DynamicClientRegistrationList(
value: object,
): value is PaginatedOAuth2DynamicClientRegistrationList {
if (!("pagination" in value) || value["pagination"] === undefined) return false;
if (!("results" in value) || value["results"] === undefined) return false;
if (!("autocomplete" in value) || value["autocomplete"] === undefined) return false;
return true;
}
export function PaginatedOAuth2DynamicClientRegistrationListFromJSON(
json: any,
): PaginatedOAuth2DynamicClientRegistrationList {
return PaginatedOAuth2DynamicClientRegistrationListFromJSONTyped(json, false);
}
export function PaginatedOAuth2DynamicClientRegistrationListFromJSONTyped(
json: any,
ignoreDiscriminator: boolean,
): PaginatedOAuth2DynamicClientRegistrationList {
if (json == null) {
return json;
}
return {
pagination: PaginationFromJSON(json["pagination"]),
results: (json["results"] as Array<any>).map(OAuth2DynamicClientRegistrationFromJSON),
autocomplete: json["autocomplete"],
};
}
export function PaginatedOAuth2DynamicClientRegistrationListToJSON(
json: any,
): PaginatedOAuth2DynamicClientRegistrationList {
return PaginatedOAuth2DynamicClientRegistrationListToJSONTyped(json, false);
}
export function PaginatedOAuth2DynamicClientRegistrationListToJSONTyped(
value?: PaginatedOAuth2DynamicClientRegistrationList | null,
ignoreDiscriminator: boolean = false,
): any {
if (value == null) {
return value;
}
return {
pagination: PaginationToJSON(value["pagination"]),
results: (value["results"] as Array<any>).map(OAuth2DynamicClientRegistrationToJSON),
autocomplete: value["autocomplete"],
};
}

View File

@@ -0,0 +1,169 @@
/* tslint:disable */
/* eslint-disable */
/**
* authentik
* Making authentication simple.
*
* The version of the OpenAPI document: 2026.8.0-rc1
* Contact: hello@goauthentik.io
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { GrantTypeEnum } from "./GrantTypeEnum";
import { GrantTypeEnumFromJSON, GrantTypeEnumToJSON } from "./GrantTypeEnum";
import type { PolicyEngineMode } from "./PolicyEngineMode";
import { PolicyEngineModeFromJSON, PolicyEngineModeToJSON } from "./PolicyEngineMode";
/**
* Serializer for OAuth2DynamicClientRegistration
* @export
* @interface PatchedOAuth2DynamicClientRegistrationRequest
*/
export interface PatchedOAuth2DynamicClientRegistrationRequest {
/**
*
* @type {number}
* @memberof PatchedOAuth2DynamicClientRegistrationRequest
*/
provider?: number;
/**
* Group to assign to automatically created applications.
* @type {string}
* @memberof PatchedOAuth2DynamicClientRegistrationRequest
*/
defaultApplicationGroup?: string;
/**
* Authorization flow applied to dynamically registered clients.
* @type {string}
* @memberof PatchedOAuth2DynamicClientRegistrationRequest
*/
overrideAuthorizationFlow?: string | null;
/**
*
* @type {string}
* @memberof PatchedOAuth2DynamicClientRegistrationRequest
*/
overrideInvalidationFlow?: string | null;
/**
* Scope mappings applied to dynamically registered clients.
* @type {Array<string>}
* @memberof PatchedOAuth2DynamicClientRegistrationRequest
*/
overridePropertyMappings?: Array<string>;
/**
* Maximum access token validity for registered clients (Format: hours=1;minutes=2;seconds=3).
* @type {string}
* @memberof PatchedOAuth2DynamicClientRegistrationRequest
*/
accessTokenValidity?: string;
/**
* Maximum refresh token validity for registered clients (Format: hours=1;minutes=2;seconds=3).
* @type {string}
* @memberof PatchedOAuth2DynamicClientRegistrationRequest
*/
refreshTokenValidity?: string;
/**
* If empty, all grant types are allowed.
* @type {Array<GrantTypeEnum>}
* @memberof PatchedOAuth2DynamicClientRegistrationRequest
*/
allowedGrantTypes?: Array<GrantTypeEnum>;
/**
*
* @type {PolicyEngineMode}
* @memberof PatchedOAuth2DynamicClientRegistrationRequest
*/
policyEngineMode?: PolicyEngineMode;
}
/**
* Check if a given object implements the PatchedOAuth2DynamicClientRegistrationRequest interface.
*/
export function instanceOfPatchedOAuth2DynamicClientRegistrationRequest(
value: object,
): value is PatchedOAuth2DynamicClientRegistrationRequest {
return true;
}
export function PatchedOAuth2DynamicClientRegistrationRequestFromJSON(
json: any,
): PatchedOAuth2DynamicClientRegistrationRequest {
return PatchedOAuth2DynamicClientRegistrationRequestFromJSONTyped(json, false);
}
export function PatchedOAuth2DynamicClientRegistrationRequestFromJSONTyped(
json: any,
ignoreDiscriminator: boolean,
): PatchedOAuth2DynamicClientRegistrationRequest {
if (json == null) {
return json;
}
return {
provider: json["provider"] == null ? undefined : json["provider"],
defaultApplicationGroup:
json["default_application_group"] == null
? undefined
: json["default_application_group"],
overrideAuthorizationFlow:
json["override_authorization_flow"] === undefined
? undefined
: json["override_authorization_flow"] === null
? null
: json["override_authorization_flow"],
overrideInvalidationFlow:
json["override_invalidation_flow"] === undefined
? undefined
: json["override_invalidation_flow"] === null
? null
: json["override_invalidation_flow"],
overridePropertyMappings:
json["override_property_mappings"] == null
? undefined
: json["override_property_mappings"],
accessTokenValidity:
json["access_token_validity"] == null ? undefined : json["access_token_validity"],
refreshTokenValidity:
json["refresh_token_validity"] == null ? undefined : json["refresh_token_validity"],
allowedGrantTypes:
json["allowed_grant_types"] == null
? undefined
: (json["allowed_grant_types"] as Array<any>).map(GrantTypeEnumFromJSON),
policyEngineMode:
json["policy_engine_mode"] == null
? undefined
: PolicyEngineModeFromJSON(json["policy_engine_mode"]),
};
}
export function PatchedOAuth2DynamicClientRegistrationRequestToJSON(
json: any,
): PatchedOAuth2DynamicClientRegistrationRequest {
return PatchedOAuth2DynamicClientRegistrationRequestToJSONTyped(json, false);
}
export function PatchedOAuth2DynamicClientRegistrationRequestToJSONTyped(
value?: PatchedOAuth2DynamicClientRegistrationRequest | null,
ignoreDiscriminator: boolean = false,
): any {
if (value == null) {
return value;
}
return {
provider: value["provider"],
default_application_group: value["defaultApplicationGroup"],
override_authorization_flow: value["overrideAuthorizationFlow"],
override_invalidation_flow: value["overrideInvalidationFlow"],
override_property_mappings: value["overridePropertyMappings"],
access_token_validity: value["accessTokenValidity"],
refresh_token_validity: value["refreshTokenValidity"],
allowed_grant_types:
value["allowedGrantTypes"] == null
? undefined
: (value["allowedGrantTypes"] as Array<any>).map(GrantTypeEnumToJSON),
policy_engine_mode: PolicyEngineModeToJSON(value["policyEngineMode"]),
};
}

View File

@@ -14,8 +14,8 @@
import type { ClientTypeEnum } from "./ClientTypeEnum";
import { ClientTypeEnumFromJSON, ClientTypeEnumToJSON } from "./ClientTypeEnum";
import type { GrantTypesEnum } from "./GrantTypesEnum";
import { GrantTypesEnumFromJSON, GrantTypesEnumToJSON } from "./GrantTypesEnum";
import type { GrantTypeEnum } from "./GrantTypeEnum";
import { GrantTypeEnumFromJSON, GrantTypeEnumToJSON } from "./GrantTypeEnum";
import type { IssuerModeEnum } from "./IssuerModeEnum";
import { IssuerModeEnumFromJSON, IssuerModeEnumToJSON } from "./IssuerModeEnum";
import type { OAuth2ProviderLogoutMethodEnum } from "./OAuth2ProviderLogoutMethodEnum";
@@ -72,10 +72,10 @@ export interface PatchedOAuth2ProviderRequest {
clientType?: ClientTypeEnum;
/**
*
* @type {Array<GrantTypesEnum>}
* @type {Array<GrantTypeEnum>}
* @memberof PatchedOAuth2ProviderRequest
*/
grantTypes?: Array<GrantTypesEnum>;
grantTypes?: Array<GrantTypeEnum>;
/**
*
* @type {string}
@@ -211,7 +211,7 @@ export function PatchedOAuth2ProviderRequestFromJSONTyped(
grantTypes:
json["grant_types"] == null
? undefined
: (json["grant_types"] as Array<any>).map(GrantTypesEnumFromJSON),
: (json["grant_types"] as Array<any>).map(GrantTypeEnumFromJSON),
clientId: json["client_id"] == null ? undefined : json["client_id"],
clientSecret: json["client_secret"] == null ? undefined : json["client_secret"],
accessCodeValidity:
@@ -279,7 +279,7 @@ export function PatchedOAuth2ProviderRequestToJSONTyped(
grant_types:
value["grantTypes"] == null
? undefined
: (value["grantTypes"] as Array<any>).map(GrantTypesEnumToJSON),
: (value["grantTypes"] as Array<any>).map(GrantTypeEnumToJSON),
client_id: value["clientId"],
client_secret: value["clientSecret"],
access_code_validity: value["accessCodeValidity"],

View File

@@ -225,7 +225,7 @@ export * from "./GoogleWorkspaceProviderUser";
export * from "./GoogleWorkspaceProviderUserRequest";
export * from "./GrantRequest";
export * from "./GrantRequestCreateRequest";
export * from "./GrantTypesEnum";
export * from "./GrantTypeEnum";
export * from "./Group";
export * from "./GroupKerberosSourceConnection";
export * from "./GroupKerberosSourceConnectionRequest";
@@ -335,6 +335,8 @@ export * from "./NotificationTransportRequest";
export * from "./NotificationTransportTest";
export * from "./NotificationWebhookMapping";
export * from "./NotificationWebhookMappingRequest";
export * from "./OAuth2DynamicClientRegistration";
export * from "./OAuth2DynamicClientRegistrationRequest";
export * from "./OAuth2Provider";
export * from "./OAuth2ProviderLogoutMethodEnum";
export * from "./OAuth2ProviderRequest";
@@ -445,6 +447,7 @@ export * from "./PaginatedNotificationList";
export * from "./PaginatedNotificationRuleList";
export * from "./PaginatedNotificationTransportList";
export * from "./PaginatedNotificationWebhookMappingList";
export * from "./PaginatedOAuth2DynamicClientRegistrationList";
export * from "./PaginatedOAuth2ProviderList";
export * from "./PaginatedOAuthSourceList";
export * from "./PaginatedOAuthSourcePropertyMappingList";
@@ -608,6 +611,7 @@ export * from "./PatchedNotificationRequest";
export * from "./PatchedNotificationRuleRequest";
export * from "./PatchedNotificationTransportRequest";
export * from "./PatchedNotificationWebhookMappingRequest";
export * from "./PatchedOAuth2DynamicClientRegistrationRequest";
export * from "./PatchedOAuth2ProviderRequest";
export * from "./PatchedOAuthSourcePropertyMappingRequest";
export * from "./PatchedOAuthSourceRequest";

View File

@@ -18549,6 +18549,170 @@ paths:
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
/providers/oauth2-dcr/:
get:
operationId: providers_oauth2_dcr_list
description: OAuth2 Dynamic Client Registration configuration ViewSet
parameters:
- $ref: '#/components/parameters/QueryPaginationOrdering'
- $ref: '#/components/parameters/QueryPaginationPage'
- $ref: '#/components/parameters/QueryPaginationPageSize'
- in: query
name: provider
schema:
type: integer
- $ref: '#/components/parameters/QuerySearch'
tags:
- providers
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedOAuth2DynamicClientRegistrationList'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
post:
operationId: providers_oauth2_dcr_create
description: OAuth2 Dynamic Client Registration configuration ViewSet
tags:
- providers
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/OAuth2DynamicClientRegistrationRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/OAuth2DynamicClientRegistration'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
/providers/oauth2-dcr/{pbm_uuid}/:
get:
operationId: providers_oauth2_dcr_retrieve
description: OAuth2 Dynamic Client Registration configuration ViewSet
parameters:
- in: path
name: pbm_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this OAuth2 Dynamic Client Registration.
required: true
tags:
- providers
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/OAuth2DynamicClientRegistration'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
put:
operationId: providers_oauth2_dcr_update
description: OAuth2 Dynamic Client Registration configuration ViewSet
parameters:
- in: path
name: pbm_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this OAuth2 Dynamic Client Registration.
required: true
tags:
- providers
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/OAuth2DynamicClientRegistrationRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/OAuth2DynamicClientRegistration'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
patch:
operationId: providers_oauth2_dcr_partial_update
description: OAuth2 Dynamic Client Registration configuration ViewSet
parameters:
- in: path
name: pbm_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this OAuth2 Dynamic Client Registration.
required: true
tags:
- providers
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedOAuth2DynamicClientRegistrationRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/OAuth2DynamicClientRegistration'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
delete:
operationId: providers_oauth2_dcr_destroy
description: OAuth2 Dynamic Client Registration configuration ViewSet
parameters:
- in: path
name: pbm_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this OAuth2 Dynamic Client Registration.
required: true
tags:
- providers
security:
- authentik: []
responses:
'204':
description: No response body
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
/providers/oauth2/{id}/:
get:
operationId: providers_oauth2_retrieve
@@ -35607,6 +35771,7 @@ components:
- authentik.enterprise.policies.unique_password
- authentik.enterprise.providers.google_workspace
- authentik.enterprise.providers.microsoft_entra
- authentik.enterprise.providers.oauth2
- authentik.enterprise.providers.radius
- authentik.enterprise.providers.scim
- authentik.enterprise.providers.ssf
@@ -41784,7 +41949,7 @@ components:
Clamped to the granting rule binding(s)' expiry_granted_max.
required:
- pbms
GrantTypesEnum:
GrantTypeEnum:
enum:
- authorization_code
- implicit
@@ -44805,6 +44970,7 @@ components:
- authentik_providers_ldap.ldapprovider
- authentik_providers_oauth2.scopemapping
- authentik_providers_oauth2.oauth2provider
- authentik_providers_oauth2.oauth2dynamicclientregistration
- authentik_providers_proxy.proxyprovider
- authentik_providers_rac.racprovider
- authentik_providers_rac.endpoint
@@ -45373,6 +45539,95 @@ components:
required:
- expression
- name
OAuth2DynamicClientRegistration:
type: object
description: Serializer for OAuth2DynamicClientRegistration
properties:
pbm_uuid:
type: string
format: uuid
readOnly: true
provider:
type: integer
default_application_group:
type: string
description: Group to assign to automatically created applications.
override_authorization_flow:
type: string
format: uuid
nullable: true
description: Authorization flow applied to dynamically registered clients.
override_invalidation_flow:
type: string
format: uuid
nullable: true
override_property_mappings:
type: array
items:
type: string
format: uuid
description: Scope mappings applied to dynamically registered clients.
access_token_validity:
type: string
description: 'Maximum access token validity for registered clients (Format:
hours=1;minutes=2;seconds=3).'
refresh_token_validity:
type: string
description: 'Maximum refresh token validity for registered clients (Format:
hours=1;minutes=2;seconds=3).'
allowed_grant_types:
type: array
items:
$ref: '#/components/schemas/GrantTypeEnum'
description: If empty, all grant types are allowed.
policy_engine_mode:
$ref: '#/components/schemas/PolicyEngineMode'
required:
- pbm_uuid
- provider
OAuth2DynamicClientRegistrationRequest:
type: object
description: Serializer for OAuth2DynamicClientRegistration
properties:
provider:
type: integer
default_application_group:
type: string
description: Group to assign to automatically created applications.
override_authorization_flow:
type: string
format: uuid
nullable: true
description: Authorization flow applied to dynamically registered clients.
override_invalidation_flow:
type: string
format: uuid
nullable: true
override_property_mappings:
type: array
items:
type: string
format: uuid
description: Scope mappings applied to dynamically registered clients.
access_token_validity:
type: string
minLength: 1
description: 'Maximum access token validity for registered clients (Format:
hours=1;minutes=2;seconds=3).'
refresh_token_validity:
type: string
minLength: 1
description: 'Maximum refresh token validity for registered clients (Format:
hours=1;minutes=2;seconds=3).'
allowed_grant_types:
type: array
items:
$ref: '#/components/schemas/GrantTypeEnum'
description: If empty, all grant types are allowed.
policy_engine_mode:
$ref: '#/components/schemas/PolicyEngineMode'
required:
- provider
OAuth2Provider:
type: object
description: OAuth2Provider Serializer
@@ -45446,7 +45701,7 @@ components:
grant_types:
type: array
items:
$ref: '#/components/schemas/GrantTypesEnum'
$ref: '#/components/schemas/GrantTypeEnum'
client_id:
type: string
maxLength: 255
@@ -45571,7 +45826,7 @@ components:
grant_types:
type: array
items:
$ref: '#/components/schemas/GrantTypesEnum'
$ref: '#/components/schemas/GrantTypeEnum'
client_id:
type: string
minLength: 1
@@ -47693,6 +47948,21 @@ components:
- autocomplete
- pagination
- results
PaginatedOAuth2DynamicClientRegistrationList:
type: object
properties:
pagination:
$ref: '#/components/schemas/Pagination'
results:
type: array
items:
$ref: '#/components/schemas/OAuth2DynamicClientRegistration'
autocomplete:
$ref: '#/components/schemas/Autocomplete'
required:
- autocomplete
- pagination
- results
PaginatedOAuth2ProviderList:
type: object
properties:
@@ -51375,6 +51645,47 @@ components:
expression:
type: string
minLength: 1
PatchedOAuth2DynamicClientRegistrationRequest:
type: object
description: Serializer for OAuth2DynamicClientRegistration
properties:
provider:
type: integer
default_application_group:
type: string
description: Group to assign to automatically created applications.
override_authorization_flow:
type: string
format: uuid
nullable: true
description: Authorization flow applied to dynamically registered clients.
override_invalidation_flow:
type: string
format: uuid
nullable: true
override_property_mappings:
type: array
items:
type: string
format: uuid
description: Scope mappings applied to dynamically registered clients.
access_token_validity:
type: string
minLength: 1
description: 'Maximum access token validity for registered clients (Format:
hours=1;minutes=2;seconds=3).'
refresh_token_validity:
type: string
minLength: 1
description: 'Maximum refresh token validity for registered clients (Format:
hours=1;minutes=2;seconds=3).'
allowed_grant_types:
type: array
items:
$ref: '#/components/schemas/GrantTypeEnum'
description: If empty, all grant types are allowed.
policy_engine_mode:
$ref: '#/components/schemas/PolicyEngineMode'
PatchedOAuth2ProviderRequest:
type: object
description: OAuth2Provider Serializer
@@ -51409,7 +51720,7 @@ components:
grant_types:
type: array
items:
$ref: '#/components/schemas/GrantTypesEnum'
$ref: '#/components/schemas/GrantTypeEnum'
client_id:
type: string
minLength: 1

View File

@@ -0,0 +1,68 @@
import { renderForm } from "./OAuth2DCRFormForm.js";
import { aki } from "#common/api/client";
import { ModelForm } from "#elements/forms/ModelForm";
import { SlottedTemplateResult } from "#elements/types";
import { OAuth2DynamicClientRegistration, ProvidersApi } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { customElement, property } from "lit/decorators.js";
/**
* Form page for OAuth2 Dynamic Client Registration configuration
*
* @element ak-provider-oauth2-dcr-form
*
*/
@customElement("ak-provider-oauth2-dcr-form")
export class OAuth2DCRForm extends ModelForm<OAuth2DynamicClientRegistration, string> {
public static override verboseName = msg("Dynamic Client Registration");
public static override verboseNamePlural = msg("Dynamic Client Registration");
public static override createLabel = msg("Create");
/**
* The provider this configuration is (or will be) attached to.
* Only used when creating a new configuration.
*/
@property({ type: Number })
public providerID: number | null = null;
public override getSuccessMessage(): string {
return this.instance
? msg("Successfully updated Dynamic Client Registration.")
: msg("Successfully enabled Dynamic Client Registration.");
}
protected override loadInstance(pk: string): Promise<OAuth2DynamicClientRegistration> {
return aki(ProvidersApi).providersOauth2DcrRetrieve({
pbmUuid: pk,
});
}
public override async send(
data: OAuth2DynamicClientRegistration,
): Promise<OAuth2DynamicClientRegistration> {
if (this.instance) {
return aki(ProvidersApi).providersOauth2DcrUpdate({
pbmUuid: this.instance.pbmUuid,
oAuth2DynamicClientRegistrationRequest: data,
});
}
data.provider = this.providerID || 0;
return aki(ProvidersApi).providersOauth2DcrCreate({
oAuth2DynamicClientRegistrationRequest: data,
});
}
protected override renderForm(): SlottedTemplateResult {
return renderForm({ dcr: this.instance });
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-provider-oauth2-dcr-form": OAuth2DCRForm;
}
}

View File

@@ -0,0 +1,131 @@
import "#admin/common/ak-flow-search/ak-flow-search";
import "#components/ak-switch-input";
import "#components/ak-text-input";
import "#elements/LicenseNotice";
import "#elements/ak-checkbox-group/ak-checkbox-group";
import "#elements/ak-dual-select/ak-dual-select-dynamic-selected-provider";
import "#elements/forms/FormGroup";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/Radio";
import "#elements/utils/TimeDeltaHelp";
import "#components/ak-radio-input";
import { propertyMappingsProvider, propertyMappingsSelector } from "./OAuth2ProviderFormHelpers.js";
import { policyEngineModes } from "#admin/policies/PolicyEngineModes";
import { GrantTypeCheckboxItems } from "#admin/providers/oauth2/labels";
import { FlowDesignationEnum, OAuth2DynamicClientRegistration } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
export interface OAuth2DCRFormProps {
dcr?: Partial<OAuth2DynamicClientRegistration> | null;
}
export function renderForm({ dcr }: OAuth2DCRFormProps) {
dcr ||= {};
return html`<ak-license-notice></ak-license-notice>
<ak-text-input
name="defaultApplicationGroup"
label=${msg("Default application group")}
value="${ifDefined(dcr.defaultApplicationGroup)}"
help=${msg("Group assigned to automatically created applications.")}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Override authorization flow")}
name="overrideAuthorizationFlow"
>
<ak-flow-search
label=${msg("Override authorization flow")}
placeholder=${msg("Select an authorization flow...")}
flowType=${FlowDesignationEnum.Authorization}
.currentFlow=${dcr.overrideAuthorizationFlow}
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg(
"Authorization flow applied to dynamically registered clients. When not selected, authorization flow of the parent provider is used.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Override invalidation flow")}
name="overrideInvalidationFlow"
>
<ak-flow-search
label=${msg("Override invalidation flow")}
placeholder=${msg("Select an invalidation flow...")}
flowType=${FlowDesignationEnum.Invalidation}
.currentFlow=${dcr.overrideInvalidationFlow}
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg(
"Invalidation flow applied to dynamically registered clients. When not selected, authorization flow of the parent provider is used.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Override property mappings")}
name="overridePropertyMappings"
>
<ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider}
.selector=${propertyMappingsSelector(dcr.overridePropertyMappings)}
available-label=${msg("Available Scopes")}
selected-label=${msg("Selected Scopes")}
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text">
${msg(
"Scope mappings applied to dynamically registered clients. When not selected, authorization flow of the parent provider is used.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label=${msg("Advanced settings")}>
<div class="pf-c-form">
<ak-text-input
name="accessTokenValidity"
label=${msg("Access token validity")}
value="${dcr.accessTokenValidity ?? "hours=1"}"
input-hint="code"
required
.bighelp=${html`<p class="pf-c-form__helper-text">
${msg("Maximum access token validity for registered clients.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
></ak-text-input>
<ak-text-input
name="refreshTokenValidity"
label=${msg("Refresh token validity")}
value="${dcr.refreshTokenValidity ?? "days=30"}"
input-hint="code"
required
.bighelp=${html`<p class="pf-c-form__helper-text">
${msg("Maximum refresh token validity for registered clients.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Allowed grant types")}
name="allowedGrantTypes"
>
<ak-checkbox-group
name="allowedGrantTypes"
.options=${GrantTypeCheckboxItems}
.value=${dcr.allowedGrantTypes ?? []}
></ak-checkbox-group>
<p class="pf-c-form__helper-text">
${msg("If none are selected, all grant types are allowed.")}
</p>
</ak-form-element-horizontal>
<ak-radio-input
name="policyEngineMode"
label=${msg("Policy engine mode")}
required
.options=${policyEngineModes}
.value=${dcr.policyEngineMode}
></ak-radio-input>
</div>
</ak-form-group>`;
}

View File

@@ -30,7 +30,7 @@ import { AKLabel } from "#components/ak-label";
import {
ClientTypeEnum,
FlowDesignationEnum,
GrantTypesEnum,
GrantTypeEnum,
IssuerModeEnum,
MatchingModeEnum,
OAuth2Provider,
@@ -134,25 +134,25 @@ const redirectUriHelpMessages: string[] = [
];
const grantTypes = [
[GrantTypesEnum.AuthorizationCode, msg("Authorization Code")],
[GrantTypesEnum.Implicit, msg("Implicit")],
[GrantTypesEnum.Hybrid, msg("Hybrid")],
[GrantTypesEnum.RefreshToken, msg("Refresh token")],
[GrantTypesEnum.ClientCredentials, msg("Client credentials")],
[GrantTypesEnum.Password, msg("Password")],
[GrantTypesEnum.UrnIetfParamsOauthGrantTypeDeviceCode, msg("Device-code")],
[GrantTypesEnum.UrnIetfParamsOauthGrantTypeTokenExchange, msg("Token exchange")],
[GrantTypeEnum.AuthorizationCode, msg("Authorization Code")],
[GrantTypeEnum.Implicit, msg("Implicit")],
[GrantTypeEnum.Hybrid, msg("Hybrid")],
[GrantTypeEnum.RefreshToken, msg("Refresh token")],
[GrantTypeEnum.ClientCredentials, msg("Client credentials")],
[GrantTypeEnum.Password, msg("Password")],
[GrantTypeEnum.UrnIetfParamsOauthGrantTypeDeviceCode, msg("Device-code")],
[GrantTypeEnum.UrnIetfParamsOauthGrantTypeTokenExchange, msg("Token exchange")],
];
const defaultGrantTypes = [
// TODO: Clean up defaults after 2026
GrantTypesEnum.AuthorizationCode,
GrantTypesEnum.Implicit,
GrantTypesEnum.Hybrid,
GrantTypesEnum.RefreshToken,
GrantTypesEnum.ClientCredentials,
GrantTypesEnum.Password,
GrantTypesEnum.UrnIetfParamsOauthGrantTypeDeviceCode,
GrantTypeEnum.AuthorizationCode,
GrantTypeEnum.Implicit,
GrantTypeEnum.Hybrid,
GrantTypeEnum.RefreshToken,
GrantTypeEnum.ClientCredentials,
GrantTypeEnum.Password,
GrantTypeEnum.UrnIetfParamsOauthGrantTypeDeviceCode,
];
type ShowClientSecret = (show: boolean) => void;

View File

@@ -13,6 +13,7 @@ import "#elements/ak-mdx/index";
import "#elements/buttons/ModalButton";
import "#elements/buttons/SpinnerButton/index";
import "#elements/Divider";
import "#admin/policies/BoundPoliciesList";
import { aki } from "#common/api/client";
import { EVENT_REFRESH } from "#common/constants";
@@ -24,6 +25,7 @@ import { SlottedTemplateResult } from "#elements/types";
import renderDescriptionList from "#components/DescriptionList";
import { taskCard } from "#components/tasks/taskCard";
import { OAuth2DCRForm } from "#admin/providers/oauth2/OAuth2DCRForm";
import { OAuth2ProviderFormPage } from "#admin/providers/oauth2/OAuth2ProviderForm";
import {
@@ -31,6 +33,7 @@ import {
CoreApi,
CoreUsersListRequest,
ModelEnum,
OAuth2DynamicClientRegistration,
OAuth2Provider,
OAuth2ProviderLogoutMethodEnum,
OAuth2ProviderSetupURLs,
@@ -101,6 +104,9 @@ export class OAuth2ProviderViewPage extends AKElement {
@state()
previewUser?: User;
@state()
dcrConfig?: OAuth2DynamicClientRegistration | null;
static styles: CSSResult[] = [
PFButton,
PFPage,
@@ -130,6 +136,19 @@ export class OAuth2ProviderViewPage extends AKElement {
.then((preview) => (this.preview = preview));
}
fetchDCRConfig(): void {
aki(ProvidersApi)
.providersOauth2DcrList({
provider: this.provider?.pk,
})
.then((response) => {
this.dcrConfig = response.results[0] ?? null;
})
.catch(() => {
this.dcrConfig = null;
});
}
render(): SlottedTemplateResult {
if (!this.provider) {
return nothing;
@@ -166,6 +185,18 @@ export class OAuth2ProviderViewPage extends AKElement {
>
${this.renderTabPreview()}
</div>
<div
role="tabpanel"
tabindex="0"
slot="page-dcr"
id="page-dcr"
aria-label="${msg("Dynamic Client Registration")}"
@activate=${() => {
this.fetchDCRConfig();
}}
>
${this.renderTabDCR()}
</div>
<div
role="tabpanel"
tabindex="0"
@@ -464,6 +495,86 @@ export class OAuth2ProviderViewPage extends AKElement {
</div>
</div>`;
}
renderTabDCR(): SlottedTemplateResult {
if (this.dcrConfig === undefined) {
return html`<ak-empty-state loading></ak-empty-state>`;
}
if (this.dcrConfig === null) {
return html`<div class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-empty-state icon="fa-plug">
<span>${msg("Dynamic Client Registration is not enabled.")}</span>
<p slot="body">
${msg(
"Allow OAuth2/OIDC clients to register themselves against this provider (RFC 7591).",
)}
</p>
<div slot="primary">
<button
class="pf-c-button pf-m-primary"
${modalInvoker(OAuth2DCRForm, {
providerID: this.provider?.pk || 0,
})}
>
${msg("Enable Dynamic Client Registration")}
</button>
</div>
</ak-empty-state>
</div>
</div>
</div>`;
}
const dcr = this.dcrConfig;
return html`<div
class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"
>
<div class="pf-c-card pf-l-grid__item pf-m-4-col">
<div class="pf-c-card__title">${msg("Dynamic Client Registration")}</div>
<div class="pf-c-card__body">
${renderDescriptionList([
[
msg("Default application group"),
html`${dcr.defaultApplicationGroup !== ""
? dcr.defaultApplicationGroup
: "-"}`,
],
[
msg("Allowed grant types"),
html`${(dcr.allowedGrantTypes || []).length > 0
? dcr.allowedGrantTypes?.join(", ")
: msg("All")}`,
],
[
msg("Related actions"),
html`<button
class="pf-c-button pf-m-primary pf-m-block"
${modalInvoker(OAuth2DCRForm, {
instancePk: dcr.pbmUuid,
})}
>
${msg("Edit")}
</button>`,
],
])}
</div>
</div>
<div class="pf-c-card pf-l-grid__item pf-m-8-col">
<div class="pf-c-card__title">${msg("Dynamic application policies")}</div>
<ak-bound-policies-list
target=${this.dcrConfig.pbmUuid}
.policyEngineMode=${this.dcrConfig.policyEngineMode}
>
<span slot="description">
${msg(
"Bindings configured here will be copied to dynamically registered applications. If no bindings are created, bindings of this providers' application are copied.",
)}
</span>
</ak-bound-policies-list>
</div>
</div>`;
}
}
declare global {

View File

@@ -0,0 +1,33 @@
import { MessageFormatter } from "#common/ui/locale/format";
import { CheckboxItem } from "#elements/ak-checkbox-group/ak-checkbox-group";
import { GrantTypeEnum } from "@goauthentik/api";
import { msg } from "@lit/localize";
export const GrantTypeLabelRecord: Record<GrantTypeEnum, MessageFormatter<string>> = {
[GrantTypeEnum.AuthorizationCode]: () => msg("Authorization Code"),
[GrantTypeEnum.Implicit]: () => msg("Implicit"),
[GrantTypeEnum.Hybrid]: () => msg("Hybrid"),
[GrantTypeEnum.RefreshToken]: () => msg("Refresh token"),
[GrantTypeEnum.ClientCredentials]: () => msg("Client credentials"),
[GrantTypeEnum.Password]: () => msg("Password"),
[GrantTypeEnum.UrnIetfParamsOauthGrantTypeDeviceCode]: () => msg("Device-code"),
[GrantTypeEnum.UrnIetfParamsOauthGrantTypeTokenExchange]: () => msg("Token exchange"),
[GrantTypeEnum.UnknownDefaultOpenApi]: () => msg("Unknown Grant type"),
};
export const GrantTypeCheckboxItems: CheckboxItem<GrantTypeEnum>[] = [
GrantTypeEnum.AuthorizationCode,
GrantTypeEnum.Implicit,
GrantTypeEnum.Hybrid,
GrantTypeEnum.RefreshToken,
GrantTypeEnum.ClientCredentials,
GrantTypeEnum.Password,
GrantTypeEnum.UrnIetfParamsOauthGrantTypeDeviceCode,
GrantTypeEnum.UrnIetfParamsOauthGrantTypeTokenExchange,
].map((grantType) => ({
name: grantType,
label: GrantTypeLabelRecord[grantType](),
}));

View File

@@ -12,11 +12,12 @@ import { map } from "lit/directives/map.js";
import PFCheck from "@patternfly/patternfly/components/Check/check.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
type CheckboxKv = { name: string; label: string | TemplateResult };
type CheckboxPr = [string, string | TemplateResult];
export type CheckboxPair = CheckboxKv | CheckboxPr;
export type CheckboxItem<T extends string = string> = { name: T; label: string | TemplateResult };
export type CheckboxPair<T extends string = string> = [name: T, label: string | TemplateResult];
function* kvToPairs(items: Iterable<CheckboxPair>): Iterable<CheckboxPr> {
export type CheckboxItemInit<T extends string = string> = CheckboxItem<T> | CheckboxPair<T>;
function* generateCheckboxKeyValuePairs(items: Iterable<CheckboxItemInit>): Iterable<CheckboxPair> {
for (const item of items) {
yield Array.isArray(item) ? item : [item.name, item.label];
}
@@ -84,7 +85,7 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
}
@property({ type: Array })
public options: CheckboxPair[] = [];
public options: CheckboxItemInit[] = [];
@property({ type: Array })
public value: string[] = [];
@@ -180,7 +181,7 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
});
}
protected renderCheckbox = ([name, label]: CheckboxPr): SlottedTemplateResult => {
protected renderCheckbox = ([name, label]: CheckboxPair): SlottedTemplateResult => {
const selected = this.values.includes(name);
const blockFwd = (e: Event) => {
e.stopImmediatePropagation();
@@ -208,7 +209,7 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
protected override render(): SlottedTemplateResult {
return html`<div part="checkbox-group" class="pf-c-form__group-control pf-m-stack">
${map(kvToPairs(this.options), this.renderCheckbox)}
${map(generateCheckboxKeyValuePairs(this.options), this.renderCheckbox)}
</div>`;
}
}