Merge branch 'main' into multi-tenant-django-tenants
This commit is contained in:
commit
72fcaa92dd
|
@ -61,10 +61,6 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
postgresql_version: ${{ matrix.psql }}
|
||||
- name: checkout stable
|
||||
run: |
|
||||
# Delete all poetry envs
|
||||
|
@ -76,7 +72,7 @@ jobs:
|
|||
git checkout version/$(python -c "from authentik import __version__; print(__version__)")
|
||||
rm -rf .github/ scripts/
|
||||
mv ../.github ../scripts .
|
||||
- name: Setup authentik env (ensure stable deps are installed)
|
||||
- name: Setup authentik env (stable)
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
postgresql_version: ${{ matrix.psql }}
|
||||
|
@ -90,15 +86,20 @@ jobs:
|
|||
git clean -d -fx .
|
||||
git checkout $GITHUB_SHA
|
||||
# Delete previous poetry env
|
||||
rm -rf $(poetry env info --path)
|
||||
rm -rf /home/runner/.cache/pypoetry/virtualenvs/*
|
||||
- name: Setup authentik env (ensure latest deps are installed)
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
postgresql_version: ${{ matrix.psql }}
|
||||
- name: migrate to latest
|
||||
run: |
|
||||
poetry install
|
||||
poetry run python -m lifecycle.migrate
|
||||
- name: run tests
|
||||
env:
|
||||
# Test in the main database that we just migrated from the previous stable version
|
||||
AUTHENTIK_POSTGRESQL__TEST__NAME: authentik
|
||||
run: |
|
||||
poetry run make test
|
||||
test-unittest:
|
||||
name: test-unittest - PostgreSQL ${{ matrix.psql }}
|
||||
runs-on: ubuntu-latest
|
||||
|
|
5
Makefile
5
Makefile
|
@ -115,8 +115,9 @@ gen-diff: ## (Release) generate the changelog diff between the current schema a
|
|||
npx prettier --write diff.md
|
||||
|
||||
gen-clean:
|
||||
rm -rf web/api/src/
|
||||
rm -rf api/
|
||||
rm -rf gen-go-api/
|
||||
rm -rf gen-ts-api/
|
||||
rm -rf web/node_modules/@goauthentik/api/
|
||||
|
||||
gen-client-ts: ## Build and install the authentik API for Typescript into the authentik UI Application
|
||||
docker run \
|
||||
|
|
|
@ -12,6 +12,8 @@ from authentik.blueprints.tests import reconcile_app
|
|||
from authentik.core.models import Token, TokenIntents, User, UserTypes
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.models import Outpost
|
||||
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
|
||||
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider
|
||||
|
||||
|
@ -49,8 +51,12 @@ class TestAPIAuth(TestCase):
|
|||
with self.assertRaises(AuthenticationFailed):
|
||||
bearer_auth(f"Bearer {token.key}".encode())
|
||||
|
||||
def test_managed_outpost(self):
|
||||
@reconcile_app("authentik_outposts")
|
||||
def test_managed_outpost_fail(self):
|
||||
"""Test managed outpost"""
|
||||
outpost = Outpost.objects.filter(managed=MANAGED_OUTPOST).first()
|
||||
outpost.user.delete()
|
||||
outpost.delete()
|
||||
with self.assertRaises(AuthenticationFailed):
|
||||
bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from drf_spectacular.utils import extend_schema, inline_serializer
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField, DateTimeField, JSONField
|
||||
from rest_framework.fields import CharField, DateTimeField
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ListSerializer, ModelSerializer
|
||||
|
@ -15,7 +15,7 @@ from authentik.blueprints.v1.importer import Importer
|
|||
from authentik.blueprints.v1.oci import OCI_PREFIX
|
||||
from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.core.api.utils import JSONDictField, PassiveSerializer
|
||||
|
||||
|
||||
class ManagedSerializer:
|
||||
|
@ -28,7 +28,7 @@ class MetadataSerializer(PassiveSerializer):
|
|||
"""Serializer for blueprint metadata"""
|
||||
|
||||
name = CharField()
|
||||
labels = JSONField()
|
||||
labels = JSONDictField()
|
||||
|
||||
|
||||
class BlueprintInstanceSerializer(ModelSerializer):
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import BooleanField, JSONField
|
||||
from rest_framework.fields import BooleanField
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.blueprints.v1.meta.registry import BaseMetaModel, MetaResult, registry
|
||||
from authentik.core.api.utils import PassiveSerializer, is_dict
|
||||
from authentik.core.api.utils import JSONDictField, PassiveSerializer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from authentik.blueprints.models import BlueprintInstance
|
||||
|
@ -17,7 +17,7 @@ LOGGER = get_logger()
|
|||
class ApplyBlueprintMetaSerializer(PassiveSerializer):
|
||||
"""Serializer for meta apply blueprint model"""
|
||||
|
||||
identifiers = JSONField(validators=[is_dict])
|
||||
identifiers = JSONDictField()
|
||||
required = BooleanField(default=True)
|
||||
|
||||
# We cannot override `instance` as that will confuse rest_framework
|
||||
|
|
|
@ -8,7 +8,7 @@ from django_filters.filterset import FilterSet
|
|||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, IntegerField, JSONField
|
||||
from rest_framework.fields import CharField, IntegerField
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ListSerializer, ModelSerializer, ValidationError
|
||||
|
@ -16,7 +16,7 @@ from rest_framework.viewsets import ModelViewSet
|
|||
|
||||
from authentik.api.decorators import permission_required
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer, is_dict
|
||||
from authentik.core.api.utils import JSONDictField, PassiveSerializer
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.rbac.api.roles import RoleSerializer
|
||||
|
||||
|
@ -24,7 +24,7 @@ from authentik.rbac.api.roles import RoleSerializer
|
|||
class GroupMemberSerializer(ModelSerializer):
|
||||
"""Stripped down user serializer to show relevant users for groups"""
|
||||
|
||||
attributes = JSONField(validators=[is_dict], required=False)
|
||||
attributes = JSONDictField(required=False)
|
||||
uid = CharField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -44,7 +44,7 @@ class GroupMemberSerializer(ModelSerializer):
|
|||
class GroupSerializer(ModelSerializer):
|
||||
"""Group Serializer"""
|
||||
|
||||
attributes = JSONField(validators=[is_dict], required=False)
|
||||
attributes = JSONDictField(required=False)
|
||||
users_obj = ListSerializer(
|
||||
child=GroupMemberSerializer(), read_only=True, source="users", required=False
|
||||
)
|
||||
|
|
|
@ -32,13 +32,7 @@ from drf_spectacular.utils import (
|
|||
)
|
||||
from guardian.shortcuts import get_anonymous_user, get_objects_for_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import (
|
||||
CharField,
|
||||
IntegerField,
|
||||
JSONField,
|
||||
ListField,
|
||||
SerializerMethodField,
|
||||
)
|
||||
from rest_framework.fields import CharField, IntegerField, ListField, SerializerMethodField
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import (
|
||||
|
@ -58,7 +52,7 @@ from authentik.api.decorators import permission_required
|
|||
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict
|
||||
from authentik.core.api.utils import JSONDictField, LinkSerializer, PassiveSerializer
|
||||
from authentik.core.middleware import (
|
||||
SESSION_KEY_IMPERSONATE_ORIGINAL_USER,
|
||||
SESSION_KEY_IMPERSONATE_USER,
|
||||
|
@ -88,7 +82,7 @@ LOGGER = get_logger()
|
|||
class UserGroupSerializer(ModelSerializer):
|
||||
"""Simplified Group Serializer for user's groups"""
|
||||
|
||||
attributes = JSONField(required=False)
|
||||
attributes = JSONDictField(required=False)
|
||||
parent_name = CharField(source="parent.name", read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -109,7 +103,7 @@ class UserSerializer(ModelSerializer):
|
|||
|
||||
is_superuser = BooleanField(read_only=True)
|
||||
avatar = CharField(read_only=True)
|
||||
attributes = JSONField(validators=[is_dict], required=False)
|
||||
attributes = JSONDictField(required=False)
|
||||
groups = PrimaryKeyRelatedField(
|
||||
allow_empty=True, many=True, source="ak_groups", queryset=Group.objects.all(), default=list
|
||||
)
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
from typing import Any
|
||||
|
||||
from django.db.models import Model
|
||||
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
|
||||
from drf_spectacular.plumbing import build_basic_type
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from rest_framework.fields import CharField, IntegerField, JSONField
|
||||
from rest_framework.serializers import Serializer, SerializerMethodField, ValidationError
|
||||
|
||||
|
@ -13,6 +16,21 @@ def is_dict(value: Any):
|
|||
raise ValidationError("Value must be a dictionary, and not have any duplicate keys.")
|
||||
|
||||
|
||||
class JSONDictField(JSONField):
|
||||
"""JSON Field which only allows dictionaries"""
|
||||
|
||||
default_validators = [is_dict]
|
||||
|
||||
|
||||
class JSONExtension(OpenApiSerializerFieldExtension):
|
||||
"""Generate API Schema for JSON fields as"""
|
||||
|
||||
target_class = "authentik.core.api.utils.JSONDictField"
|
||||
|
||||
def map_serializer_field(self, auto_schema, direction):
|
||||
return build_basic_type(OpenApiTypes.OBJECT)
|
||||
|
||||
|
||||
class PassiveSerializer(Serializer):
|
||||
"""Base serializer class which doesn't implement create/update methods"""
|
||||
|
||||
|
@ -26,7 +44,7 @@ class PassiveSerializer(Serializer):
|
|||
class PropertyMappingPreviewSerializer(PassiveSerializer):
|
||||
"""Preview how the current user is mapped via the property mappings selected in a provider"""
|
||||
|
||||
preview = JSONField(read_only=True)
|
||||
preview = JSONDictField(read_only=True)
|
||||
|
||||
|
||||
class MetaNameSerializer(PassiveSerializer):
|
||||
|
|
|
@ -30,7 +30,6 @@ from authentik.lib.models import (
|
|||
DomainlessFormattedURLValidator,
|
||||
SerializerModel,
|
||||
)
|
||||
from authentik.lib.utils.http import get_client_ip
|
||||
from authentik.policies.models import PolicyBindingModel
|
||||
from authentik.root.install_id import get_install_id
|
||||
|
||||
|
@ -748,12 +747,14 @@ class AuthenticatedSession(ExpiringModel):
|
|||
@staticmethod
|
||||
def from_request(request: HttpRequest, user: User) -> Optional["AuthenticatedSession"]:
|
||||
"""Create a new session from a http request"""
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
|
||||
if not hasattr(request, "session") or not request.session.session_key:
|
||||
return None
|
||||
return AuthenticatedSession(
|
||||
session_key=request.session.session_key,
|
||||
user=user,
|
||||
last_ip=get_client_ip(request),
|
||||
last_ip=ClientIPMiddleware.get_client_ip(request),
|
||||
last_user_agent=request.META.get("HTTP_USER_AGENT", ""),
|
||||
expires=request.session.get_expiry_date(),
|
||||
)
|
||||
|
|
|
@ -44,28 +44,14 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="pf-c-background-image">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
|
||||
<filter id="image_overlay">
|
||||
<feColorMatrix in="SourceGraphic" type="matrix" values="1.3 0 0 0 0 0 1.3 0 0 0 0 0 1.3 0 0 0 0 0 1 0" />
|
||||
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
|
||||
<feFuncR type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncR>
|
||||
<feFuncG type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncG>
|
||||
<feFuncB type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncB>
|
||||
<feFuncA type="table" tableValues="0 1"></feFuncA>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</svg>
|
||||
</div>
|
||||
<ak-message-container></ak-message-container>
|
||||
<div class="pf-c-login">
|
||||
<div class="pf-c-login stacked">
|
||||
<div class="ak-login-container">
|
||||
<header class="pf-c-login__header">
|
||||
<div class="pf-c-brand ak-brand">
|
||||
<main class="pf-c-login__main">
|
||||
<div class="pf-c-login__main-header pf-c-brand ak-brand">
|
||||
<img src="{{ brand.branding_logo }}" alt="authentik Logo" />
|
||||
</div>
|
||||
</header>
|
||||
{% block main_container %}
|
||||
<main class="pf-c-login__main">
|
||||
<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">
|
||||
{% block card_title %}
|
||||
|
@ -77,7 +63,6 @@
|
|||
{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
<footer class="pf-c-login__footer">
|
||||
<ul class="pf-c-list pf-m-inline">
|
||||
{% for link in footer_links %}
|
||||
|
|
|
@ -38,9 +38,10 @@ from authentik.events.utils import (
|
|||
)
|
||||
from authentik.lib.models import DomainlessURLValidator, SerializerModel
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.lib.utils.http import get_client_ip, get_http_session
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.policies.models import PolicyBindingModel
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
from authentik.stages.email.utils import TemplateEmailMessage
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
|
@ -247,7 +248,7 @@ class Event(SerializerModel, ExpiringModel):
|
|||
self.user = get_user(request.session[SESSION_KEY_IMPERSONATE_ORIGINAL_USER])
|
||||
self.user["on_behalf_of"] = get_user(request.session[SESSION_KEY_IMPERSONATE_USER])
|
||||
# User 255.255.255.255 as fallback if IP cannot be determined
|
||||
self.client_ip = get_client_ip(request)
|
||||
self.client_ip = ClientIPMiddleware.get_client_ip(request)
|
||||
# Apply GeoIP Data, when enabled
|
||||
self.with_geoip()
|
||||
# If there's no app set, we get it from the requests too
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Generated by Django 4.2.6 on 2023-10-28 14:24
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
|
@ -31,4 +31,19 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.RunPython(set_oobe_flow_authentication),
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="authentication",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("none", "None"),
|
||||
("require_authenticated", "Require Authenticated"),
|
||||
("require_unauthenticated", "Require Unauthenticated"),
|
||||
("require_superuser", "Require Superuser"),
|
||||
("require_outpost", "Require Outpost"),
|
||||
],
|
||||
default="none",
|
||||
help_text="Required level of authentication and authorization to access a flow.",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -31,6 +31,7 @@ class FlowAuthenticationRequirement(models.TextChoices):
|
|||
REQUIRE_AUTHENTICATED = "require_authenticated"
|
||||
REQUIRE_UNAUTHENTICATED = "require_unauthenticated"
|
||||
REQUIRE_SUPERUSER = "require_superuser"
|
||||
REQUIRE_OUTPOST = "require_outpost"
|
||||
|
||||
|
||||
class NotConfiguredAction(models.TextChoices):
|
||||
|
|
|
@ -23,6 +23,7 @@ from authentik.flows.models import (
|
|||
)
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.policies.engine import PolicyEngine
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
|
||||
LOGGER = get_logger()
|
||||
PLAN_CONTEXT_PENDING_USER = "pending_user"
|
||||
|
@ -141,6 +142,10 @@ class FlowPlanner:
|
|||
and not request.user.is_superuser
|
||||
):
|
||||
raise FlowNonApplicableException()
|
||||
if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST:
|
||||
outpost_user = ClientIPMiddleware.get_outpost_user(request)
|
||||
if not outpost_user:
|
||||
raise FlowNonApplicableException()
|
||||
|
||||
def plan(
|
||||
self, request: HttpRequest, default_context: Optional[dict[str, Any]] = None
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.test import RequestFactory, TestCase
|
|||
from django.urls import reverse
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
|
||||
from authentik.blueprints.tests import reconcile_app
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
|
||||
|
@ -15,9 +16,12 @@ from authentik.flows.markers import ReevaluateMarker, StageMarker
|
|||
from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation, FlowStageBinding
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
|
||||
from authentik.lib.tests.utils import dummy_get_response
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.models import Outpost
|
||||
from authentik.policies.dummy.models import DummyPolicy
|
||||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.policies.types import PolicyResult
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
from authentik.stages.dummy.models import DummyStage
|
||||
|
||||
POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False))
|
||||
|
@ -68,6 +72,34 @@ class TestFlowPlanner(TestCase):
|
|||
planner.allow_empty_flows = True
|
||||
planner.plan(request)
|
||||
|
||||
@reconcile_app("authentik_outposts")
|
||||
def test_authentication_outpost(self):
|
||||
"""Test flow authentication (outpost)"""
|
||||
flow = create_test_flow()
|
||||
flow.authentication = FlowAuthenticationRequirement.REQUIRE_OUTPOST
|
||||
request = self.request_factory.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||
)
|
||||
request.user = AnonymousUser()
|
||||
with self.assertRaises(FlowNonApplicableException):
|
||||
planner = FlowPlanner(flow)
|
||||
planner.allow_empty_flows = True
|
||||
planner.plan(request)
|
||||
|
||||
outpost = Outpost.objects.filter(managed=MANAGED_OUTPOST).first()
|
||||
request = self.request_factory.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||
HTTP_X_AUTHENTIK_OUTPOST_TOKEN=outpost.token.key,
|
||||
HTTP_X_AUTHENTIK_REMOTE_IP="1.2.3.4",
|
||||
)
|
||||
request.user = AnonymousUser()
|
||||
middleware = ClientIPMiddleware(dummy_get_response)
|
||||
middleware(request)
|
||||
|
||||
planner = FlowPlanner(flow)
|
||||
planner.allow_empty_flows = True
|
||||
planner.plan(request)
|
||||
|
||||
@patch(
|
||||
"authentik.policies.engine.PolicyEngine.result",
|
||||
POLICY_RETURN_FALSE,
|
||||
|
|
|
@ -8,6 +8,8 @@ postgresql:
|
|||
password: "env://POSTGRES_PASSWORD"
|
||||
use_pgbouncer: false
|
||||
use_pgpool: false
|
||||
test:
|
||||
name: test_authentik
|
||||
|
||||
listen:
|
||||
listen_http: 0.0.0.0:9000
|
||||
|
|
|
@ -3,8 +3,8 @@ from django.test import RequestFactory, TestCase
|
|||
|
||||
from authentik.core.models import Token, TokenIntents, UserTypes
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.utils.http import OUTPOST_REMOTE_IP_HEADER, OUTPOST_TOKEN_HEADER, get_client_ip
|
||||
from authentik.lib.views import bad_request_message
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
|
||||
|
||||
class TestHTTP(TestCase):
|
||||
|
@ -22,12 +22,12 @@ class TestHTTP(TestCase):
|
|||
def test_normal(self):
|
||||
"""Test normal request"""
|
||||
request = self.factory.get("/")
|
||||
self.assertEqual(get_client_ip(request), "127.0.0.1")
|
||||
self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.1")
|
||||
|
||||
def test_forward_for(self):
|
||||
"""Test x-forwarded-for request"""
|
||||
request = self.factory.get("/", HTTP_X_FORWARDED_FOR="127.0.0.2")
|
||||
self.assertEqual(get_client_ip(request), "127.0.0.2")
|
||||
self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.2")
|
||||
|
||||
def test_fake_outpost(self):
|
||||
"""Test faked IP which is overridden by an outpost"""
|
||||
|
@ -38,28 +38,28 @@ class TestHTTP(TestCase):
|
|||
request = self.factory.get(
|
||||
"/",
|
||||
**{
|
||||
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
|
||||
OUTPOST_TOKEN_HEADER: "abc",
|
||||
ClientIPMiddleware.outpost_remote_ip_header: "1.2.3.4",
|
||||
ClientIPMiddleware.outpost_token_header: "abc",
|
||||
},
|
||||
)
|
||||
self.assertEqual(get_client_ip(request), "127.0.0.1")
|
||||
self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.1")
|
||||
# Invalid, user doesn't have permissions
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
**{
|
||||
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
|
||||
OUTPOST_TOKEN_HEADER: token.key,
|
||||
ClientIPMiddleware.outpost_remote_ip_header: "1.2.3.4",
|
||||
ClientIPMiddleware.outpost_token_header: token.key,
|
||||
},
|
||||
)
|
||||
self.assertEqual(get_client_ip(request), "127.0.0.1")
|
||||
self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.1")
|
||||
# Valid
|
||||
self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||
self.user.save()
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
**{
|
||||
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
|
||||
OUTPOST_TOKEN_HEADER: token.key,
|
||||
ClientIPMiddleware.outpost_remote_ip_header: "1.2.3.4",
|
||||
ClientIPMiddleware.outpost_token_header: token.key,
|
||||
},
|
||||
)
|
||||
self.assertEqual(get_client_ip(request), "1.2.3.4")
|
||||
self.assertEqual(ClientIPMiddleware.get_client_ip(request), "1.2.3.4")
|
||||
|
|
|
@ -1,82 +1,12 @@
|
|||
"""http helpers"""
|
||||
from typing import Any, Optional
|
||||
|
||||
from django.http import HttpRequest
|
||||
from requests.sessions import Session
|
||||
from sentry_sdk.hub import Hub
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik import get_full_version
|
||||
|
||||
OUTPOST_REMOTE_IP_HEADER = "HTTP_X_AUTHENTIK_REMOTE_IP"
|
||||
OUTPOST_TOKEN_HEADER = "HTTP_X_AUTHENTIK_OUTPOST_TOKEN" # nosec
|
||||
DEFAULT_IP = "255.255.255.255"
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
def _get_client_ip_from_meta(meta: dict[str, Any]) -> str:
|
||||
"""Attempt to get the client's IP by checking common HTTP Headers.
|
||||
Returns none if no IP Could be found
|
||||
|
||||
No additional validation is done here as requests are expected to only arrive here
|
||||
via the go proxy, which deals with validating these headers for us"""
|
||||
headers = (
|
||||
"HTTP_X_FORWARDED_FOR",
|
||||
"REMOTE_ADDR",
|
||||
)
|
||||
for _header in headers:
|
||||
if _header in meta:
|
||||
ips: list[str] = meta.get(_header).split(",")
|
||||
return ips[0].strip()
|
||||
return DEFAULT_IP
|
||||
|
||||
|
||||
def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
|
||||
"""Get the actual remote IP when set by an outpost. Only
|
||||
allowed when the request is authenticated, by an outpost internal service account"""
|
||||
from authentik.core.models import Token, TokenIntents, UserTypes
|
||||
|
||||
if OUTPOST_REMOTE_IP_HEADER not in request.META or OUTPOST_TOKEN_HEADER not in request.META:
|
||||
return None
|
||||
fake_ip = request.META[OUTPOST_REMOTE_IP_HEADER]
|
||||
token = (
|
||||
Token.filter_not_expired(
|
||||
key=request.META.get(OUTPOST_TOKEN_HEADER), intent=TokenIntents.INTENT_API
|
||||
)
|
||||
.select_related("user")
|
||||
.first()
|
||||
)
|
||||
if not token:
|
||||
LOGGER.warning("Attempted remote-ip override without token", fake_ip=fake_ip)
|
||||
return None
|
||||
user = token.user
|
||||
if user.type != UserTypes.INTERNAL_SERVICE_ACCOUNT:
|
||||
LOGGER.warning(
|
||||
"Remote-IP override: user doesn't have permission",
|
||||
user=user,
|
||||
fake_ip=fake_ip,
|
||||
)
|
||||
return None
|
||||
# Update sentry scope to include correct IP
|
||||
user = Hub.current.scope._user
|
||||
if not user:
|
||||
user = {}
|
||||
user["ip_address"] = fake_ip
|
||||
Hub.current.scope.set_user(user)
|
||||
return fake_ip
|
||||
|
||||
|
||||
def get_client_ip(request: Optional[HttpRequest]) -> str:
|
||||
"""Attempt to get the client's IP by checking common HTTP Headers.
|
||||
Returns none if no IP Could be found"""
|
||||
if not request:
|
||||
return DEFAULT_IP
|
||||
override = _get_outpost_override_ip(request)
|
||||
if override:
|
||||
return override
|
||||
return _get_client_ip_from_meta(request.META)
|
||||
|
||||
|
||||
def authentik_user_agent() -> str:
|
||||
"""Get a common user agent"""
|
||||
return f"authentik@{get_full_version()}"
|
||||
|
|
|
@ -9,13 +9,13 @@ from rest_framework.fields import BooleanField, CharField, DateTimeField
|
|||
from rest_framework.relations import PrimaryKeyRelatedField
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import JSONField, ModelSerializer, ValidationError
|
||||
from rest_framework.serializers import ModelSerializer, ValidationError
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik import get_build_hash
|
||||
from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer, is_dict
|
||||
from authentik.core.api.utils import JSONDictField, PassiveSerializer
|
||||
from authentik.core.models import Provider
|
||||
from authentik.outposts.api.service_connections import ServiceConnectionSerializer
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
|
@ -34,7 +34,7 @@ from authentik.providers.radius.models import RadiusProvider
|
|||
class OutpostSerializer(ModelSerializer):
|
||||
"""Outpost Serializer"""
|
||||
|
||||
config = JSONField(validators=[is_dict], source="_config")
|
||||
config = JSONDictField(source="_config")
|
||||
# Need to set allow_empty=True for the embedded outpost with no providers
|
||||
# is checked for other providers in the API Viewset
|
||||
providers = PrimaryKeyRelatedField(
|
||||
|
@ -95,7 +95,7 @@ class OutpostSerializer(ModelSerializer):
|
|||
class OutpostDefaultConfigSerializer(PassiveSerializer):
|
||||
"""Global default outpost config"""
|
||||
|
||||
config = JSONField(read_only=True)
|
||||
config = JSONDictField(read_only=True)
|
||||
|
||||
|
||||
class OutpostHealthSerializer(PassiveSerializer):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"""Serializer for policy execution"""
|
||||
from rest_framework.fields import BooleanField, CharField, DictField, JSONField, ListField
|
||||
from rest_framework.fields import BooleanField, CharField, DictField, ListField
|
||||
from rest_framework.relations import PrimaryKeyRelatedField
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer, is_dict
|
||||
from authentik.core.api.utils import JSONDictField, PassiveSerializer
|
||||
from authentik.core.models import User
|
||||
|
||||
|
||||
|
@ -10,7 +10,7 @@ class PolicyTestSerializer(PassiveSerializer):
|
|||
"""Test policy execution for a user with context"""
|
||||
|
||||
user = PrimaryKeyRelatedField(queryset=User.objects.all())
|
||||
context = JSONField(required=False, validators=[is_dict])
|
||||
context = JSONDictField(required=False)
|
||||
|
||||
|
||||
class PolicyTestResultSerializer(PassiveSerializer):
|
||||
|
|
|
@ -7,9 +7,9 @@ from structlog.stdlib import get_logger
|
|||
|
||||
from authentik.flows.planner import PLAN_CONTEXT_SSO
|
||||
from authentik.lib.expression.evaluator import BaseEvaluator
|
||||
from authentik.lib.utils.http import get_client_ip
|
||||
from authentik.policies.exceptions import PolicyException
|
||||
from authentik.policies.types import PolicyRequest, PolicyResult
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
|
||||
LOGGER = get_logger()
|
||||
if TYPE_CHECKING:
|
||||
|
@ -49,7 +49,7 @@ class PolicyEvaluator(BaseEvaluator):
|
|||
"""Update context based on http request"""
|
||||
# update website/docs/expressions/_objects.md
|
||||
# update website/docs/expressions/_functions.md
|
||||
self._context["ak_client_ip"] = ip_address(get_client_ip(request))
|
||||
self._context["ak_client_ip"] = ip_address(ClientIPMiddleware.get_client_ip(request))
|
||||
self._context["http_request"] = request
|
||||
|
||||
def handle_error(self, exc: Exception, expression_source: str):
|
||||
|
|
|
@ -13,9 +13,9 @@ from structlog import get_logger
|
|||
from authentik.core.models import ExpiringModel
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.models import SerializerModel
|
||||
from authentik.lib.utils.http import get_client_ip
|
||||
from authentik.policies.models import Policy
|
||||
from authentik.policies.types import PolicyRequest, PolicyResult
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
|
||||
LOGGER = get_logger()
|
||||
CACHE_KEY_PREFIX = "goauthentik.io/policies/reputation/scores/"
|
||||
|
@ -44,7 +44,7 @@ class ReputationPolicy(Policy):
|
|||
return "ak-policy-reputation-form"
|
||||
|
||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||
remote_ip = get_client_ip(request.http_request)
|
||||
remote_ip = ClientIPMiddleware.get_client_ip(request.http_request)
|
||||
query = Q()
|
||||
if self.check_ip:
|
||||
query |= Q(ip=remote_ip)
|
||||
|
|
|
@ -7,9 +7,9 @@ from structlog.stdlib import get_logger
|
|||
|
||||
from authentik.core.signals import login_failed
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.utils.http import get_client_ip
|
||||
from authentik.policies.reputation.models import CACHE_KEY_PREFIX
|
||||
from authentik.policies.reputation.tasks import save_reputation
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
from authentik.stages.identification.signals import identification_failed
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
@ -18,7 +18,7 @@ CACHE_TIMEOUT = CONFIG.get_int("cache.timeout_reputation")
|
|||
|
||||
def update_score(request: HttpRequest, identifier: str, amount: int):
|
||||
"""Update score for IP and User"""
|
||||
remote_ip = get_client_ip(request)
|
||||
remote_ip = ClientIPMiddleware.get_client_ip(request)
|
||||
|
||||
try:
|
||||
# We only update the cache here, as its faster than writing to the DB
|
||||
|
|
|
@ -9,7 +9,6 @@ from django.http import HttpRequest
|
|||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.events.geo import GEOIP_READER
|
||||
from authentik.lib.utils.http import get_client_ip
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from authentik.core.models import User
|
||||
|
@ -38,10 +37,12 @@ class PolicyRequest:
|
|||
|
||||
def set_http_request(self, request: HttpRequest): # pragma: no cover
|
||||
"""Load data from HTTP request, including geoip when enabled"""
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
|
||||
self.http_request = request
|
||||
if not GEOIP_READER.enabled:
|
||||
return
|
||||
client_ip = get_client_ip(request)
|
||||
client_ip = ClientIPMiddleware.get_client_ip(request)
|
||||
if not client_ip:
|
||||
return
|
||||
self.context["geoip"] = GEOIP_READER.city(client_ip)
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
"""SCIM constants"""
|
||||
PAGE_SIZE = 100
|
||||
PAGE_TIMEOUT = 60 * 60 * 0.5 # Half an hour
|
||||
|
|
|
@ -12,7 +12,7 @@ from structlog.stdlib import get_logger
|
|||
from authentik.core.models import Group, User
|
||||
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
|
||||
from authentik.lib.utils.reflection import path_to_class
|
||||
from authentik.providers.scim.clients import PAGE_SIZE
|
||||
from authentik.providers.scim.clients import PAGE_SIZE, PAGE_TIMEOUT
|
||||
from authentik.providers.scim.clients.base import SCIMClient
|
||||
from authentik.providers.scim.clients.exceptions import SCIMRequestException, StopSync
|
||||
from authentik.providers.scim.clients.group import SCIMGroupClient
|
||||
|
@ -53,6 +53,9 @@ def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
|
|||
LOGGER.debug("Starting SCIM sync")
|
||||
users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)
|
||||
groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)
|
||||
self.soft_time_limit = self.time_limit = (
|
||||
users_paginator.count + groups_paginator.count
|
||||
) * PAGE_TIMEOUT
|
||||
with allow_join_result():
|
||||
try:
|
||||
for page in users_paginator.page_range:
|
||||
|
@ -69,7 +72,10 @@ def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
|
|||
self.set_status(result)
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
@CELERY_APP.task(
|
||||
soft_time_limit=PAGE_TIMEOUT,
|
||||
task_time_limit=PAGE_TIMEOUT,
|
||||
)
|
||||
def scim_sync_users(page: int, provider_pk: int):
|
||||
"""Sync single or multiple users to SCIM"""
|
||||
messages = []
|
||||
|
|
|
@ -93,7 +93,7 @@ class SCIMMembershipTests(TestCase):
|
|||
"emails": [],
|
||||
"active": True,
|
||||
"externalId": user.uid,
|
||||
"name": {"familyName": "", "formatted": "", "givenName": ""},
|
||||
"name": {"familyName": " ", "formatted": " ", "givenName": ""},
|
||||
"displayName": "",
|
||||
"userName": user.username,
|
||||
},
|
||||
|
@ -184,7 +184,7 @@ class SCIMMembershipTests(TestCase):
|
|||
"displayName": "",
|
||||
"emails": [],
|
||||
"externalId": user.uid,
|
||||
"name": {"familyName": "", "formatted": "", "givenName": ""},
|
||||
"name": {"familyName": " ", "formatted": " ", "givenName": ""},
|
||||
"userName": user.username,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -61,7 +61,7 @@ class SCIMUserTests(TestCase):
|
|||
uid = generate_id()
|
||||
user = User.objects.create(
|
||||
username=uid,
|
||||
name=uid,
|
||||
name=f"{uid} {uid}",
|
||||
email=f"{uid}@goauthentik.io",
|
||||
)
|
||||
self.assertEqual(mock.call_count, 2)
|
||||
|
@ -81,11 +81,11 @@ class SCIMUserTests(TestCase):
|
|||
],
|
||||
"externalId": user.uid,
|
||||
"name": {
|
||||
"familyName": "",
|
||||
"formatted": uid,
|
||||
"familyName": uid,
|
||||
"formatted": f"{uid} {uid}",
|
||||
"givenName": uid,
|
||||
},
|
||||
"displayName": uid,
|
||||
"displayName": f"{uid} {uid}",
|
||||
"userName": uid,
|
||||
},
|
||||
)
|
||||
|
@ -114,7 +114,7 @@ class SCIMUserTests(TestCase):
|
|||
uid = generate_id()
|
||||
user = User.objects.create(
|
||||
username=uid,
|
||||
name=uid,
|
||||
name=f"{uid} {uid}",
|
||||
email=f"{uid}@goauthentik.io",
|
||||
)
|
||||
self.assertEqual(mock.call_count, 2)
|
||||
|
@ -135,11 +135,11 @@ class SCIMUserTests(TestCase):
|
|||
"value": f"{uid}@goauthentik.io",
|
||||
}
|
||||
],
|
||||
"displayName": uid,
|
||||
"displayName": f"{uid} {uid}",
|
||||
"externalId": user.uid,
|
||||
"name": {
|
||||
"familyName": "",
|
||||
"formatted": uid,
|
||||
"familyName": uid,
|
||||
"formatted": f"{uid} {uid}",
|
||||
"givenName": uid,
|
||||
},
|
||||
"userName": uid,
|
||||
|
@ -170,7 +170,7 @@ class SCIMUserTests(TestCase):
|
|||
uid = generate_id()
|
||||
user = User.objects.create(
|
||||
username=uid,
|
||||
name=uid,
|
||||
name=f"{uid} {uid}",
|
||||
email=f"{uid}@goauthentik.io",
|
||||
)
|
||||
self.assertEqual(mock.call_count, 2)
|
||||
|
@ -190,11 +190,11 @@ class SCIMUserTests(TestCase):
|
|||
],
|
||||
"externalId": user.uid,
|
||||
"name": {
|
||||
"familyName": "",
|
||||
"formatted": uid,
|
||||
"familyName": uid,
|
||||
"formatted": f"{uid} {uid}",
|
||||
"givenName": uid,
|
||||
},
|
||||
"displayName": uid,
|
||||
"displayName": f"{uid} {uid}",
|
||||
"userName": uid,
|
||||
},
|
||||
)
|
||||
|
@ -234,7 +234,7 @@ class SCIMUserTests(TestCase):
|
|||
)
|
||||
user = User.objects.create(
|
||||
username=uid,
|
||||
name=uid,
|
||||
name=f"{uid} {uid}",
|
||||
email=f"{uid}@goauthentik.io",
|
||||
)
|
||||
|
||||
|
@ -258,11 +258,11 @@ class SCIMUserTests(TestCase):
|
|||
],
|
||||
"externalId": user.uid,
|
||||
"name": {
|
||||
"familyName": "",
|
||||
"formatted": uid,
|
||||
"familyName": uid,
|
||||
"formatted": f"{uid} {uid}",
|
||||
"givenName": uid,
|
||||
},
|
||||
"displayName": uid,
|
||||
"displayName": f"{uid} {uid}",
|
||||
"userName": uid,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from hashlib import sha512
|
||||
from time import time
|
||||
from timeit import default_timer
|
||||
from typing import Callable
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sessions.backends.base import UpdateError
|
||||
|
@ -15,9 +15,10 @@ from django.middleware.csrf import CsrfViewMiddleware as UpstreamCsrfViewMiddlew
|
|||
from django.utils.cache import patch_vary_headers
|
||||
from django.utils.http import http_date
|
||||
from jwt import PyJWTError, decode, encode
|
||||
from sentry_sdk.hub import Hub
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.utils.http import get_client_ip
|
||||
from authentik.core.models import Token, TokenIntents, User, UserTypes
|
||||
|
||||
LOGGER = get_logger("authentik.asgi")
|
||||
ACR_AUTHENTIK_SESSION = "goauthentik.io/core/default"
|
||||
|
@ -156,6 +157,111 @@ class CsrfViewMiddleware(UpstreamCsrfViewMiddleware):
|
|||
patch_vary_headers(response, ("Cookie",))
|
||||
|
||||
|
||||
class ClientIPMiddleware:
|
||||
"""Set a "known-good" client IP on the request, by default based off of x-forwarded-for
|
||||
which is set by the go proxy, but also allowing the remote IP to be overridden by an outpost
|
||||
for protocols like LDAP"""
|
||||
|
||||
get_response: Callable[[HttpRequest], HttpResponse]
|
||||
outpost_remote_ip_header = "HTTP_X_AUTHENTIK_REMOTE_IP"
|
||||
outpost_token_header = "HTTP_X_AUTHENTIK_OUTPOST_TOKEN" # nosec
|
||||
default_ip = "255.255.255.255"
|
||||
|
||||
request_attr_client_ip = "client_ip"
|
||||
request_attr_outpost_user = "outpost_user"
|
||||
|
||||
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
|
||||
self.get_response = get_response
|
||||
|
||||
def _get_client_ip_from_meta(self, meta: dict[str, Any]) -> str:
|
||||
"""Attempt to get the client's IP by checking common HTTP Headers.
|
||||
Returns none if no IP Could be found
|
||||
|
||||
No additional validation is done here as requests are expected to only arrive here
|
||||
via the go proxy, which deals with validating these headers for us"""
|
||||
headers = (
|
||||
"HTTP_X_FORWARDED_FOR",
|
||||
"REMOTE_ADDR",
|
||||
)
|
||||
for _header in headers:
|
||||
if _header in meta:
|
||||
ips: list[str] = meta.get(_header).split(",")
|
||||
return ips[0].strip()
|
||||
return self.default_ip
|
||||
|
||||
# FIXME: this should probably not be in `root` but rather in a middleware in `outposts`
|
||||
# but for now it's fine
|
||||
def _get_outpost_override_ip(self, request: HttpRequest) -> Optional[str]:
|
||||
"""Get the actual remote IP when set by an outpost. Only
|
||||
allowed when the request is authenticated, by an outpost internal service account"""
|
||||
if (
|
||||
self.outpost_remote_ip_header not in request.META
|
||||
or self.outpost_token_header not in request.META
|
||||
):
|
||||
return None
|
||||
delegated_ip = request.META[self.outpost_remote_ip_header]
|
||||
token = (
|
||||
Token.filter_not_expired(
|
||||
key=request.META.get(self.outpost_token_header), intent=TokenIntents.INTENT_API
|
||||
)
|
||||
.select_related("user")
|
||||
.first()
|
||||
)
|
||||
if not token:
|
||||
LOGGER.warning("Attempted remote-ip override without token", delegated_ip=delegated_ip)
|
||||
return None
|
||||
user: User = token.user
|
||||
if user.type != UserTypes.INTERNAL_SERVICE_ACCOUNT:
|
||||
LOGGER.warning(
|
||||
"Remote-IP override: user doesn't have permission",
|
||||
user=user,
|
||||
delegated_ip=delegated_ip,
|
||||
)
|
||||
return None
|
||||
# Update sentry scope to include correct IP
|
||||
user = Hub.current.scope._user
|
||||
if not user:
|
||||
user = {}
|
||||
user["ip_address"] = delegated_ip
|
||||
Hub.current.scope.set_user(user)
|
||||
# Set the outpost service account on the request
|
||||
setattr(request, self.request_attr_outpost_user, user)
|
||||
return delegated_ip
|
||||
|
||||
def _get_client_ip(self, request: Optional[HttpRequest]) -> str:
|
||||
"""Attempt to get the client's IP by checking common HTTP Headers.
|
||||
Returns none if no IP Could be found"""
|
||||
if not request:
|
||||
return self.default_ip
|
||||
override = self._get_outpost_override_ip(request)
|
||||
if override:
|
||||
return override
|
||||
return self._get_client_ip_from_meta(request.META)
|
||||
|
||||
@staticmethod
|
||||
def get_outpost_user(request: HttpRequest) -> Optional[User]:
|
||||
"""Get outpost user that authenticated this request"""
|
||||
return getattr(request, ClientIPMiddleware.request_attr_outpost_user, None)
|
||||
|
||||
@staticmethod
|
||||
def get_client_ip(request: HttpRequest) -> str:
|
||||
"""Get correct client IP, including any overrides from outposts that
|
||||
have the permission to do so"""
|
||||
if request and not hasattr(request, ClientIPMiddleware.request_attr_client_ip):
|
||||
ClientIPMiddleware(lambda request: request).set_ip(request)
|
||||
return getattr(
|
||||
request, ClientIPMiddleware.request_attr_client_ip, ClientIPMiddleware.default_ip
|
||||
)
|
||||
|
||||
def set_ip(self, request: HttpRequest):
|
||||
"""Set the IP"""
|
||||
setattr(request, self.request_attr_client_ip, self._get_client_ip(request))
|
||||
|
||||
def __call__(self, request: HttpRequest) -> HttpResponse:
|
||||
self.set_ip(request)
|
||||
return self.get_response(request)
|
||||
|
||||
|
||||
class ChannelsLoggingMiddleware:
|
||||
"""Logging middleware for channels"""
|
||||
|
||||
|
@ -201,7 +307,7 @@ class LoggingMiddleware:
|
|||
"""Log request"""
|
||||
LOGGER.info(
|
||||
request.get_full_path(),
|
||||
remote=get_client_ip(request),
|
||||
remote=ClientIPMiddleware.get_client_ip(request),
|
||||
method=request.method,
|
||||
scheme=request.scheme,
|
||||
status=status_code,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
"""
|
||||
Module for abstract serializer/unserializer base classes.
|
||||
"""
|
||||
import pickle # nosec
|
||||
|
||||
|
||||
class PickleSerializer:
|
||||
"""
|
||||
Simple wrapper around pickle to be used in signing.dumps()/loads() and
|
||||
cache backends.
|
||||
"""
|
||||
|
||||
def __init__(self, protocol=None):
|
||||
self.protocol = pickle.HIGHEST_PROTOCOL if protocol is None else protocol
|
||||
|
||||
def dumps(self, obj):
|
||||
"""Pickle data to be stored in redis"""
|
||||
return pickle.dumps(obj, self.protocol)
|
||||
|
||||
def loads(self, data):
|
||||
"""Unpickle data to be loaded from redis"""
|
||||
return pickle.loads(data) # nosec
|
|
@ -151,6 +151,7 @@ SPECTACULAR_SETTINGS = {
|
|||
"EventActions": "authentik.events.models.EventAction",
|
||||
"ChallengeChoices": "authentik.flows.challenge.ChallengeTypes",
|
||||
"FlowDesignationEnum": "authentik.flows.models.FlowDesignation",
|
||||
"FlowLayoutEnum": "authentik.flows.models.FlowLayout",
|
||||
"PolicyEngineMode": "authentik.policies.models.PolicyEngineMode",
|
||||
"ProxyMode": "authentik.providers.proxy.models.ProxyMode",
|
||||
"PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes",
|
||||
|
@ -219,7 +220,7 @@ DJANGO_REDIS_SCAN_ITERSIZE = 1000
|
|||
DJANGO_REDIS_IGNORE_EXCEPTIONS = True
|
||||
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_SERIALIZER = "django.contrib.sessions.serializers.PickleSerializer"
|
||||
SESSION_SERIALIZER = "authentik.root.sessions.pickle.PickleSerializer"
|
||||
SESSION_CACHE_ALIAS = "default"
|
||||
# Configured via custom SessionMiddleware
|
||||
# SESSION_COOKIE_SAMESITE = "None"
|
||||
|
@ -232,7 +233,7 @@ MIDDLEWARE = [
|
|||
"django_tenants.middleware.default.DefaultTenantMiddleware",
|
||||
"authentik.root.middleware.LoggingMiddleware",
|
||||
"django_prometheus.middleware.PrometheusBeforeMiddleware",
|
||||
"authentik.brands.middleware.BrandMiddleware",
|
||||
"authentik.root.middleware.ClientIPMiddleware",
|
||||
"authentik.root.middleware.SessionMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"authentik.core.middleware.RequestIDMiddleware",
|
||||
|
@ -295,6 +296,9 @@ DATABASES = {
|
|||
"SSLROOTCERT": CONFIG.get("postgresql.sslrootcert"),
|
||||
"SSLCERT": CONFIG.get("postgresql.sslcert"),
|
||||
"SSLKEY": CONFIG.get("postgresql.sslkey"),
|
||||
"TEST": {
|
||||
"NAME": CONFIG.get("postgresql.test.name"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,8 @@ from authentik.stages.authenticator.util import hex_validator, random_hex
|
|||
class TOTPDigits(models.TextChoices):
|
||||
"""OTP Time Digits"""
|
||||
|
||||
SIX = 6, _("6 digits, widely compatible")
|
||||
EIGHT = 8, _("8 digits, not compatible with apps like Google Authenticator")
|
||||
SIX = "6", _("6 digits, widely compatible")
|
||||
EIGHT = "8", _("8 digits, not compatible with apps like Google Authenticator")
|
||||
|
||||
|
||||
class AuthenticatorTOTPStage(ConfigurableStage, FriendlyNamedStage, Stage):
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.http.response import Http404
|
|||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import gettext as __
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.fields import CharField, JSONField
|
||||
from rest_framework.fields import CharField
|
||||
from rest_framework.serializers import ValidationError
|
||||
from structlog.stdlib import get_logger
|
||||
from webauthn.authentication.generate_authentication_options import generate_authentication_options
|
||||
|
@ -16,13 +16,13 @@ from webauthn.helpers.base64url_to_bytes import base64url_to_bytes
|
|||
from webauthn.helpers.exceptions import InvalidAuthenticationResponse
|
||||
from webauthn.helpers.structs import AuthenticationCredential
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.core.api.utils import JSONDictField, PassiveSerializer
|
||||
from authentik.core.models import Application, User
|
||||
from authentik.core.signals import login_failed
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.stage import StageView
|
||||
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE
|
||||
from authentik.lib.utils.http import get_client_ip
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
from authentik.stages.authenticator import match_token
|
||||
from authentik.stages.authenticator.models import Device
|
||||
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
|
||||
|
@ -40,7 +40,7 @@ class DeviceChallenge(PassiveSerializer):
|
|||
|
||||
device_class = CharField()
|
||||
device_uid = CharField()
|
||||
challenge = JSONField()
|
||||
challenge = JSONDictField()
|
||||
|
||||
|
||||
def get_challenge_for_device(
|
||||
|
@ -197,7 +197,7 @@ def validate_challenge_duo(device_pk: int, stage_view: StageView, user: User) ->
|
|||
response = stage.auth_client().auth(
|
||||
"auto",
|
||||
user_id=device.duo_user_id,
|
||||
ipaddr=get_client_ip(stage_view.request),
|
||||
ipaddr=ClientIPMiddleware.get_client_ip(stage_view.request),
|
||||
type=__(
|
||||
"%(brand_name)s Login request"
|
||||
% {
|
||||
|
|
|
@ -6,10 +6,10 @@ from typing import Optional
|
|||
from django.conf import settings
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from jwt import PyJWTError, decode, encode
|
||||
from rest_framework.fields import CharField, IntegerField, JSONField, ListField, UUIDField
|
||||
from rest_framework.fields import CharField, IntegerField, ListField, UUIDField
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.core.api.utils import JSONDictField, PassiveSerializer
|
||||
from authentik.core.models import User
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
|
||||
|
@ -68,7 +68,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
|||
selected_stage = CharField(required=False)
|
||||
|
||||
code = CharField(required=False)
|
||||
webauthn = JSONField(required=False)
|
||||
webauthn = JSONDictField(required=False)
|
||||
duo = IntegerField(required=False)
|
||||
component = CharField(default="ak-stage-authenticator-validate")
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""WebAuthn stage"""
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.request import QueryDict
|
||||
from rest_framework.fields import CharField, JSONField
|
||||
from rest_framework.fields import CharField
|
||||
from rest_framework.serializers import ValidationError
|
||||
from webauthn.helpers.bytes_to_base64url import bytes_to_base64url
|
||||
from webauthn.helpers.exceptions import InvalidRegistrationResponse
|
||||
|
@ -16,6 +16,7 @@ from webauthn.registration.verify_registration_response import (
|
|||
verify_registration_response,
|
||||
)
|
||||
|
||||
from authentik.core.api.utils import JSONDictField
|
||||
from authentik.core.models import User
|
||||
from authentik.flows.challenge import (
|
||||
Challenge,
|
||||
|
@ -33,14 +34,14 @@ SESSION_KEY_WEBAUTHN_CHALLENGE = "authentik/stages/authenticator_webauthn/challe
|
|||
class AuthenticatorWebAuthnChallenge(WithUserInfoChallenge):
|
||||
"""WebAuthn Challenge"""
|
||||
|
||||
registration = JSONField()
|
||||
registration = JSONDictField()
|
||||
component = CharField(default="ak-stage-authenticator-webauthn")
|
||||
|
||||
|
||||
class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
|
||||
"""WebAuthn Challenge response"""
|
||||
|
||||
response = JSONField()
|
||||
response = JSONDictField()
|
||||
component = CharField(default="ak-stage-authenticator-webauthn")
|
||||
|
||||
request: HttpRequest
|
||||
|
|
|
@ -12,7 +12,8 @@ from authentik.flows.challenge import (
|
|||
WithUserInfoChallenge,
|
||||
)
|
||||
from authentik.flows.stage import ChallengeStageView
|
||||
from authentik.lib.utils.http import get_client_ip, get_http_session
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
from authentik.stages.captcha.models import CaptchaStage
|
||||
|
||||
|
||||
|
@ -42,7 +43,7 @@ class CaptchaChallengeResponse(ChallengeResponse):
|
|||
data={
|
||||
"secret": stage.private_key,
|
||||
"response": token,
|
||||
"remoteip": get_client_ip(self.stage.request),
|
||||
"remoteip": ClientIPMiddleware.get_client_ip(self.stage.request),
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
|
|
@ -26,8 +26,8 @@ from authentik.flows.models import FlowDesignation
|
|||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView
|
||||
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_GET
|
||||
from authentik.lib.utils.http import get_client_ip
|
||||
from authentik.lib.utils.urls import reverse_with_qs
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
from authentik.sources.oauth.types.apple import AppleLoginChallenge
|
||||
from authentik.sources.plex.models import PlexAuthenticationChallenge
|
||||
from authentik.stages.identification.models import IdentificationStage
|
||||
|
@ -103,7 +103,7 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
|||
self.stage.logger.info(
|
||||
"invalid_login",
|
||||
identifier=uid_field,
|
||||
client_ip=get_client_ip(self.stage.request),
|
||||
client_ip=ClientIPMiddleware.get_client_ip(self.stage.request),
|
||||
action="invalid_identifier",
|
||||
context={
|
||||
"stage": sanitize_item(self.stage),
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
"""Invitation Stage API Views"""
|
||||
from django_filters.filters import BooleanFilter
|
||||
from django_filters.filterset import FilterSet
|
||||
from rest_framework.fields import JSONField
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.groups import GroupMemberSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import is_dict
|
||||
from authentik.core.api.utils import JSONDictField
|
||||
from authentik.flows.api.flows import FlowSerializer
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
from authentik.stages.invitation.models import Invitation, InvitationStage
|
||||
|
@ -47,7 +46,7 @@ class InvitationSerializer(ModelSerializer):
|
|||
"""Invitation Serializer"""
|
||||
|
||||
created_by = GroupMemberSerializer(read_only=True)
|
||||
fixed_data = JSONField(validators=[is_dict], required=False)
|
||||
fixed_data = JSONDictField(required=False)
|
||||
flow_obj = FlowSerializer(read_only=True, required=False, source="flow")
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -16,8 +16,8 @@ from authentik.flows.tests import FlowTestCase
|
|||
from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.utils.http import DEFAULT_IP
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
from authentik.stages.user_login.models import UserLoginStage
|
||||
|
||||
|
||||
|
@ -76,7 +76,7 @@ class TestUserLoginStage(FlowTestCase):
|
|||
other_session = AuthenticatedSession.objects.create(
|
||||
user=self.user,
|
||||
session_key=key,
|
||||
last_ip=DEFAULT_IP,
|
||||
last_ip=ClientIPMiddleware.default_ip,
|
||||
)
|
||||
cache.set(f"{KEY_PREFIX}{other_session.session_key}", "foo")
|
||||
|
||||
|
|
|
@ -3282,7 +3282,8 @@
|
|||
"none",
|
||||
"require_authenticated",
|
||||
"require_unauthenticated",
|
||||
"require_superuser"
|
||||
"require_superuser",
|
||||
"require_outpost"
|
||||
],
|
||||
"title": "Authentication",
|
||||
"description": "Required level of authentication and authorization to access a flow."
|
||||
|
@ -5415,22 +5416,8 @@
|
|||
"title": "Icon"
|
||||
},
|
||||
"provider_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"apple",
|
||||
"azuread",
|
||||
"discord",
|
||||
"facebook",
|
||||
"github",
|
||||
"google",
|
||||
"mailcow",
|
||||
"openidconnect",
|
||||
"okta",
|
||||
"patreon",
|
||||
"reddit",
|
||||
"twitch",
|
||||
"twitter"
|
||||
],
|
||||
"type": [],
|
||||
"enum": [],
|
||||
"title": "Provider type"
|
||||
},
|
||||
"request_token_url": {
|
||||
|
|
|
@ -11,13 +11,15 @@ entries:
|
|||
name: "authentik default SCIM Mapping: User"
|
||||
expression: |
|
||||
# Some implementations require givenName and familyName to be set
|
||||
givenName, familyName = request.user.name, ""
|
||||
givenName, familyName = request.user.name, " "
|
||||
formatted = request.user.name + " "
|
||||
# This default sets givenName to the name before the first space
|
||||
# and the remainder as family name
|
||||
# if the user's name has no space the givenName is the entire name
|
||||
# (this might cause issues with some SCIM implementations)
|
||||
if " " in request.user.name:
|
||||
givenName, _, familyName = request.user.name.partition(" ")
|
||||
formatted = request.user.name
|
||||
|
||||
# photos supports URLs to images, however authentik might return data URIs
|
||||
avatar = request.user.avatar
|
||||
|
@ -39,7 +41,7 @@ entries:
|
|||
return {
|
||||
"userName": request.user.username,
|
||||
"name": {
|
||||
"formatted": request.user.name,
|
||||
"formatted": formatted,
|
||||
"givenName": givenName,
|
||||
"familyName": familyName,
|
||||
},
|
||||
|
|
|
@ -81,7 +81,6 @@ var rootCmd = &cobra.Command{
|
|||
for {
|
||||
<-ex
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
|
|
8
go.mod
8
go.mod
|
@ -10,7 +10,7 @@ require (
|
|||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
github.com/go-openapi/runtime v0.26.2
|
||||
github.com/go-openapi/strfmt v0.21.9
|
||||
github.com/go-openapi/strfmt v0.21.10
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
|
@ -27,7 +27,7 @@ require (
|
|||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
goauthentik.io/api/v3 v3.2023104.2
|
||||
goauthentik.io/api/v3 v3.2023104.3
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.15.0
|
||||
golang.org/x/sync v0.5.0
|
||||
|
@ -49,7 +49,7 @@ require (
|
|||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.21.4 // indirect
|
||||
github.com/go-openapi/errors v0.20.4 // indirect
|
||||
github.com/go-openapi/errors v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/loads v0.21.2 // indirect
|
||||
|
@ -73,7 +73,7 @@ require (
|
|||
go.opentelemetry.io/otel v1.17.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.17.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.17.0 // indirect
|
||||
golang.org/x/crypto v0.16.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
|
|
16
go.sum
16
go.sum
|
@ -98,8 +98,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
|
|||
github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc=
|
||||
github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo=
|
||||
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M=
|
||||
github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
|
||||
github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY=
|
||||
github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
|
@ -116,8 +116,8 @@ github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6
|
|||
github.com/go-openapi/spec v0.20.11 h1:J/TzFDLTt4Rcl/l1PmyErvkqlJDncGvPTMnCI39I4gY=
|
||||
github.com/go-openapi/spec v0.20.11/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
|
||||
github.com/go-openapi/strfmt v0.21.9 h1:LnEGOO9qyEC1v22Bzr323M98G13paIUGPU7yeJtG9Xs=
|
||||
github.com/go-openapi/strfmt v0.21.9/go.mod h1:0k3v301mglEaZRJdDDGSlN6Npq4VMVU69DE0LUyf7uA=
|
||||
github.com/go-openapi/strfmt v0.21.10 h1:JIsly3KXZB/Qf4UzvzJpg4OELH/0ASDQsyk//TTBDDk=
|
||||
github.com/go-openapi/strfmt v0.21.10/go.mod h1:vNDMwbilnl7xKiO/Ve/8H8Bb2JIInBnH+lqiw6QWgis=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
|
@ -309,8 +309,8 @@ go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYO
|
|||
go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
goauthentik.io/api/v3 v3.2023104.2 h1:TV3SdaPGhjVE7If0l1kt+H23xwgEabWUFgX4ijkkeSc=
|
||||
goauthentik.io/api/v3 v3.2023104.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2023104.3 h1:MzwdB21Q+G+wACEZiX0T1iVV4l7PjopjaVv6muqJE1M=
|
||||
goauthentik.io/api/v3 v3.2023104.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -320,8 +320,8 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
|
|
@ -25,13 +25,13 @@ type Config struct {
|
|||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Host string `yaml:"host" env:"AUTHENTIK_REDIS__HOST"`
|
||||
Port int `yaml:"port" env:"AUTHENTIK_REDIS__PORT"`
|
||||
DB int `yaml:"db" env:"AUTHENTIK_REDIS__DB"`
|
||||
Username string `yaml:"username" env:"AUTHENTIK_REDIS__USERNAME"`
|
||||
Password string `yaml:"password" env:"AUTHENTIK_REDIS__PASSWORD"`
|
||||
TLS bool `yaml:"tls" env:"AUTHENTIK_REDIS__TLS"`
|
||||
TLSReqs string `yaml:"tls_reqs" env:"AUTHENTIK_REDIS__TLS_REQS"`
|
||||
Host string `yaml:"host" env:"AUTHENTIK_REDIS__HOST"`
|
||||
Port int `yaml:"port" env:"AUTHENTIK_REDIS__PORT"`
|
||||
DB int `yaml:"db" env:"AUTHENTIK_REDIS__DB"`
|
||||
Username string `yaml:"username" env:"AUTHENTIK_REDIS__USERNAME"`
|
||||
Password string `yaml:"password" env:"AUTHENTIK_REDIS__PASSWORD"`
|
||||
TLS bool `yaml:"tls" env:"AUTHENTIK_REDIS__TLS"`
|
||||
TLSReqs string `yaml:"tls_reqs" env:"AUTHENTIK_REDIS__TLS_REQS"`
|
||||
}
|
||||
|
||||
type ListenConfig struct {
|
||||
|
|
|
@ -76,7 +76,6 @@ func NewAPIController(akURL url.URL, token string) *APIController {
|
|||
// Because we don't know the outpost UUID, we simply do a list and pick the first
|
||||
// The service account this token belongs to should only have access to a single outpost
|
||||
outposts, _, err := apiClient.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
|
||||
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to fetch outpost configuration, retrying in 3 seconds")
|
||||
time.Sleep(time.Second * 3)
|
||||
|
@ -168,7 +167,6 @@ func (a *APIController) OnRefresh() error {
|
|||
// Because we don't know the outpost UUID, we simply do a list and pick the first
|
||||
// The service account this token belongs to should only have access to a single outpost
|
||||
outposts, _, err := a.Client.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
|
||||
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to fetch outpost configuration")
|
||||
return err
|
||||
|
|
|
@ -14,8 +14,10 @@ import (
|
|||
webutils "goauthentik.io/internal/utils/web"
|
||||
)
|
||||
|
||||
var initialSetup = false
|
||||
var tlsTransport *http.RoundTripper = nil
|
||||
var (
|
||||
initialSetup = false
|
||||
tlsTransport *http.RoundTripper = nil
|
||||
)
|
||||
|
||||
func doGlobalSetup(outpost api.Outpost, globalConfig *api.Config) {
|
||||
l := log.WithField("logger", "authentik.outpost")
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
package handler
|
||||
|
||||
type Handler interface {
|
||||
}
|
||||
type Handler interface{}
|
||||
|
|
|
@ -78,5 +78,4 @@ func (ls *LDAPServer) fallbackRootDSE(req *search.Request) (ldap.ServerSearchRes
|
|||
},
|
||||
Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
|
|
@ -34,26 +34,26 @@ func (ds *DirectSearcher) SearchBase(req *search.Request) (ldap.ServerSearchResu
|
|||
Values: []string{"3"},
|
||||
},
|
||||
{
|
||||
Name: "supportedCapabilities",
|
||||
Name: "supportedCapabilities",
|
||||
Values: []string{
|
||||
"1.2.840.113556.1.4.800", //LDAP_CAP_ACTIVE_DIRECTORY_OID
|
||||
"1.2.840.113556.1.4.1791", //LDAP_CAP_ACTIVE_DIRECTORY_LDAP_INTEG_OID
|
||||
"1.2.840.113556.1.4.1670", //LDAP_CAP_ACTIVE_DIRECTORY_V51_OID
|
||||
"1.2.840.113556.1.4.1880", //LDAP_CAP_ACTIVE_DIRECTORY_ADAM_DIGEST_OID
|
||||
"1.2.840.113556.1.4.1851", //LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID
|
||||
"1.2.840.113556.1.4.1920", //LDAP_CAP_ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID
|
||||
"1.2.840.113556.1.4.1935", //LDAP_CAP_ACTIVE_DIRECTORY_V60_OID
|
||||
"1.2.840.113556.1.4.2080", //LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID
|
||||
"1.2.840.113556.1.4.2237", //LDAP_CAP_ACTIVE_DIRECTORY_W8_OID
|
||||
"1.2.840.113556.1.4.800", // LDAP_CAP_ACTIVE_DIRECTORY_OID
|
||||
"1.2.840.113556.1.4.1791", // LDAP_CAP_ACTIVE_DIRECTORY_LDAP_INTEG_OID
|
||||
"1.2.840.113556.1.4.1670", // LDAP_CAP_ACTIVE_DIRECTORY_V51_OID
|
||||
"1.2.840.113556.1.4.1880", // LDAP_CAP_ACTIVE_DIRECTORY_ADAM_DIGEST_OID
|
||||
"1.2.840.113556.1.4.1851", // LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID
|
||||
"1.2.840.113556.1.4.1920", // LDAP_CAP_ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID
|
||||
"1.2.840.113556.1.4.1935", // LDAP_CAP_ACTIVE_DIRECTORY_V60_OID
|
||||
"1.2.840.113556.1.4.2080", // LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID
|
||||
"1.2.840.113556.1.4.2237", // LDAP_CAP_ACTIVE_DIRECTORY_W8_OID
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "supportedControl",
|
||||
Name: "supportedControl",
|
||||
Values: []string{
|
||||
"2.16.840.1.113730.3.4.9", //VLV Request LDAPv3 Control
|
||||
"2.16.840.1.113730.3.4.10", //VLV Response LDAPv3 Control
|
||||
"1.2.840.113556.1.4.474", //Sort result
|
||||
"1.2.840.113556.1.4.319", //Paged Result Control
|
||||
"2.16.840.1.113730.3.4.9", // VLV Request LDAPv3 Control
|
||||
"2.16.840.1.113730.3.4.10", // VLV Response LDAPv3 Control
|
||||
"1.2.840.113556.1.4.474", // Sort result
|
||||
"1.2.840.113556.1.4.319", // Paged Result Control
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -143,7 +143,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
// as a member.
|
||||
for _, u := range g.UsersObj {
|
||||
if flag.UserPk == u.Pk {
|
||||
//TODO: Is there a better way to clone this object?
|
||||
// TODO: Is there a better way to clone this object?
|
||||
fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}, []api.Role{})
|
||||
fg.SetUsers([]int32{flag.UserPk})
|
||||
if g.Parent.IsSet() {
|
||||
|
|
|
@ -220,7 +220,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*A
|
|||
for _, regex := range strings.Split(*p.SkipPathRegex, "\n") {
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
//TODO: maybe create event for this?
|
||||
// TODO: maybe create event for this?
|
||||
a.log.WithError(err).Warning("failed to compile SkipPathRegex")
|
||||
continue
|
||||
} else {
|
||||
|
|
|
@ -59,12 +59,12 @@ func (a *Application) addHeaders(headers http.Header, c *Claims) {
|
|||
userAttributes := c.Proxy.UserAttributes
|
||||
a.setAuthorizationHeader(headers, c)
|
||||
// Check if user has additional headers set that we should sent
|
||||
if additionalHeaders, ok := userAttributes["additionalHeaders"].(map[string]interface{}); ok {
|
||||
if additionalHeaders, ok := userAttributes["additionalHeaders"]; ok {
|
||||
a.log.WithField("headers", additionalHeaders).Trace("setting additional headers")
|
||||
if additionalHeaders == nil {
|
||||
return
|
||||
}
|
||||
for key, value := range additionalHeaders {
|
||||
for key, value := range additionalHeaders.(map[string]interface{}) {
|
||||
headers.Set(key, toString(value))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package constants
|
||||
|
||||
const SessionOAuthState = "oauth_state"
|
||||
const SessionClaims = "claims"
|
||||
const (
|
||||
SessionOAuthState = "oauth_state"
|
||||
SessionClaims = "claims"
|
||||
)
|
||||
|
||||
const SessionRedirect = "redirect"
|
||||
|
||||
|
|
|
@ -26,26 +26,13 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="pf-c-background-image">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
|
||||
<filter id="image_overlay">
|
||||
<feColorMatrix in="SourceGraphic" type="matrix" values="1.3 0 0 0 0 0 1.3 0 0 0 0 0 1.3 0 0 0 0 0 1 0" />
|
||||
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
|
||||
<feFuncR type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncR>
|
||||
<feFuncG type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncG>
|
||||
<feFuncB type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncB>
|
||||
<feFuncA type="table" tableValues="0 1"></feFuncA>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="pf-c-login">
|
||||
<div class="pf-c-login stacked">
|
||||
<div class="ak-login-container">
|
||||
<header class="pf-c-login__header">
|
||||
<div class="pf-c-brand ak-brand">
|
||||
<main class="pf-c-login__main">
|
||||
<div class="pf-c-login__main-header pf-c-brand ak-brand">
|
||||
<img src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik Logo" />
|
||||
</div>
|
||||
</header>
|
||||
<main class="pf-c-login__main">
|
||||
<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">
|
||||
{{ .Title }}
|
||||
|
|
|
@ -15,7 +15,7 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
|
|||
fe := flow.NewFlowExecutor(r.Context(), r.pi.flowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{
|
||||
"username": username,
|
||||
"client": r.RemoteAddr(),
|
||||
"requestId": r.ID,
|
||||
"requestId": r.ID(),
|
||||
})
|
||||
fe.DelegateClientIP(r.RemoteAddr())
|
||||
fe.Params.Add("goauthentik.io/outpost/radius", "true")
|
||||
|
@ -27,7 +27,6 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
|
|||
}
|
||||
|
||||
passed, err := fe.Execute()
|
||||
|
||||
if err != nil {
|
||||
r.Log().WithField("username", username).WithError(err).Warning("failed to execute flow")
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
|
|
|
@ -14,12 +14,10 @@ import (
|
|||
"goauthentik.io/internal/utils/sentry"
|
||||
)
|
||||
|
||||
var (
|
||||
Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "authentik_main_request_duration_seconds",
|
||||
Help: "API request latencies in seconds",
|
||||
}, []string{"dest"})
|
||||
)
|
||||
var Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "authentik_main_request_duration_seconds",
|
||||
Help: "API request latencies in seconds",
|
||||
}, []string{"dest"})
|
||||
|
||||
func (ws *WebServer) runMetricsServer() {
|
||||
m := mux.NewRouter()
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-12-18 09:56+0000\n"
|
||||
"POT-Creation-Date: 2023-12-20 08:41+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -100,170 +100,170 @@ msgstr ""
|
|||
msgid "Create a SAML Provider by importing its Metadata."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/users.py:155
|
||||
#: authentik/core/api/users.py:149
|
||||
msgid "No leading or trailing slashes allowed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/users.py:158
|
||||
#: authentik/core/api/users.py:152
|
||||
msgid "No empty segments in user path allowed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:86
|
||||
#: authentik/core/models.py:85
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:88
|
||||
#: authentik/core/models.py:87
|
||||
msgid "Users added to this group will be superusers."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:162
|
||||
#: authentik/core/models.py:161
|
||||
msgid "Group"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:163
|
||||
#: authentik/core/models.py:162
|
||||
msgid "Groups"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:178
|
||||
#: authentik/core/models.py:177
|
||||
msgid "User's display name."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:274 authentik/providers/oauth2/models.py:295
|
||||
#: authentik/core/models.py:273 authentik/providers/oauth2/models.py:295
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:275
|
||||
#: authentik/core/models.py:274
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:277
|
||||
#: authentik/core/models.py:276
|
||||
#: authentik/stages/email/templates/email/password_reset.html:28
|
||||
msgid "Reset Password"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:278
|
||||
#: authentik/core/models.py:277
|
||||
msgid "Can impersonate other users"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:279 authentik/rbac/models.py:54
|
||||
#: authentik/core/models.py:278 authentik/rbac/models.py:54
|
||||
msgid "Can assign permissions to users"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:280 authentik/rbac/models.py:55
|
||||
#: authentik/core/models.py:279 authentik/rbac/models.py:55
|
||||
msgid "Can unassign permissions from users"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:294
|
||||
#: authentik/core/models.py:293
|
||||
msgid ""
|
||||
"Flow used for authentication when the associated application is accessed by "
|
||||
"an un-authenticated user."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:304
|
||||
#: authentik/core/models.py:303
|
||||
msgid "Flow used when authorizing this provider."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:316
|
||||
#: authentik/core/models.py:315
|
||||
msgid ""
|
||||
"Accessed from applications; optional backchannel providers for protocols "
|
||||
"like LDAP and SCIM."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:371
|
||||
#: authentik/core/models.py:370
|
||||
msgid "Application's display Name."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:372
|
||||
#: authentik/core/models.py:371
|
||||
msgid "Internal application name, used in URLs."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:384
|
||||
#: authentik/core/models.py:383
|
||||
msgid "Open launch URL in a new browser tab or window."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:448
|
||||
#: authentik/core/models.py:447
|
||||
msgid "Application"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:449
|
||||
#: authentik/core/models.py:448
|
||||
msgid "Applications"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:455
|
||||
#: authentik/core/models.py:454
|
||||
msgid "Use the source-specific identifier"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:457
|
||||
#: authentik/core/models.py:456
|
||||
msgid ""
|
||||
"Link to a user with identical email address. Can have security implications "
|
||||
"when a source doesn't validate email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:461
|
||||
#: authentik/core/models.py:460
|
||||
msgid ""
|
||||
"Use the user's email address, but deny enrollment when the email address "
|
||||
"already exists."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:464
|
||||
#: authentik/core/models.py:463
|
||||
msgid ""
|
||||
"Link to a user with identical username. Can have security implications when "
|
||||
"a username is used with another source."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:468
|
||||
#: authentik/core/models.py:467
|
||||
msgid ""
|
||||
"Use the user's username, but deny enrollment when the username already "
|
||||
"exists."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:475
|
||||
#: authentik/core/models.py:474
|
||||
msgid "Source's display Name."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:476
|
||||
#: authentik/core/models.py:475
|
||||
msgid "Internal source name, used in URLs."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:495
|
||||
#: authentik/core/models.py:494
|
||||
msgid "Flow to use when authenticating existing users."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:504
|
||||
#: authentik/core/models.py:503
|
||||
msgid "Flow to use when enrolling new users."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:512
|
||||
#: authentik/core/models.py:511
|
||||
msgid ""
|
||||
"How the source determines if an existing user should be authenticated or a "
|
||||
"new user enrolled."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:684
|
||||
#: authentik/core/models.py:683
|
||||
msgid "Token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:685
|
||||
#: authentik/core/models.py:684
|
||||
msgid "Tokens"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:690
|
||||
#: authentik/core/models.py:689
|
||||
msgid "View token's key"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:726
|
||||
#: authentik/core/models.py:725
|
||||
msgid "Property Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:727
|
||||
#: authentik/core/models.py:726
|
||||
msgid "Property Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:762
|
||||
#: authentik/core/models.py:763
|
||||
msgid "Authenticated Session"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:763
|
||||
#: authentik/core/models.py:764
|
||||
msgid "Authenticated Sessions"
|
||||
msgstr ""
|
||||
|
||||
|
@ -338,7 +338,7 @@ msgstr ""
|
|||
msgid "Go home"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/templates/login/base_full.html:90
|
||||
#: authentik/core/templates/login/base_full.html:75
|
||||
msgid "Powered by authentik"
|
||||
msgstr ""
|
||||
|
||||
|
@ -388,105 +388,105 @@ msgstr ""
|
|||
msgid "License Usage Records"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:294
|
||||
#: authentik/events/models.py:295
|
||||
msgid "Event"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:295
|
||||
#: authentik/events/models.py:296
|
||||
msgid "Events"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:301
|
||||
#: authentik/events/models.py:302
|
||||
msgid "authentik inbuilt notifications"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:302
|
||||
#: authentik/events/models.py:303
|
||||
msgid "Generic Webhook"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:303
|
||||
#: authentik/events/models.py:304
|
||||
msgid "Slack Webhook (Slack/Discord)"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:304
|
||||
#: authentik/events/models.py:305
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:322
|
||||
#: authentik/events/models.py:323
|
||||
msgid ""
|
||||
"Only send notification once, for example when sending a webhook into a chat "
|
||||
"channel."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:387
|
||||
#: authentik/events/models.py:388
|
||||
msgid "Severity"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:392
|
||||
#: authentik/events/models.py:393
|
||||
msgid "Dispatched for user"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:401
|
||||
#: authentik/events/models.py:402
|
||||
msgid "Event user"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:495
|
||||
#: authentik/events/models.py:496
|
||||
msgid "Notification Transport"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:496
|
||||
#: authentik/events/models.py:497
|
||||
msgid "Notification Transports"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:502
|
||||
#: authentik/events/models.py:503
|
||||
msgid "Notice"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:503
|
||||
#: authentik/events/models.py:504
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:504
|
||||
#: authentik/events/models.py:505
|
||||
msgid "Alert"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:529
|
||||
#: authentik/events/models.py:530
|
||||
msgid "Notification"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:530
|
||||
#: authentik/events/models.py:531
|
||||
msgid "Notifications"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:540
|
||||
#: authentik/events/models.py:541
|
||||
msgid ""
|
||||
"Select which transports should be used to notify the user. If none are "
|
||||
"selected, the notification will only be shown in the authentik UI."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:548
|
||||
#: authentik/events/models.py:549
|
||||
msgid "Controls which severity level the created notifications will have."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:553
|
||||
#: authentik/events/models.py:554
|
||||
msgid ""
|
||||
"Define which group of users this notification should be sent and shown to. "
|
||||
"If left empty, Notification won't ben sent."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:571
|
||||
#: authentik/events/models.py:572
|
||||
msgid "Notification Rule"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:572
|
||||
#: authentik/events/models.py:573
|
||||
msgid "Notification Rules"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:592
|
||||
#: authentik/events/models.py:593
|
||||
msgid "Webhook Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/events/models.py:593
|
||||
#: authentik/events/models.py:594
|
||||
msgid "Webhook Mappings"
|
||||
msgstr ""
|
||||
|
||||
|
@ -547,7 +547,7 @@ msgstr ""
|
|||
msgid "Pre-flow policies"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/api/flows_diagram.py:214 authentik/flows/models.py:193
|
||||
#: authentik/flows/api/flows_diagram.py:214 authentik/flows/models.py:194
|
||||
msgid "Flow"
|
||||
msgstr ""
|
||||
|
||||
|
@ -555,72 +555,72 @@ msgstr ""
|
|||
msgid "Flow does not apply to current user."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:114
|
||||
#: authentik/flows/models.py:115
|
||||
#, python-format
|
||||
msgid "Dynamic In-memory stage: %(doc)s"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:129
|
||||
#: authentik/flows/models.py:130
|
||||
msgid "Visible in the URL."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:131
|
||||
#: authentik/flows/models.py:132
|
||||
msgid "Shown as the Title in Flow pages."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:138
|
||||
#: authentik/flows/models.py:139
|
||||
msgid ""
|
||||
"Decides what this Flow is used for. For example, the Authentication flow is "
|
||||
"redirect to when an un-authenticated user visits authentik."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:147
|
||||
#: authentik/flows/models.py:148
|
||||
msgid "Background shown during execution"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:154
|
||||
#: authentik/flows/models.py:155
|
||||
msgid ""
|
||||
"Enable compatibility mode, increases compatibility with password managers on "
|
||||
"mobile devices."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:162
|
||||
#: authentik/flows/models.py:163
|
||||
msgid "Configure what should happen when a flow denies access to a user."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:168
|
||||
#: authentik/flows/models.py:169
|
||||
msgid "Required level of authentication and authorization to access a flow."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:194
|
||||
#: authentik/flows/models.py:195
|
||||
msgid "Flows"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:197
|
||||
#: authentik/flows/models.py:198
|
||||
msgid "Can export a Flow"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:198
|
||||
#: authentik/flows/models.py:199
|
||||
msgid "Can inspect a Flow's execution"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:199
|
||||
#: authentik/flows/models.py:200
|
||||
msgid "View Flow's cache metrics"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:200
|
||||
#: authentik/flows/models.py:201
|
||||
msgid "Clear Flow's cache metrics"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:216
|
||||
#: authentik/flows/models.py:217
|
||||
msgid "Evaluate policies during the Flow planning process."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:220
|
||||
#: authentik/flows/models.py:221
|
||||
msgid "Evaluate policies when the Stage is present to the user."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:227
|
||||
#: authentik/flows/models.py:228
|
||||
msgid ""
|
||||
"Configure how the flow executor should handle an invalid response to a "
|
||||
"challenge. RETRY returns the error message and a similar challenge to the "
|
||||
|
@ -628,25 +628,25 @@ msgid ""
|
|||
"RESTART_WITH_CONTEXT restarts the flow while keeping the current context."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:250
|
||||
#: authentik/flows/models.py:251
|
||||
msgid "Flow Stage Binding"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:251
|
||||
#: authentik/flows/models.py:252
|
||||
msgid "Flow Stage Bindings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:266
|
||||
#: authentik/flows/models.py:267
|
||||
msgid ""
|
||||
"Flow used by an authenticated user to configure this Stage. If empty, user "
|
||||
"will not be able to configure this stage."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:306
|
||||
#: authentik/flows/models.py:307
|
||||
msgid "Flow Token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:307
|
||||
#: authentik/flows/models.py:308
|
||||
msgid "Flow Tokens"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1526,27 +1526,27 @@ msgstr ""
|
|||
msgid "Starting full SCIM sync"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/scim/tasks.py:59
|
||||
#: authentik/providers/scim/tasks.py:62
|
||||
#, python-format
|
||||
msgid "Syncing page %(page)d of users"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/scim/tasks.py:63
|
||||
#: authentik/providers/scim/tasks.py:66
|
||||
#, python-format
|
||||
msgid "Syncing page %(page)d of groups"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/scim/tasks.py:92
|
||||
#: authentik/providers/scim/tasks.py:98
|
||||
#, python-format
|
||||
msgid "Failed to sync user %(user_name)s due to remote error: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/scim/tasks.py:103 authentik/providers/scim/tasks.py:144
|
||||
#: authentik/providers/scim/tasks.py:109 authentik/providers/scim/tasks.py:150
|
||||
#, python-format
|
||||
msgid "Stopping sync due to error: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/scim/tasks.py:133
|
||||
#: authentik/providers/scim/tasks.py:139
|
||||
#, python-format
|
||||
msgid "Failed to sync group %(group_name)s due to remote error: %(error)s"
|
||||
msgstr ""
|
||||
|
|
File diff suppressed because it is too large
Load Diff
663
schema.yml
663
schema.yml
|
@ -28487,46 +28487,6 @@ components:
|
|||
* `authentik.blueprints` - authentik Blueprints
|
||||
* `authentik.core` - authentik Core
|
||||
* `authentik.enterprise` - authentik Enterprise
|
||||
AppleChallengeResponseRequest:
|
||||
type: object
|
||||
description: Pseudo class for plex response
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-source-oauth-apple
|
||||
AppleLoginChallenge:
|
||||
type: object
|
||||
description: Special challenge for apple-native authentication flow, which happens
|
||||
on the client.
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-source-oauth-apple
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
client_id:
|
||||
type: string
|
||||
scope:
|
||||
type: string
|
||||
redirect_uri:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
required:
|
||||
- client_id
|
||||
- redirect_uri
|
||||
- scope
|
||||
- state
|
||||
- type
|
||||
Application:
|
||||
type: object
|
||||
description: Application Serializer
|
||||
|
@ -28850,12 +28810,14 @@ components:
|
|||
- require_authenticated
|
||||
- require_unauthenticated
|
||||
- require_superuser
|
||||
- require_outpost
|
||||
type: string
|
||||
description: |-
|
||||
* `none` - None
|
||||
* `require_authenticated` - Require Authenticated
|
||||
* `require_unauthenticated` - Require Unauthenticated
|
||||
* `require_superuser` - Require Superuser
|
||||
* `require_outpost` - Require Outpost
|
||||
AuthenticatorAttachmentEnum:
|
||||
enum:
|
||||
- platform
|
||||
|
@ -29026,47 +28988,6 @@ components:
|
|||
- client_id
|
||||
- client_secret
|
||||
- name
|
||||
AuthenticatorSMSChallenge:
|
||||
type: object
|
||||
description: SMS Setup challenge
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-sms
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
pending_user:
|
||||
type: string
|
||||
pending_user_avatar:
|
||||
type: string
|
||||
phone_number_required:
|
||||
type: boolean
|
||||
default: true
|
||||
required:
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
- type
|
||||
AuthenticatorSMSChallengeResponseRequest:
|
||||
type: object
|
||||
description: SMS Challenge response, device is set by get_response_instance
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-stage-authenticator-sms
|
||||
code:
|
||||
type: integer
|
||||
phone_number:
|
||||
type: string
|
||||
minLength: 1
|
||||
AuthenticatorSMSStage:
|
||||
type: object
|
||||
description: AuthenticatorSMSStage Serializer
|
||||
|
@ -29192,44 +29113,6 @@ components:
|
|||
- from_number
|
||||
- name
|
||||
- provider
|
||||
AuthenticatorStaticChallenge:
|
||||
type: object
|
||||
description: Static authenticator challenge
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-static
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
pending_user:
|
||||
type: string
|
||||
pending_user_avatar:
|
||||
type: string
|
||||
codes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- codes
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
- type
|
||||
AuthenticatorStaticChallengeResponseRequest:
|
||||
type: object
|
||||
description: Pseudo class for static response
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-stage-authenticator-static
|
||||
AuthenticatorStaticStage:
|
||||
type: object
|
||||
description: AuthenticatorStaticStage Serializer
|
||||
|
@ -29316,46 +29199,6 @@ components:
|
|||
minimum: 0
|
||||
required:
|
||||
- name
|
||||
AuthenticatorTOTPChallenge:
|
||||
type: object
|
||||
description: TOTP Setup challenge
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-totp
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
pending_user:
|
||||
type: string
|
||||
pending_user_avatar:
|
||||
type: string
|
||||
config_url:
|
||||
type: string
|
||||
required:
|
||||
- config_url
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
- type
|
||||
AuthenticatorTOTPChallengeResponseRequest:
|
||||
type: object
|
||||
description: TOTP Challenge response, device is set by get_response_instance
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-stage-authenticator-totp
|
||||
code:
|
||||
type: integer
|
||||
required:
|
||||
- code
|
||||
AuthenticatorTOTPStage:
|
||||
type: object
|
||||
description: AuthenticatorTOTPStage Serializer
|
||||
|
@ -29540,104 +29383,6 @@ components:
|
|||
* `discouraged` - Discouraged
|
||||
required:
|
||||
- name
|
||||
AuthenticatorValidationChallenge:
|
||||
type: object
|
||||
description: Authenticator challenge
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-validate
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
pending_user:
|
||||
type: string
|
||||
pending_user_avatar:
|
||||
type: string
|
||||
device_challenges:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DeviceChallenge'
|
||||
configuration_stages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SelectableStage'
|
||||
required:
|
||||
- configuration_stages
|
||||
- device_challenges
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
- type
|
||||
AuthenticatorValidationChallengeResponseRequest:
|
||||
type: object
|
||||
description: Challenge used for Code-based and WebAuthn authenticators
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-stage-authenticator-validate
|
||||
selected_challenge:
|
||||
$ref: '#/components/schemas/DeviceChallengeRequest'
|
||||
selected_stage:
|
||||
type: string
|
||||
minLength: 1
|
||||
code:
|
||||
type: string
|
||||
minLength: 1
|
||||
webauthn:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
duo:
|
||||
type: integer
|
||||
AuthenticatorWebAuthnChallenge:
|
||||
type: object
|
||||
description: WebAuthn Challenge
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-webauthn
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
pending_user:
|
||||
type: string
|
||||
pending_user_avatar:
|
||||
type: string
|
||||
registration:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
required:
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
- registration
|
||||
- type
|
||||
AuthenticatorWebAuthnChallengeResponseRequest:
|
||||
type: object
|
||||
description: WebAuthn Challenge response
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-stage-authenticator-webauthn
|
||||
response:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
required:
|
||||
- response
|
||||
AutoSubmitChallengeResponseRequest:
|
||||
type: object
|
||||
description: Pseudo class for autosubmit response
|
||||
|
@ -29928,50 +29673,6 @@ components:
|
|||
* `can_impersonate` - Can Impersonate
|
||||
* `can_debug` - Can Debug
|
||||
* `is_enterprise` - Is Enterprise
|
||||
CaptchaChallenge:
|
||||
type: object
|
||||
description: Site public key
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-captcha
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
pending_user:
|
||||
type: string
|
||||
pending_user_avatar:
|
||||
type: string
|
||||
site_key:
|
||||
type: string
|
||||
js_url:
|
||||
type: string
|
||||
required:
|
||||
- js_url
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
- site_key
|
||||
- type
|
||||
CaptchaChallengeResponseRequest:
|
||||
type: object
|
||||
description: Validate captcha token
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-stage-captcha
|
||||
token:
|
||||
type: string
|
||||
minLength: 1
|
||||
required:
|
||||
- token
|
||||
CaptchaStage:
|
||||
type: object
|
||||
description: CaptchaStage Serializer
|
||||
|
@ -30174,20 +29875,10 @@ components:
|
|||
ChallengeTypes:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AccessDeniedChallenge'
|
||||
- $ref: '#/components/schemas/AppleLoginChallenge'
|
||||
- $ref: '#/components/schemas/AuthenticatorDuoChallenge'
|
||||
- $ref: '#/components/schemas/AuthenticatorSMSChallenge'
|
||||
- $ref: '#/components/schemas/AuthenticatorStaticChallenge'
|
||||
- $ref: '#/components/schemas/AuthenticatorTOTPChallenge'
|
||||
- $ref: '#/components/schemas/AuthenticatorValidationChallenge'
|
||||
- $ref: '#/components/schemas/AuthenticatorWebAuthnChallenge'
|
||||
- $ref: '#/components/schemas/AutosubmitChallenge'
|
||||
- $ref: '#/components/schemas/CaptchaChallenge'
|
||||
- $ref: '#/components/schemas/ConsentChallenge'
|
||||
- $ref: '#/components/schemas/DummyChallenge'
|
||||
- $ref: '#/components/schemas/EmailChallenge'
|
||||
- $ref: '#/components/schemas/FlowErrorChallenge'
|
||||
- $ref: '#/components/schemas/IdentificationChallenge'
|
||||
- $ref: '#/components/schemas/OAuthDeviceCodeChallenge'
|
||||
- $ref: '#/components/schemas/OAuthDeviceCodeFinishChallenge'
|
||||
- $ref: '#/components/schemas/PasswordChallenge'
|
||||
|
@ -30195,25 +29886,14 @@ components:
|
|||
- $ref: '#/components/schemas/PromptChallenge'
|
||||
- $ref: '#/components/schemas/RedirectChallenge'
|
||||
- $ref: '#/components/schemas/ShellChallenge'
|
||||
- $ref: '#/components/schemas/UserLoginChallenge'
|
||||
discriminator:
|
||||
propertyName: component
|
||||
mapping:
|
||||
ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge'
|
||||
ak-source-oauth-apple: '#/components/schemas/AppleLoginChallenge'
|
||||
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallenge'
|
||||
ak-stage-authenticator-sms: '#/components/schemas/AuthenticatorSMSChallenge'
|
||||
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallenge'
|
||||
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallenge'
|
||||
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallenge'
|
||||
ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallenge'
|
||||
ak-stage-autosubmit: '#/components/schemas/AutosubmitChallenge'
|
||||
ak-stage-captcha: '#/components/schemas/CaptchaChallenge'
|
||||
ak-stage-consent: '#/components/schemas/ConsentChallenge'
|
||||
ak-stage-dummy: '#/components/schemas/DummyChallenge'
|
||||
ak-stage-email: '#/components/schemas/EmailChallenge'
|
||||
ak-stage-flow-error: '#/components/schemas/FlowErrorChallenge'
|
||||
ak-stage-identification: '#/components/schemas/IdentificationChallenge'
|
||||
ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallenge'
|
||||
ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallenge'
|
||||
ak-stage-password: '#/components/schemas/PasswordChallenge'
|
||||
|
@ -30221,7 +29901,6 @@ components:
|
|||
ak-stage-prompt: '#/components/schemas/PromptChallenge'
|
||||
xak-flow-redirect: '#/components/schemas/RedirectChallenge'
|
||||
xak-flow-shell: '#/components/schemas/ShellChallenge'
|
||||
ak-stage-user-login: '#/components/schemas/UserLoginChallenge'
|
||||
ClientTypeEnum:
|
||||
enum:
|
||||
- confidential
|
||||
|
@ -30404,7 +30083,7 @@ components:
|
|||
cancel_url:
|
||||
type: string
|
||||
layout:
|
||||
$ref: '#/components/schemas/LayoutEnum'
|
||||
$ref: '#/components/schemas/FlowLayoutEnum'
|
||||
required:
|
||||
- cancel_url
|
||||
- layout
|
||||
|
@ -30565,38 +30244,6 @@ components:
|
|||
- type
|
||||
- verbose_name
|
||||
- verbose_name_plural
|
||||
DeviceChallenge:
|
||||
type: object
|
||||
description: Single device challenge
|
||||
properties:
|
||||
device_class:
|
||||
type: string
|
||||
device_uid:
|
||||
type: string
|
||||
challenge:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
required:
|
||||
- challenge
|
||||
- device_class
|
||||
- device_uid
|
||||
DeviceChallengeRequest:
|
||||
type: object
|
||||
description: Single device challenge
|
||||
properties:
|
||||
device_class:
|
||||
type: string
|
||||
minLength: 1
|
||||
device_uid:
|
||||
type: string
|
||||
minLength: 1
|
||||
challenge:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
required:
|
||||
- challenge
|
||||
- device_class
|
||||
- device_uid
|
||||
DeviceClassesEnum:
|
||||
enum:
|
||||
- static
|
||||
|
@ -30753,33 +30400,6 @@ components:
|
|||
required:
|
||||
- domain
|
||||
- tenant
|
||||
DummyChallenge:
|
||||
type: object
|
||||
description: Dummy challenge
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-dummy
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
required:
|
||||
- type
|
||||
DummyChallengeResponseRequest:
|
||||
type: object
|
||||
description: Dummy challenge response
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-stage-dummy
|
||||
DummyPolicy:
|
||||
type: object
|
||||
description: Dummy Policy Serializer
|
||||
|
@ -30954,35 +30574,6 @@ components:
|
|||
* `success` - Success
|
||||
* `waiting` - Waiting
|
||||
* `invalid` - Invalid
|
||||
EmailChallenge:
|
||||
type: object
|
||||
description: Email challenge
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-email
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
required:
|
||||
- type
|
||||
EmailChallengeResponseRequest:
|
||||
type: object
|
||||
description: |-
|
||||
Email challenge resposen. No fields. This challenge is
|
||||
always declared invalid to give the user a chance to retry
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-stage-email
|
||||
EmailStage:
|
||||
type: object
|
||||
description: EmailStage Serializer
|
||||
|
@ -31983,7 +31574,7 @@ components:
|
|||
description: Get export URL for flow
|
||||
readOnly: true
|
||||
layout:
|
||||
$ref: '#/components/schemas/LayoutEnum'
|
||||
$ref: '#/components/schemas/FlowLayoutEnum'
|
||||
denied_action:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/DeniedActionEnum'
|
||||
|
@ -32003,6 +31594,7 @@ components:
|
|||
* `require_authenticated` - Require Authenticated
|
||||
* `require_unauthenticated` - Require Unauthenticated
|
||||
* `require_superuser` - Require Superuser
|
||||
* `require_outpost` - Require Outpost
|
||||
required:
|
||||
- background
|
||||
- cache_count
|
||||
|
@ -32017,47 +31609,25 @@ components:
|
|||
- title
|
||||
FlowChallengeResponseRequest:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AppleChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorSMSChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorStaticChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorWebAuthnChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AutoSubmitChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/CaptchaChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/ConsentChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/DummyChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/EmailChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/IdentificationChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/OAuthDeviceCodeChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/OAuthDeviceCodeFinishChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/PasswordChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/PlexAuthenticationChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/PromptChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/UserLoginChallengeResponseRequest'
|
||||
discriminator:
|
||||
propertyName: component
|
||||
mapping:
|
||||
ak-source-oauth-apple: '#/components/schemas/AppleChallengeResponseRequest'
|
||||
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
|
||||
ak-stage-authenticator-sms: '#/components/schemas/AuthenticatorSMSChallengeResponseRequest'
|
||||
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallengeResponseRequest'
|
||||
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
|
||||
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
|
||||
ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallengeResponseRequest'
|
||||
ak-stage-autosubmit: '#/components/schemas/AutoSubmitChallengeResponseRequest'
|
||||
ak-stage-captcha: '#/components/schemas/CaptchaChallengeResponseRequest'
|
||||
ak-stage-consent: '#/components/schemas/ConsentChallengeResponseRequest'
|
||||
ak-stage-dummy: '#/components/schemas/DummyChallengeResponseRequest'
|
||||
ak-stage-email: '#/components/schemas/EmailChallengeResponseRequest'
|
||||
ak-stage-identification: '#/components/schemas/IdentificationChallengeResponseRequest'
|
||||
ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallengeResponseRequest'
|
||||
ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallengeResponseRequest'
|
||||
ak-stage-password: '#/components/schemas/PasswordChallengeResponseRequest'
|
||||
ak-source-plex: '#/components/schemas/PlexAuthenticationChallengeResponseRequest'
|
||||
ak-stage-prompt: '#/components/schemas/PromptChallengeResponseRequest'
|
||||
ak-stage-user-login: '#/components/schemas/UserLoginChallengeResponseRequest'
|
||||
FlowDesignationEnum:
|
||||
enum:
|
||||
- authentication
|
||||
|
@ -32170,6 +31740,20 @@ components:
|
|||
- next_planned_stage
|
||||
- plan_context
|
||||
- session_id
|
||||
FlowLayoutEnum:
|
||||
enum:
|
||||
- stacked
|
||||
- content_left
|
||||
- content_right
|
||||
- sidebar_left
|
||||
- sidebar_right
|
||||
type: string
|
||||
description: |-
|
||||
* `stacked` - STACKED
|
||||
* `content_left` - CONTENT_LEFT
|
||||
* `content_right` - CONTENT_RIGHT
|
||||
* `sidebar_left` - SIDEBAR_LEFT
|
||||
* `sidebar_right` - SIDEBAR_RIGHT
|
||||
FlowRequest:
|
||||
type: object
|
||||
description: Flow Serializer
|
||||
|
@ -32207,7 +31791,7 @@ components:
|
|||
description: Enable compatibility mode, increases compatibility with password
|
||||
managers on mobile devices.
|
||||
layout:
|
||||
$ref: '#/components/schemas/LayoutEnum'
|
||||
$ref: '#/components/schemas/FlowLayoutEnum'
|
||||
denied_action:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/DeniedActionEnum'
|
||||
|
@ -32227,6 +31811,7 @@ components:
|
|||
* `require_authenticated` - Require Authenticated
|
||||
* `require_unauthenticated` - Require Unauthenticated
|
||||
* `require_superuser` - Require Superuser
|
||||
* `require_outpost` - Require Outpost
|
||||
required:
|
||||
- designation
|
||||
- name
|
||||
|
@ -32285,7 +31870,7 @@ components:
|
|||
description: Get export URL for flow
|
||||
readOnly: true
|
||||
layout:
|
||||
$ref: '#/components/schemas/LayoutEnum'
|
||||
$ref: '#/components/schemas/FlowLayoutEnum'
|
||||
denied_action:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/DeniedActionEnum'
|
||||
|
@ -32341,7 +31926,7 @@ components:
|
|||
description: Enable compatibility mode, increases compatibility with password
|
||||
managers on mobile devices.
|
||||
layout:
|
||||
$ref: '#/components/schemas/LayoutEnum'
|
||||
$ref: '#/components/schemas/FlowLayoutEnum'
|
||||
denied_action:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/DeniedActionEnum'
|
||||
|
@ -32626,68 +32211,6 @@ components:
|
|||
format: uuid
|
||||
required:
|
||||
- name
|
||||
IdentificationChallenge:
|
||||
type: object
|
||||
description: Identification challenges with all UI elements
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-identification
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
user_fields:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
password_fields:
|
||||
type: boolean
|
||||
application_pre:
|
||||
type: string
|
||||
enroll_url:
|
||||
type: string
|
||||
recovery_url:
|
||||
type: string
|
||||
passwordless_url:
|
||||
type: string
|
||||
primary_action:
|
||||
type: string
|
||||
sources:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/LoginSource'
|
||||
show_source_labels:
|
||||
type: boolean
|
||||
required:
|
||||
- password_fields
|
||||
- primary_action
|
||||
- show_source_labels
|
||||
- type
|
||||
- user_fields
|
||||
IdentificationChallengeResponseRequest:
|
||||
type: object
|
||||
description: Identification challenge
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-stage-identification
|
||||
uid_field:
|
||||
type: string
|
||||
minLength: 1
|
||||
password:
|
||||
type: string
|
||||
nullable: true
|
||||
required:
|
||||
- uid_field
|
||||
IdentificationStage:
|
||||
type: object
|
||||
description: IdentificationStage Serializer
|
||||
|
@ -33725,20 +33248,6 @@ components:
|
|||
required:
|
||||
- is_running
|
||||
- tasks
|
||||
LayoutEnum:
|
||||
enum:
|
||||
- stacked
|
||||
- content_left
|
||||
- content_right
|
||||
- sidebar_left
|
||||
- sidebar_right
|
||||
type: string
|
||||
description: |-
|
||||
* `stacked` - STACKED
|
||||
* `content_left` - CONTENT_LEFT
|
||||
* `content_right` - CONTENT_RIGHT
|
||||
* `sidebar_left` - SIDEBAR_LEFT
|
||||
* `sidebar_right` - SIDEBAR_RIGHT
|
||||
License:
|
||||
type: object
|
||||
description: License Serializer
|
||||
|
@ -33833,17 +33342,6 @@ components:
|
|||
type: string
|
||||
required:
|
||||
- link
|
||||
LoginChallengeTypes:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/RedirectChallenge'
|
||||
- $ref: '#/components/schemas/PlexAuthenticationChallenge'
|
||||
- $ref: '#/components/schemas/AppleLoginChallenge'
|
||||
discriminator:
|
||||
propertyName: component
|
||||
mapping:
|
||||
xak-flow-redirect: '#/components/schemas/RedirectChallenge'
|
||||
ak-source-plex: '#/components/schemas/PlexAuthenticationChallenge'
|
||||
ak-source-oauth-apple: '#/components/schemas/AppleLoginChallenge'
|
||||
LoginMetrics:
|
||||
type: object
|
||||
description: Login Metrics per 1h
|
||||
|
@ -33867,20 +33365,6 @@ components:
|
|||
- authorizations
|
||||
- logins
|
||||
- logins_failed
|
||||
LoginSource:
|
||||
type: object
|
||||
description: Serializer for Login buttons of sources
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
icon_url:
|
||||
type: string
|
||||
nullable: true
|
||||
challenge:
|
||||
$ref: '#/components/schemas/LoginChallengeTypes'
|
||||
required:
|
||||
- challenge
|
||||
- name
|
||||
Metadata:
|
||||
type: object
|
||||
description: Serializer for blueprint metadata
|
||||
|
@ -34690,7 +34174,9 @@ components:
|
|||
starts with http it is returned as-is
|
||||
readOnly: true
|
||||
provider_type:
|
||||
$ref: '#/components/schemas/ProviderTypeEnum'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ProviderTypeEnum'
|
||||
description: ''
|
||||
request_token_url:
|
||||
type: string
|
||||
nullable: true
|
||||
|
@ -34788,7 +34274,9 @@ components:
|
|||
type: string
|
||||
minLength: 1
|
||||
provider_type:
|
||||
$ref: '#/components/schemas/ProviderTypeEnum'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ProviderTypeEnum'
|
||||
description: ''
|
||||
request_token_url:
|
||||
type: string
|
||||
nullable: true
|
||||
|
@ -37281,7 +36769,7 @@ components:
|
|||
description: Enable compatibility mode, increases compatibility with password
|
||||
managers on mobile devices.
|
||||
layout:
|
||||
$ref: '#/components/schemas/LayoutEnum'
|
||||
$ref: '#/components/schemas/FlowLayoutEnum'
|
||||
denied_action:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/DeniedActionEnum'
|
||||
|
@ -37301,6 +36789,7 @@ components:
|
|||
* `require_authenticated` - Require Authenticated
|
||||
* `require_unauthenticated` - Require Unauthenticated
|
||||
* `require_superuser` - Require Superuser
|
||||
* `require_outpost` - Require Outpost
|
||||
PatchedFlowStageBindingRequest:
|
||||
type: object
|
||||
description: FlowStageBinding Serializer
|
||||
|
@ -37894,7 +37383,9 @@ components:
|
|||
type: string
|
||||
minLength: 1
|
||||
provider_type:
|
||||
$ref: '#/components/schemas/ProviderTypeEnum'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ProviderTypeEnum'
|
||||
description: ''
|
||||
request_token_url:
|
||||
type: string
|
||||
nullable: true
|
||||
|
@ -39866,35 +39357,8 @@ components:
|
|||
- authorization_flow
|
||||
- name
|
||||
ProviderTypeEnum:
|
||||
enum:
|
||||
- apple
|
||||
- azuread
|
||||
- discord
|
||||
- facebook
|
||||
- github
|
||||
- google
|
||||
- mailcow
|
||||
- openidconnect
|
||||
- okta
|
||||
- patreon
|
||||
- reddit
|
||||
- twitch
|
||||
- twitter
|
||||
type: string
|
||||
description: |-
|
||||
* `apple` - Apple
|
||||
* `azuread` - Azure AD
|
||||
* `discord` - Discord
|
||||
* `facebook` - Facebook
|
||||
* `github` - GitHub
|
||||
* `google` - Google
|
||||
* `mailcow` - Mailcow
|
||||
* `openidconnect` - OpenID Connect
|
||||
* `okta` - Okta
|
||||
* `patreon` - Patreon
|
||||
* `reddit` - Reddit
|
||||
* `twitch` - Twitch
|
||||
* `twitter` - Twitter
|
||||
enum: []
|
||||
type: boolean
|
||||
ProxyMode:
|
||||
enum:
|
||||
- proxy
|
||||
|
@ -41517,24 +40981,6 @@ components:
|
|||
- expression
|
||||
- name
|
||||
- scope_name
|
||||
SelectableStage:
|
||||
type: object
|
||||
description: Serializer for stages which can be selected by users
|
||||
properties:
|
||||
pk:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
verbose_name:
|
||||
type: string
|
||||
meta_model_name:
|
||||
type: string
|
||||
required:
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
- verbose_name
|
||||
ServiceConnection:
|
||||
type: object
|
||||
description: ServiceConnection Serializer
|
||||
|
@ -42702,43 +42148,6 @@ components:
|
|||
additionalProperties: {}
|
||||
required:
|
||||
- name
|
||||
UserLoginChallenge:
|
||||
type: object
|
||||
description: Empty challenge
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-user-login
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
pending_user:
|
||||
type: string
|
||||
pending_user_avatar:
|
||||
type: string
|
||||
required:
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
- type
|
||||
UserLoginChallengeResponseRequest:
|
||||
type: object
|
||||
description: User login challenge
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-stage-user-login
|
||||
remember_me:
|
||||
type: boolean
|
||||
required:
|
||||
- remember_me
|
||||
UserLoginStage:
|
||||
type: object
|
||||
description: UserLoginStage Serializer
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
"name": "@goauthentik/web-tests",
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"@wdio/cli": "^8.26.1",
|
||||
"@wdio/local-runner": "^8.26.1",
|
||||
"@wdio/mocha-framework": "^8.24.12",
|
||||
"@wdio/spec-reporter": "^8.24.12",
|
||||
"eslint": "^8.55.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||
"@typescript-eslint/parser": "^6.15.0",
|
||||
"@wdio/cli": "^8.26.3",
|
||||
"@wdio/local-runner": "^8.26.3",
|
||||
"@wdio/mocha-framework": "^8.26.3",
|
||||
"@wdio/spec-reporter": "^8.26.3",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-sonarjs": "^0.23.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
@ -382,9 +382,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
|
||||
"integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
|
||||
"version": "8.56.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
|
||||
"integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
|
@ -946,16 +946,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.14.0.tgz",
|
||||
"integrity": "sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==",
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz",
|
||||
"integrity": "sha512-j5qoikQqPccq9QoBAupOP+CBu8BaJ8BLjaXSioDISeTZkVO3ig7oSIKh3H+rEpee7xCXtWwSB4KIL5l6hWZzpg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.5.1",
|
||||
"@typescript-eslint/scope-manager": "6.14.0",
|
||||
"@typescript-eslint/type-utils": "6.14.0",
|
||||
"@typescript-eslint/utils": "6.14.0",
|
||||
"@typescript-eslint/visitor-keys": "6.14.0",
|
||||
"@typescript-eslint/scope-manager": "6.15.0",
|
||||
"@typescript-eslint/type-utils": "6.15.0",
|
||||
"@typescript-eslint/utils": "6.15.0",
|
||||
"@typescript-eslint/visitor-keys": "6.15.0",
|
||||
"debug": "^4.3.4",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.2.4",
|
||||
|
@ -981,15 +981,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.14.0.tgz",
|
||||
"integrity": "sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==",
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz",
|
||||
"integrity": "sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "6.14.0",
|
||||
"@typescript-eslint/types": "6.14.0",
|
||||
"@typescript-eslint/typescript-estree": "6.14.0",
|
||||
"@typescript-eslint/visitor-keys": "6.14.0",
|
||||
"@typescript-eslint/scope-manager": "6.15.0",
|
||||
"@typescript-eslint/types": "6.15.0",
|
||||
"@typescript-eslint/typescript-estree": "6.15.0",
|
||||
"@typescript-eslint/visitor-keys": "6.15.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1009,13 +1009,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.14.0.tgz",
|
||||
"integrity": "sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==",
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.15.0.tgz",
|
||||
"integrity": "sha512-+BdvxYBltqrmgCNu4Li+fGDIkW9n//NrruzG9X1vBzaNK+ExVXPoGB71kneaVw/Jp+4rH/vaMAGC6JfMbHstVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.14.0",
|
||||
"@typescript-eslint/visitor-keys": "6.14.0"
|
||||
"@typescript-eslint/types": "6.15.0",
|
||||
"@typescript-eslint/visitor-keys": "6.15.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.0.0 || >=18.0.0"
|
||||
|
@ -1026,13 +1026,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.14.0.tgz",
|
||||
"integrity": "sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==",
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.15.0.tgz",
|
||||
"integrity": "sha512-CnmHKTfX6450Bo49hPg2OkIm/D/TVYV7jO1MCfPYGwf6x3GO0VU8YMO5AYMn+u3X05lRRxA4fWCz87GFQV6yVQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "6.14.0",
|
||||
"@typescript-eslint/utils": "6.14.0",
|
||||
"@typescript-eslint/typescript-estree": "6.15.0",
|
||||
"@typescript-eslint/utils": "6.15.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.0.1"
|
||||
},
|
||||
|
@ -1053,9 +1053,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.14.0.tgz",
|
||||
"integrity": "sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==",
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.15.0.tgz",
|
||||
"integrity": "sha512-yXjbt//E4T/ee8Ia1b5mGlbNj9fB9lJP4jqLbZualwpP2BCQ5is6BcWwxpIsY4XKAhmdv3hrW92GdtJbatC6dQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^16.0.0 || >=18.0.0"
|
||||
|
@ -1066,13 +1066,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.14.0.tgz",
|
||||
"integrity": "sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==",
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.15.0.tgz",
|
||||
"integrity": "sha512-7mVZJN7Hd15OmGuWrp2T9UvqR2Ecg+1j/Bp1jXUEY2GZKV6FXlOIoqVDmLpBiEiq3katvj/2n2mR0SDwtloCew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.14.0",
|
||||
"@typescript-eslint/visitor-keys": "6.14.0",
|
||||
"@typescript-eslint/types": "6.15.0",
|
||||
"@typescript-eslint/visitor-keys": "6.15.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
|
@ -1093,17 +1093,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.14.0.tgz",
|
||||
"integrity": "sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==",
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz",
|
||||
"integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@types/json-schema": "^7.0.12",
|
||||
"@types/semver": "^7.5.0",
|
||||
"@typescript-eslint/scope-manager": "6.14.0",
|
||||
"@typescript-eslint/types": "6.14.0",
|
||||
"@typescript-eslint/typescript-estree": "6.14.0",
|
||||
"@typescript-eslint/scope-manager": "6.15.0",
|
||||
"@typescript-eslint/types": "6.15.0",
|
||||
"@typescript-eslint/typescript-estree": "6.15.0",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1118,12 +1118,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.14.0.tgz",
|
||||
"integrity": "sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==",
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.15.0.tgz",
|
||||
"integrity": "sha512-1zvtdC1a9h5Tb5jU9x3ADNXO9yjP8rXlaoChu0DQX40vf5ACVpYIVIZhIMZ6d5sDXH7vq4dsZBT1fEGj8D2n2w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.14.0",
|
||||
"@typescript-eslint/types": "6.15.0",
|
||||
"eslint-visitor-keys": "^3.4.1"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1141,18 +1141,18 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@wdio/cli": {
|
||||
"version": "8.26.1",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.26.1.tgz",
|
||||
"integrity": "sha512-KZ3MVyH4N6j0Gdy6RL6Wv0uf5OeggFe0WRpOwZFjjQpYVEV8IEuB4kDcw8ld7f3kp9YYQGabMAkGrO6tnz5T8w==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.26.3.tgz",
|
||||
"integrity": "sha512-wrq145sNBw4DrsF5GEK8TBxqVWln7GZpNpM5QeDqCcZzVHIqDud4f7nADgZGbR8dJ96NVfC3rby6wbeRQUA+eg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.1",
|
||||
"@wdio/config": "8.24.12",
|
||||
"@wdio/globals": "8.26.1",
|
||||
"@wdio/config": "8.26.3",
|
||||
"@wdio/globals": "8.26.3",
|
||||
"@wdio/logger": "8.24.12",
|
||||
"@wdio/protocols": "8.24.12",
|
||||
"@wdio/types": "8.24.12",
|
||||
"@wdio/utils": "8.24.12",
|
||||
"@wdio/types": "8.26.3",
|
||||
"@wdio/utils": "8.26.3",
|
||||
"async-exit-hook": "^2.0.1",
|
||||
"chalk": "^5.2.0",
|
||||
"chokidar": "^3.5.3",
|
||||
|
@ -1167,7 +1167,7 @@
|
|||
"lodash.union": "^4.6.0",
|
||||
"read-pkg-up": "^10.0.0",
|
||||
"recursive-readdir": "^2.2.3",
|
||||
"webdriverio": "8.26.1",
|
||||
"webdriverio": "8.26.3",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"bin": {
|
||||
|
@ -1190,14 +1190,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@wdio/config": {
|
||||
"version": "8.24.12",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.24.12.tgz",
|
||||
"integrity": "sha512-3HW7qG1rIHzOIybV6oHR1CqLghsN0G3Xzs90ZciGL8dYhtcLtYCHwuWmBw4mkaB5xViU4AmZDuj7ChiG8Cr6Qw==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.26.3.tgz",
|
||||
"integrity": "sha512-NWh2JXRSyP4gY+jeC79u0L3hSXW/s3rOWez4M6qAglT91fZTXFbIl1GM8lnZlCq03ye2qFPqYrZ+4tGNQj7YxQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@wdio/logger": "8.24.12",
|
||||
"@wdio/types": "8.24.12",
|
||||
"@wdio/utils": "8.24.12",
|
||||
"@wdio/types": "8.26.3",
|
||||
"@wdio/utils": "8.26.3",
|
||||
"decamelize": "^6.0.0",
|
||||
"deepmerge-ts": "^5.0.0",
|
||||
"glob": "^10.2.2",
|
||||
|
@ -1208,29 +1208,29 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@wdio/globals": {
|
||||
"version": "8.26.1",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.26.1.tgz",
|
||||
"integrity": "sha512-hNJ4mvSHzvAzDcBisaNgwRzJ2sLN4B/fno6VZcskgfYlg7UyWpVHigyxaddP2e1OeoxLL9pc2hKCtwgDr4UozA==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.26.3.tgz",
|
||||
"integrity": "sha512-RW3UsvnUb4DjxVOqIngXQMcDJlbH+QL/LeChznUF0FW+Mqg/mZWukBld5/dDwgQHk9F2TOzc8ctk5FM3s1AoWQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^16.13 || >=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"expect-webdriverio": "^4.6.1",
|
||||
"webdriverio": "8.26.1"
|
||||
"webdriverio": "8.26.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/local-runner": {
|
||||
"version": "8.26.1",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.26.1.tgz",
|
||||
"integrity": "sha512-n7iiB/mKt7u6bd3uJTgqOTaN2r/EUVQpBMsrXyv5XidYEr9QHuq2OOE3biswdxSez24p0zZGU35fu6oUwz5J1Q==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.26.3.tgz",
|
||||
"integrity": "sha512-YWxTBp6tc8Dlz09rnRhV2GXV4b3w5G0WyYEf81D+kXDI3QxDvYn6QujByr6Q7fQ9yLwJU4ptnT2uL5IbAwVo2A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0",
|
||||
"@wdio/logger": "8.24.12",
|
||||
"@wdio/repl": "8.24.12",
|
||||
"@wdio/runner": "8.26.1",
|
||||
"@wdio/types": "8.24.12",
|
||||
"@wdio/runner": "8.26.3",
|
||||
"@wdio/types": "8.26.3",
|
||||
"async-exit-hook": "^2.0.1",
|
||||
"split2": "^4.1.0",
|
||||
"stream-buffers": "^3.0.2"
|
||||
|
@ -1267,16 +1267,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@wdio/mocha-framework": {
|
||||
"version": "8.24.12",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.24.12.tgz",
|
||||
"integrity": "sha512-SHN7CYZnDkVUNYxLp8iMV92xcmU/4gq5dqA0pRrK4m5nIU7BoL0flm0kA+ydYUQyNedQh2ru1V63uNyTOyCKAg==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.26.3.tgz",
|
||||
"integrity": "sha512-r9uHUcXhh6TKFqTBCacnbtQx0nZjFsnV9Pony8CY9qcNCn2q666ucQbrtMfZdjuevhn5N0E710+El4eAvK3jyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/node": "^20.1.0",
|
||||
"@wdio/logger": "8.24.12",
|
||||
"@wdio/types": "8.24.12",
|
||||
"@wdio/utils": "8.24.12",
|
||||
"@wdio/types": "8.26.3",
|
||||
"@wdio/utils": "8.26.3",
|
||||
"mocha": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1302,14 +1302,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@wdio/reporter": {
|
||||
"version": "8.24.12",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.24.12.tgz",
|
||||
"integrity": "sha512-FtLzDTBXdgxXf4T9HJQ2bNpYYSKEw//jojFm9XzB4fPwzPeFY3HC+dbePucVW1SSLrVzVxqIOyHiwCLqQ/4cQw==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.26.3.tgz",
|
||||
"integrity": "sha512-F/sF1Hwxp1osM2wto4JydONQGxqkbOhwbLM0o4Y8eHPgK7/m+Kn9uygBDqPVpgQnpf0kUfhpICe9gaZzG4Jt+g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0",
|
||||
"@wdio/logger": "8.24.12",
|
||||
"@wdio/types": "8.24.12",
|
||||
"@wdio/types": "8.26.3",
|
||||
"diff": "^5.0.0",
|
||||
"object-inspect": "^1.12.0"
|
||||
},
|
||||
|
@ -1318,35 +1318,35 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@wdio/runner": {
|
||||
"version": "8.26.1",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.26.1.tgz",
|
||||
"integrity": "sha512-Uhz82HD53LvfW6hLwd/cX+vYVZpiKtAr7ipTxaLt5VPEIwcDlrVz1hoVkVMGyfvkAuHxDKLZx16bigfMg+5eTQ==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.26.3.tgz",
|
||||
"integrity": "sha512-mbZGkBbXTRtj1hL5QUbNxpJvhE4rkXvYlUuea1uOVk3e2/+k2dZeGeKPgh1Q7Dt07118dfujCB7pQCYldE/dGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0",
|
||||
"@wdio/config": "8.24.12",
|
||||
"@wdio/globals": "8.26.1",
|
||||
"@wdio/config": "8.26.3",
|
||||
"@wdio/globals": "8.26.3",
|
||||
"@wdio/logger": "8.24.12",
|
||||
"@wdio/types": "8.24.12",
|
||||
"@wdio/utils": "8.24.12",
|
||||
"@wdio/types": "8.26.3",
|
||||
"@wdio/utils": "8.26.3",
|
||||
"deepmerge-ts": "^5.0.0",
|
||||
"expect-webdriverio": "^4.6.1",
|
||||
"gaze": "^1.1.2",
|
||||
"webdriver": "8.24.12",
|
||||
"webdriverio": "8.26.1"
|
||||
"webdriver": "8.26.3",
|
||||
"webdriverio": "8.26.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.13 || >=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/spec-reporter": {
|
||||
"version": "8.24.12",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.24.12.tgz",
|
||||
"integrity": "sha512-Ng3ErWK8eESamCYwIr2Uv49+46RvmT8FnmGaJ6irJoAp101K8zENEs1pyqYHJReucN+ka/wM87blfc2k8NEHCA==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.26.3.tgz",
|
||||
"integrity": "sha512-YfKlBOmxGyJk08BpDnsjPsp85XyhG7Cu2qoAVxtJ8kkJOZaGfUg9TBV9DXDqvdZcxCMnPfDfQIda6LzfkZf58Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@wdio/reporter": "8.24.12",
|
||||
"@wdio/types": "8.24.12",
|
||||
"@wdio/reporter": "8.26.3",
|
||||
"@wdio/types": "8.26.3",
|
||||
"chalk": "^5.1.2",
|
||||
"easy-table": "^1.2.0",
|
||||
"pretty-ms": "^7.0.0"
|
||||
|
@ -1368,9 +1368,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@wdio/types": {
|
||||
"version": "8.24.12",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.24.12.tgz",
|
||||
"integrity": "sha512-SaD3OacDiW06DvSgAQ7sDBbpiI9qZRg7eoVYeBg3uSGVtUq84vTETRhhV7D6xTC00IqZu+mmN2TY5/q+7Gqy7w==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.26.3.tgz",
|
||||
"integrity": "sha512-WOxvSV4sKJ5QCRNTJHeKCzgO2TZmcK1eDlJ+FObt9Pnt+4pCRy/881eVY/Aj2bozn2hhzq0AK/h6oPAUV/gjCg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0"
|
||||
|
@ -1380,14 +1380,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@wdio/utils": {
|
||||
"version": "8.24.12",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.24.12.tgz",
|
||||
"integrity": "sha512-uzwZyBVgqz0Wz1KL3aOUaQsxT8TNkzxti4NNTSMrU256qAPqc/n75rB7V73QASapCMpy70mZZTsuPgQYYj4ytQ==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.26.3.tgz",
|
||||
"integrity": "sha512-LA/iCKgJQemAAXoN6vHyKBtngdkFUWEnjB8Yd1Xm3gUQTvY4GVlvcqOxC2RF5Th7/L2LNxc6TWuErYv/mm5H+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "^1.6.0",
|
||||
"@wdio/logger": "8.24.12",
|
||||
"@wdio/types": "8.24.12",
|
||||
"@wdio/types": "8.26.3",
|
||||
"decamelize": "^6.0.0",
|
||||
"deepmerge-ts": "^5.1.0",
|
||||
"edgedriver": "^5.3.5",
|
||||
|
@ -2456,9 +2456,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/devtools-protocol": {
|
||||
"version": "0.0.1233178",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1233178.tgz",
|
||||
"integrity": "sha512-jmMfyaqlzddwmDaSR1AQ+5ek+f7rupZdxKuPdkRcoxrZoF70Idg/4dTgXA08TLPmwAwB54gh49Wm2l/gRM0eUg==",
|
||||
"version": "0.0.1237913",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1237913.tgz",
|
||||
"integrity": "sha512-Pxtmz2ZIqBkpU82HaIdsvCQBG94yTC4xajrEsWx9p38QKEfBCJktSazsHkrjf9j3dVVNPhg5LR21F6KWeXpjiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/diff": {
|
||||
|
@ -2806,15 +2806,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
|
||||
"integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
|
||||
"version": "8.56.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
|
||||
"integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
"@eslint/eslintrc": "^2.1.4",
|
||||
"@eslint/js": "8.55.0",
|
||||
"@eslint/js": "8.56.0",
|
||||
"@humanwhocodes/config-array": "^0.11.13",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
|
@ -3887,6 +3887,43 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/got": {
|
||||
"version": "12.6.1",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz",
|
||||
"integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^5.2.0",
|
||||
"@szmarczak/http-timer": "^5.0.1",
|
||||
"cacheable-lookup": "^7.0.0",
|
||||
"cacheable-request": "^10.2.8",
|
||||
"decompress-response": "^6.0.0",
|
||||
"form-data-encoder": "^2.1.2",
|
||||
"get-stream": "^6.0.1",
|
||||
"http2-wrapper": "^2.1.10",
|
||||
"lowercase-keys": "^3.0.0",
|
||||
"p-cancelable": "^3.0.0",
|
||||
"responselike": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/got?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/got/node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
|
@ -8458,18 +8495,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/webdriver": {
|
||||
"version": "8.24.12",
|
||||
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.24.12.tgz",
|
||||
"integrity": "sha512-03DQIClHoaAqTsmDkxGwo4HwHfkn9LzJ1wfNyUerzKg8DnyXeiT6ILqj6EXLfsvh5zddU2vhYGLFXSerPgkuOQ==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.26.3.tgz",
|
||||
"integrity": "sha512-vHbMj0BFXPMtKVmJsVIkFVbdOT8eXkjFeJ7LmJL8cMMe1S5Lt44DqRjSBBoGsqYoYgIBmKpqAQcDrHrv9m7smQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0",
|
||||
"@types/ws": "^8.5.3",
|
||||
"@wdio/config": "8.24.12",
|
||||
"@wdio/config": "8.26.3",
|
||||
"@wdio/logger": "8.24.12",
|
||||
"@wdio/protocols": "8.24.12",
|
||||
"@wdio/types": "8.24.12",
|
||||
"@wdio/utils": "8.24.12",
|
||||
"@wdio/types": "8.26.3",
|
||||
"@wdio/utils": "8.26.3",
|
||||
"deepmerge-ts": "^5.1.0",
|
||||
"got": "^12.6.1",
|
||||
"ky": "^0.33.0",
|
||||
|
@ -8479,61 +8516,24 @@
|
|||
"node": "^16.13 || >=18"
|
||||
}
|
||||
},
|
||||
"node_modules/webdriver/node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/webdriver/node_modules/got": {
|
||||
"version": "12.6.1",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz",
|
||||
"integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^5.2.0",
|
||||
"@szmarczak/http-timer": "^5.0.1",
|
||||
"cacheable-lookup": "^7.0.0",
|
||||
"cacheable-request": "^10.2.8",
|
||||
"decompress-response": "^6.0.0",
|
||||
"form-data-encoder": "^2.1.2",
|
||||
"get-stream": "^6.0.1",
|
||||
"http2-wrapper": "^2.1.10",
|
||||
"lowercase-keys": "^3.0.0",
|
||||
"p-cancelable": "^3.0.0",
|
||||
"responselike": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/got?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/webdriverio": {
|
||||
"version": "8.26.1",
|
||||
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.26.1.tgz",
|
||||
"integrity": "sha512-KnM92UPqk7FmPJpZf3krHrqn0ydjSdyAMn+i4uENxLBqm1OyQ12gSKtIatt8FOP9/C+UrFXATSOd+jRkU2xMkw==",
|
||||
"version": "8.26.3",
|
||||
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.26.3.tgz",
|
||||
"integrity": "sha512-5Ka8MOQoK866EI3whiCvzD1IiKFBq9niWF3lh92uMt6ZjbUZZoe5esWIHhFsHFxT6dOOU8uXR/Gr6qsBJFZReA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0",
|
||||
"@wdio/config": "8.24.12",
|
||||
"@wdio/config": "8.26.3",
|
||||
"@wdio/logger": "8.24.12",
|
||||
"@wdio/protocols": "8.24.12",
|
||||
"@wdio/repl": "8.24.12",
|
||||
"@wdio/types": "8.24.12",
|
||||
"@wdio/utils": "8.24.12",
|
||||
"@wdio/types": "8.26.3",
|
||||
"@wdio/utils": "8.26.3",
|
||||
"archiver": "^6.0.0",
|
||||
"aria-query": "^5.0.0",
|
||||
"css-shorthand-properties": "^1.1.1",
|
||||
"css-value": "^0.0.1",
|
||||
"devtools-protocol": "^0.0.1233178",
|
||||
"devtools-protocol": "^0.0.1237913",
|
||||
"grapheme-splitter": "^1.0.2",
|
||||
"import-meta-resolve": "^4.0.0",
|
||||
"is-plain-obj": "^4.1.0",
|
||||
|
@ -8545,7 +8545,7 @@
|
|||
"resq": "^1.9.1",
|
||||
"rgb2hex": "0.2.5",
|
||||
"serialize-error": "^11.0.1",
|
||||
"webdriver": "8.24.12"
|
||||
"webdriver": "8.26.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.13 || >=18"
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"@wdio/cli": "^8.26.1",
|
||||
"@wdio/local-runner": "^8.26.1",
|
||||
"@wdio/mocha-framework": "^8.24.12",
|
||||
"@wdio/spec-reporter": "^8.24.12",
|
||||
"eslint": "^8.55.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||
"@typescript-eslint/parser": "^6.15.0",
|
||||
"@wdio/cli": "^8.26.3",
|
||||
"@wdio/local-runner": "^8.26.3",
|
||||
"@wdio/mocha-framework": "^8.26.3",
|
||||
"@wdio/spec-reporter": "^8.26.3",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-sonarjs": "^0.23.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
// THIS IS A GENERATED FILE. DO NOT EDIT BY HAND.
|
||||
//
|
||||
// This file is generated by the build-storybook-import-maps script in the UI's base directory.
|
||||
// This is a *hack* to work around an inconsistency in the way rollup, vite, and storybook
|
||||
// import CSS modules.
|
||||
//
|
||||
// Sometime around 2030 or so, the Javascript community may finally get its collective act together
|
||||
// and we'll have one unified way of doing this. I can only hope.
|
||||
|
||||
export const cssImportMaps = {
|
||||
'import AKGlobal from "@goauthentik/common/styles/authentik.css";':
|
||||
'import AKGlobal from "@goauthentik/common/styles/authentik.css?inline";',
|
||||
'import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";':
|
||||
'import PFAlert from "@patternfly/patternfly/components/Alert/alert.css?inline";',
|
||||
'import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css";':
|
||||
'import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css?inline";',
|
||||
'import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";':
|
||||
'import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css?inline";',
|
||||
'import PFBackdrop from "@patternfly/patternfly/components/Backdrop/backdrop.css";':
|
||||
'import PFBackdrop from "@patternfly/patternfly/components/Backdrop/backdrop.css?inline";',
|
||||
'import PFBackgroundImage from "@patternfly/patternfly/components/BackgroundImage/background-image.css";':
|
||||
'import PFBackgroundImage from "@patternfly/patternfly/components/BackgroundImage/background-image.css?inline";',
|
||||
'import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";':
|
||||
'import PFBanner from "@patternfly/patternfly/components/Banner/banner.css?inline";',
|
||||
'import PFBase from "@patternfly/patternfly/patternfly-base.css";':
|
||||
'import PFBase from "@patternfly/patternfly/patternfly-base.css?inline";',
|
||||
'import PFBrand from "@patternfly/patternfly/components/Brand/brand.css";':
|
||||
'import PFBrand from "@patternfly/patternfly/components/Brand/brand.css?inline";',
|
||||
'import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";':
|
||||
'import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css?inline";',
|
||||
'import PFButton from "@patternfly/patternfly/components/Button/button.css";':
|
||||
'import PFButton from "@patternfly/patternfly/components/Button/button.css?inline";',
|
||||
'import PFCard from "@patternfly/patternfly/components/Card/card.css";':
|
||||
'import PFCard from "@patternfly/patternfly/components/Card/card.css?inline";',
|
||||
'import PFCheck from "@patternfly/patternfly/components/Check/check.css";':
|
||||
'import PFCheck from "@patternfly/patternfly/components/Check/check.css?inline";',
|
||||
'import PFChip from "@patternfly/patternfly/components/Chip/chip.css";':
|
||||
'import PFChip from "@patternfly/patternfly/components/Chip/chip.css?inline";',
|
||||
'import PFChipGroup from "@patternfly/patternfly/components/ChipGroup/chip-group.css";':
|
||||
'import PFChipGroup from "@patternfly/patternfly/components/ChipGroup/chip-group.css?inline";',
|
||||
'import PFContent from "@patternfly/patternfly/components/Content/content.css";':
|
||||
'import PFContent from "@patternfly/patternfly/components/Content/content.css?inline";',
|
||||
'import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css";':
|
||||
'import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css?inline";',
|
||||
'import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";':
|
||||
'import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css?inline";',
|
||||
'import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";':
|
||||
'import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css?inline";',
|
||||
'import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";':
|
||||
'import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css?inline";',
|
||||
'import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";':
|
||||
'import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css?inline";',
|
||||
'import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";':
|
||||
'import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css?inline";',
|
||||
'import PFExpandableSection from "@patternfly/patternfly/components/ExpandableSection/expandable-section.css";':
|
||||
'import PFExpandableSection from "@patternfly/patternfly/components/ExpandableSection/expandable-section.css?inline";',
|
||||
'import PFFAIcons from "@patternfly/patternfly/base/patternfly-fa-icons.css";':
|
||||
'import PFFAIcons from "@patternfly/patternfly/base/patternfly-fa-icons.css?inline";',
|
||||
'import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";':
|
||||
'import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css?inline";',
|
||||
'import PFForm from "@patternfly/patternfly/components/Form/form.css";':
|
||||
'import PFForm from "@patternfly/patternfly/components/Form/form.css?inline";',
|
||||
'import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";':
|
||||
'import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css?inline";',
|
||||
'import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";':
|
||||
'import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css?inline";',
|
||||
'import PFGlobal from "@patternfly/patternfly/patternfly-base.css";':
|
||||
'import PFGlobal from "@patternfly/patternfly/patternfly-base.css?inline";',
|
||||
'import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";':
|
||||
'import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css?inline";',
|
||||
'import PFHint from "@patternfly/patternfly/components/Hint/hint.css";':
|
||||
'import PFHint from "@patternfly/patternfly/components/Hint/hint.css?inline";',
|
||||
'import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css";':
|
||||
'import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css?inline";',
|
||||
'import PFLabel from "@patternfly/patternfly/components/Label/label.css";':
|
||||
'import PFLabel from "@patternfly/patternfly/components/Label/label.css?inline";',
|
||||
'import PFList from "@patternfly/patternfly/components/List/list.css";':
|
||||
'import PFList from "@patternfly/patternfly/components/List/list.css?inline";',
|
||||
'import PFLogin from "@patternfly/patternfly/components/Login/login.css";':
|
||||
'import PFLogin from "@patternfly/patternfly/components/Login/login.css?inline";',
|
||||
'import PFModalBox from "@patternfly/patternfly/components/ModalBox/modal-box.css";':
|
||||
'import PFModalBox from "@patternfly/patternfly/components/ModalBox/modal-box.css?inline";',
|
||||
'import PFNav from "@patternfly/patternfly/components/Nav/nav.css";':
|
||||
'import PFNav from "@patternfly/patternfly/components/Nav/nav.css?inline";',
|
||||
'import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";':
|
||||
'import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css?inline";',
|
||||
'import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css";':
|
||||
'import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css?inline";',
|
||||
'import PFPage from "@patternfly/patternfly/components/Page/page.css";':
|
||||
'import PFPage from "@patternfly/patternfly/components/Page/page.css?inline";',
|
||||
'import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css";':
|
||||
'import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css?inline";',
|
||||
'import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css";':
|
||||
'import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css?inline";',
|
||||
'import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";':
|
||||
'import PFRadio from "@patternfly/patternfly/components/Radio/radio.css?inline";',
|
||||
'import PFSelect from "@patternfly/patternfly/components/Select/select.css";':
|
||||
'import PFSelect from "@patternfly/patternfly/components/Select/select.css?inline";',
|
||||
'import PFSidebar from "@patternfly/patternfly/components/Sidebar/sidebar.css";':
|
||||
'import PFSidebar from "@patternfly/patternfly/components/Sidebar/sidebar.css?inline";',
|
||||
'import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";':
|
||||
'import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css?inline";',
|
||||
'import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";':
|
||||
'import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css?inline";',
|
||||
'import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";':
|
||||
'import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css?inline";',
|
||||
'import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css";':
|
||||
'import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css?inline";',
|
||||
'import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css";':
|
||||
'import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css?inline";',
|
||||
'import PFTable from "@patternfly/patternfly/components/Table/table.css";':
|
||||
'import PFTable from "@patternfly/patternfly/components/Table/table.css?inline";',
|
||||
'import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css";':
|
||||
'import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css?inline";',
|
||||
'import PFTitle from "@patternfly/patternfly/components/Title/title.css";':
|
||||
'import PFTitle from "@patternfly/patternfly/components/Title/title.css?inline";',
|
||||
'import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";':
|
||||
'import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css?inline";',
|
||||
'import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css";':
|
||||
'import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css?inline";',
|
||||
'import PFTreeView from "@patternfly/patternfly/components/TreeView/tree-view.css";':
|
||||
'import PFTreeView from "@patternfly/patternfly/components/TreeView/tree-view.css?inline";',
|
||||
'import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css";':
|
||||
'import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css?inline";',
|
||||
'import ThemeDark from "@goauthentik/common/styles/theme-dark.css";':
|
||||
'import ThemeDark from "@goauthentik/common/styles/theme-dark.css?inline";',
|
||||
'import styles from "./LibraryPageImpl.css";':
|
||||
'import styles from "./LibraryPageImpl.css?inline";',
|
||||
};
|
|
@ -1,9 +1,12 @@
|
|||
import replace from "@rollup/plugin-replace";
|
||||
import type { StorybookConfig } from "@storybook/web-components-vite";
|
||||
import { cwd } from "process";
|
||||
import modify from "rollup-plugin-modify";
|
||||
import postcssLit from "rollup-plugin-postcss-lit";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
import { cssImportMaps } from "./css-import-maps";
|
||||
|
||||
export const isProdBuild = process.env.NODE_ENV === "production";
|
||||
export const apiBasePath = process.env.AK_API_BASE_PATH || "";
|
||||
|
||||
|
@ -27,9 +30,7 @@ const config: StorybookConfig = {
|
|||
return {
|
||||
...config,
|
||||
plugins: [
|
||||
...config.plugins,
|
||||
postcssLit(),
|
||||
tsconfigPaths(),
|
||||
modify(cssImportMaps),
|
||||
replace({
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProdBuild ? "production" : "development",
|
||||
|
@ -38,6 +39,9 @@ const config: StorybookConfig = {
|
|||
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
|
||||
"preventAssignment": true,
|
||||
}),
|
||||
...config.plugins,
|
||||
postcssLit(),
|
||||
tsconfigPaths(),
|
||||
],
|
||||
};
|
||||
},
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -28,7 +28,10 @@
|
|||
"tsc:execute": "tsc --noEmit -p .",
|
||||
"tsc": "run-s build-locales tsc:execute",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' storybook build"
|
||||
"storybook:build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' storybook build",
|
||||
"storybook:build-import-map": "run-s storybook:build-import-map-script storybook:run-import-map-script",
|
||||
"storybook:build-import-map-script": "cd scripts && tsc --esModuleInterop --module es2020 --target es2020 --moduleResolution 'node' build-storybook-import-maps.ts && mv build-storybook-import-maps.js build-storybook-import-maps.mjs",
|
||||
"storybook:run-import-map-script": "node scripts/build-storybook-import-maps.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.4.7",
|
||||
|
@ -39,7 +42,7 @@
|
|||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@formatjs/intl-listformat": "^7.5.3",
|
||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||
"@goauthentik/api": "^2023.10.4-1701882394",
|
||||
"@goauthentik/api": "^2023.10.4-1702989148",
|
||||
"@lit-labs/context": "^0.4.0",
|
||||
"@lit-labs/task": "^3.1.0",
|
||||
"@lit/localize": "^0.11.4",
|
||||
|
@ -83,26 +86,26 @@
|
|||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^11.1.5",
|
||||
"@storybook/addon-essentials": "^7.6.4",
|
||||
"@storybook/addon-links": "^7.6.4",
|
||||
"@storybook/api": "^7.6.4",
|
||||
"@storybook/addon-essentials": "^7.6.5",
|
||||
"@storybook/addon-links": "^7.6.5",
|
||||
"@storybook/api": "^7.6.5",
|
||||
"@storybook/blocks": "^7.6.4",
|
||||
"@storybook/manager-api": "^7.6.4",
|
||||
"@storybook/web-components": "^7.6.4",
|
||||
"@storybook/web-components-vite": "^7.6.4",
|
||||
"@storybook/manager-api": "^7.6.5",
|
||||
"@storybook/web-components": "^7.6.5",
|
||||
"@storybook/web-components-vite": "^7.6.5",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@types/chart.js": "^2.9.41",
|
||||
"@types/codemirror": "5.60.15",
|
||||
"@types/grecaptcha": "^3.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||
"@typescript-eslint/parser": "^6.15.0",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"babel-plugin-tsconfig-paths": "^1.0.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-custom-elements": "0.0.8",
|
||||
"eslint-plugin-lit": "^1.10.1",
|
||||
"eslint-plugin-lit": "^1.11.0",
|
||||
"eslint-plugin-sonarjs": "^0.23.0",
|
||||
"eslint-plugin-storybook": "^0.6.15",
|
||||
"lit-analyzer": "^2.0.2",
|
||||
|
@ -112,11 +115,12 @@
|
|||
"pyright": "=1.1.338",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"rollup": "^4.9.0",
|
||||
"rollup": "^4.9.1",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"rollup-plugin-cssimport": "^1.0.3",
|
||||
"rollup-plugin-modify": "^3.0.0",
|
||||
"rollup-plugin-postcss-lit": "^2.1.0",
|
||||
"storybook": "^7.6.4",
|
||||
"storybook": "^7.6.5",
|
||||
"storybook-addon-mock": "^4.3.0",
|
||||
"ts-lit-plugin": "^2.0.1",
|
||||
"tslib": "^2.6.2",
|
||||
|
@ -125,9 +129,9 @@
|
|||
"vite-tsconfig-paths": "^4.2.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/darwin-arm64": "^0.19.9",
|
||||
"@esbuild/darwin-arm64": "^0.19.10",
|
||||
"@esbuild/linux-amd64": "^0.18.11",
|
||||
"@esbuild/linux-arm64": "^0.19.9"
|
||||
"@esbuild/linux-arm64": "^0.19.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function* walkFilesystem(dir: string): Generator<string, undefined, any> {
|
||||
const openeddir = fs.opendirSync(dir);
|
||||
if (!openeddir) {
|
||||
return;
|
||||
}
|
||||
|
||||
let d: fs.Dirent | null;
|
||||
while ((d = openeddir?.readSync())) {
|
||||
if (!d) {
|
||||
break;
|
||||
}
|
||||
const entry = path.join(dir, d.name);
|
||||
if (d.isDirectory()) yield* walkFilesystem(entry);
|
||||
else if (d.isFile()) yield entry;
|
||||
}
|
||||
openeddir.close();
|
||||
}
|
||||
|
||||
const import_re = /^(import \w+ from .*\.css)";/;
|
||||
function extractImportLinesFromFile(path: string) {
|
||||
const source = fs.readFileSync(path, { encoding: "utf8", flag: "r" });
|
||||
const lines = source?.split("\n") ?? [];
|
||||
return lines.filter((l) => import_re.test(l));
|
||||
}
|
||||
|
||||
function createOneImportLine(line: string) {
|
||||
const importMatch = import_re.exec(line);
|
||||
if (!importMatch) {
|
||||
throw new Error("How did an unmatchable line get here?");
|
||||
}
|
||||
const importContent = importMatch[1];
|
||||
if (!importContent) {
|
||||
throw new Error("How did an unmatchable line get here!?");
|
||||
}
|
||||
return `'${importContent}";': '${importContent}?inline";',`;
|
||||
}
|
||||
|
||||
const isSourceFile = /\.ts$/;
|
||||
function getTheSourceFiles() {
|
||||
return Array.from(walkFilesystem(path.join(__dirname, "..", "src"))).filter((path) =>
|
||||
isSourceFile.test(path),
|
||||
);
|
||||
}
|
||||
|
||||
function getTheImportLines(importPaths: string[]) {
|
||||
const importLines: string[] = importPaths.reduce(
|
||||
(acc: string[], path) => [...acc, extractImportLinesFromFile(path)].flat(),
|
||||
[],
|
||||
);
|
||||
const uniqueImportLines = new Set(importLines);
|
||||
const sortedImportLines = Array.from(uniqueImportLines.keys());
|
||||
sortedImportLines.sort();
|
||||
return sortedImportLines;
|
||||
}
|
||||
|
||||
const importPaths = getTheSourceFiles();
|
||||
const importLines = getTheImportLines(importPaths);
|
||||
|
||||
const outputFile = `
|
||||
// THIS IS A GENERATED FILE. DO NOT EDIT BY HAND.
|
||||
//
|
||||
// This file is generated by the build-storybook-import-maps script in the UI's base directory.
|
||||
// This is a *hack* to work around an inconsistency in the way rollup, vite, and storybook
|
||||
// import CSS modules.
|
||||
//
|
||||
// Sometime around 2030 or so, the Javascript community may finally get its collective act together
|
||||
// and we'll have one unified way of doing this. I can only hope.
|
||||
|
||||
export const cssImportMaps = {
|
||||
${importLines.map(createOneImportLine).join("\n")}
|
||||
};
|
||||
`;
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, "..", ".storybook", "css-import-maps.ts"), outputFile, {
|
||||
encoding: "utf8",
|
||||
flag: "w",
|
||||
});
|
|
@ -18,8 +18,8 @@ import {
|
|||
DeniedActionEnum,
|
||||
Flow,
|
||||
FlowDesignationEnum,
|
||||
FlowLayoutEnum,
|
||||
FlowsApi,
|
||||
LayoutEnum,
|
||||
PolicyEngineMode,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
|
@ -196,6 +196,13 @@ export class FlowForm extends ModelForm<Flow, string> {
|
|||
>
|
||||
${msg("Require superuser.")}
|
||||
</option>
|
||||
<option
|
||||
value=${AuthenticationEnum.RequireOutpost}
|
||||
?selected=${this.instance?.authentication ===
|
||||
AuthenticationEnum.RequireOutpost}
|
||||
>
|
||||
${msg("Require Outpost (flow can only be executed from an outpost).")}
|
||||
</option>
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Required authentication level for this flow.")}
|
||||
|
@ -302,34 +309,34 @@ export class FlowForm extends ModelForm<Flow, string> {
|
|||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value=${LayoutEnum.Stacked}
|
||||
?selected=${this.instance?.layout === LayoutEnum.Stacked}
|
||||
value=${FlowLayoutEnum.Stacked}
|
||||
?selected=${this.instance?.layout === FlowLayoutEnum.Stacked}
|
||||
>
|
||||
${LayoutToLabel(LayoutEnum.Stacked)}
|
||||
${LayoutToLabel(FlowLayoutEnum.Stacked)}
|
||||
</option>
|
||||
<option
|
||||
value=${LayoutEnum.ContentLeft}
|
||||
?selected=${this.instance?.layout === LayoutEnum.ContentLeft}
|
||||
value=${FlowLayoutEnum.ContentLeft}
|
||||
?selected=${this.instance?.layout === FlowLayoutEnum.ContentLeft}
|
||||
>
|
||||
${LayoutToLabel(LayoutEnum.ContentLeft)}
|
||||
${LayoutToLabel(FlowLayoutEnum.ContentLeft)}
|
||||
</option>
|
||||
<option
|
||||
value=${LayoutEnum.ContentRight}
|
||||
?selected=${this.instance?.layout === LayoutEnum.ContentRight}
|
||||
value=${FlowLayoutEnum.ContentRight}
|
||||
?selected=${this.instance?.layout === FlowLayoutEnum.ContentRight}
|
||||
>
|
||||
${LayoutToLabel(LayoutEnum.ContentRight)}
|
||||
${LayoutToLabel(FlowLayoutEnum.ContentRight)}
|
||||
</option>
|
||||
<option
|
||||
value=${LayoutEnum.SidebarLeft}
|
||||
?selected=${this.instance?.layout === LayoutEnum.SidebarLeft}
|
||||
value=${FlowLayoutEnum.SidebarLeft}
|
||||
?selected=${this.instance?.layout === FlowLayoutEnum.SidebarLeft}
|
||||
>
|
||||
${LayoutToLabel(LayoutEnum.SidebarLeft)}
|
||||
${LayoutToLabel(FlowLayoutEnum.SidebarLeft)}
|
||||
</option>
|
||||
<option
|
||||
value=${LayoutEnum.SidebarRight}
|
||||
?selected=${this.instance?.layout === LayoutEnum.SidebarRight}
|
||||
value=${FlowLayoutEnum.SidebarRight}
|
||||
?selected=${this.instance?.layout === FlowLayoutEnum.SidebarRight}
|
||||
>
|
||||
${LayoutToLabel(LayoutEnum.SidebarRight)}
|
||||
${LayoutToLabel(FlowLayoutEnum.SidebarRight)}
|
||||
</option>
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { msg } from "@lit/localize";
|
||||
|
||||
import { Flow, FlowDesignationEnum, LayoutEnum } from "@goauthentik/api";
|
||||
import { Flow, FlowDesignationEnum, FlowLayoutEnum } from "@goauthentik/api";
|
||||
|
||||
export function RenderFlowOption(flow: Flow): string {
|
||||
return `${flow.slug} (${flow.name})`;
|
||||
|
@ -27,19 +27,19 @@ export function DesignationToLabel(designation: FlowDesignationEnum): string {
|
|||
}
|
||||
}
|
||||
|
||||
export function LayoutToLabel(layout: LayoutEnum): string {
|
||||
export function LayoutToLabel(layout: FlowLayoutEnum): string {
|
||||
switch (layout) {
|
||||
case LayoutEnum.Stacked:
|
||||
case FlowLayoutEnum.Stacked:
|
||||
return msg("Stacked");
|
||||
case LayoutEnum.ContentLeft:
|
||||
case FlowLayoutEnum.ContentLeft:
|
||||
return msg("Content left");
|
||||
case LayoutEnum.ContentRight:
|
||||
case FlowLayoutEnum.ContentRight:
|
||||
return msg("Content right");
|
||||
case LayoutEnum.SidebarLeft:
|
||||
case FlowLayoutEnum.SidebarLeft:
|
||||
return msg("Sidebar left");
|
||||
case LayoutEnum.SidebarRight:
|
||||
case FlowLayoutEnum.SidebarRight:
|
||||
return msg("Sidebar right");
|
||||
case LayoutEnum.UnknownDefaultOpenApi:
|
||||
case FlowLayoutEnum.UnknownDefaultOpenApi:
|
||||
return msg("Unknown layout");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,3 +124,27 @@ html > form > input {
|
|||
margin-right: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* Flow-card adjustments for static pages */
|
||||
.pf-c-brand {
|
||||
padding-top: calc(
|
||||
var(--pf-c-login__main-footer-links--PaddingTop) +
|
||||
var(--pf-c-login__main-footer-links--PaddingBottom) +
|
||||
var(--pf-c-login__main-body--PaddingBottom)
|
||||
);
|
||||
max-height: 9rem;
|
||||
}
|
||||
.ak-brand {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.ak-brand img {
|
||||
padding: 0 2rem;
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
@media (min-height: 60rem) {
|
||||
.pf-c-login.stacked .pf-c-login__main {
|
||||
margin-top: 13rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,8 +37,8 @@ import {
|
|||
ContextualFlowInfo,
|
||||
FlowChallengeResponseRequest,
|
||||
FlowErrorChallenge,
|
||||
FlowLayoutEnum,
|
||||
FlowsApi,
|
||||
LayoutEnum,
|
||||
ResponseError,
|
||||
ShellChallenge,
|
||||
UiThemeEnum,
|
||||
|
@ -115,8 +115,10 @@ export class FlowExecutor extends Interface implements StageHost {
|
|||
background-color: transparent;
|
||||
}
|
||||
/* layouts */
|
||||
.pf-c-login.stacked .pf-c-login__main {
|
||||
margin-top: 13rem;
|
||||
@media (min-height: 60rem) {
|
||||
.pf-c-login.stacked .pf-c-login__main {
|
||||
margin-top: 13rem;
|
||||
}
|
||||
}
|
||||
.pf-c-login__container.content-right {
|
||||
grid-template-areas:
|
||||
|
@ -451,7 +453,7 @@ export class FlowExecutor extends Interface implements StageHost {
|
|||
}
|
||||
|
||||
getLayout(): string {
|
||||
const prefilledFlow = globalAK()?.flow?.layout || LayoutEnum.Stacked;
|
||||
const prefilledFlow = globalAK()?.flow?.layout || FlowLayoutEnum.Stacked;
|
||||
if (this.challenge) {
|
||||
return this.challenge?.flowInfo?.layout || prefilledFlow;
|
||||
}
|
||||
|
@ -461,11 +463,11 @@ export class FlowExecutor extends Interface implements StageHost {
|
|||
getLayoutClass(): string {
|
||||
const layout = this.getLayout();
|
||||
switch (layout) {
|
||||
case LayoutEnum.ContentLeft:
|
||||
case FlowLayoutEnum.ContentLeft:
|
||||
return "pf-c-login__container";
|
||||
case LayoutEnum.ContentRight:
|
||||
case FlowLayoutEnum.ContentRight:
|
||||
return "pf-c-login__container content-right";
|
||||
case LayoutEnum.Stacked:
|
||||
case FlowLayoutEnum.Stacked:
|
||||
default:
|
||||
return "ak-login-container";
|
||||
}
|
||||
|
|
10522
web/xliff/de.xlf
10522
web/xliff/de.xlf
File diff suppressed because it is too large
Load Diff
10798
web/xliff/en.xlf
10798
web/xliff/en.xlf
File diff suppressed because it is too large
Load Diff
10438
web/xliff/es.xlf
10438
web/xliff/es.xlf
File diff suppressed because it is too large
Load Diff
12680
web/xliff/fr.xlf
12680
web/xliff/fr.xlf
File diff suppressed because it is too large
Load Diff
10645
web/xliff/pl.xlf
10645
web/xliff/pl.xlf
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
10431
web/xliff/tr.xlf
10431
web/xliff/tr.xlf
File diff suppressed because it is too large
Load Diff
12682
web/xliff/zh-Hans.xlf
12682
web/xliff/zh-Hans.xlf
File diff suppressed because it is too large
Load Diff
10479
web/xliff/zh-Hant.xlf
10479
web/xliff/zh-Hant.xlf
File diff suppressed because it is too large
Load Diff
|
@ -8044,6 +8044,10 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<trans-unit id="s7513372fe60f6387">
|
||||
<source>Event volume</source>
|
||||
<target>事件容量</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s047a5f0211fedc72">
|
||||
<source>Require Outpost (flow can only be executed from an outpost).</source>
|
||||
<target>需要前哨(流程只能从前哨执行)。</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
12543
web/xliff/zh_TW.xlf
12543
web/xliff/zh_TW.xlf
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,376 @@
|
|||
---
|
||||
title: SharePoint Server SE
|
||||
---
|
||||
|
||||
<span class="badge badge--secondary">Support level: Community</span>
|
||||
|
||||
## What is Microsoft SharePoint
|
||||
|
||||
> SharePoint is a proprietary, web-based collaborative platform that integrates natively with Microsoft 365.
|
||||
>
|
||||
> Launched in 2001, SharePoint is primarily sold as a document management and storage system, although it is also used for sharing information through an intranet, implementing internal applications, and for implementing business processes.
|
||||
>
|
||||
> -- https://en.wikipedia.org/wiki/SharePoint
|
||||
|
||||
> Organizations use Microsoft SharePoint to create websites.
|
||||
>
|
||||
> You can use it as a secure place to store, organize, share, and access information from any device.
|
||||
> All you need is a web browser, such as Microsoft Edge, Internet Explorer, Chrome, or Firefox.
|
||||
>
|
||||
> -- https://support.microsoft.com/en-us/office/what-is-sharepoint-97b915e6-651b-43b2-827d-fb25777f446f
|
||||
|
||||
:::note
|
||||
There are many ways to implement SSO mechanism within Microsoft SharePoint Server Subscription Edition.
|
||||
|
||||
These guidelines provides the procedure to integrate authentik with an OIDC provider based on Microsoft documentation.
|
||||
(cf. https://learn.microsoft.com/en-us/sharepoint/security-for-sharepoint-server/set-up-oidc-auth-in-sharepoint-server-with-msaad)
|
||||
|
||||
In addition, it provides the procedure to enable claims augmentations in order to resolve group memberships.
|
||||
|
||||
For all other integration models, read Microsoft official documentation.
|
||||
(cf. https://learn.microsoft.com/en-us/sharepoint/security-for-sharepoint-server/plan-user-authentication)
|
||||
:::
|
||||
|
||||
:::caution
|
||||
This setup only works starting with **authentik** version **2023.10** and Microsoft **SharePoint** Subscription Edition starting with the **Cumulative Updates** of **September 2023**.
|
||||
:::
|
||||
|
||||
## Preparation
|
||||
|
||||
When you configure OIDC with authentik, you need the following resources:
|
||||
|
||||
1. A SharePoint Server Subscription Edition farm starting with CU of September 2023
|
||||
2. An authentik instance starting with version 2023.10
|
||||
3. (Optional) [LDAPCP](https://www.ldapcp.com/docs/overview/introduction/) installed on the target SharePoint farm
|
||||
|
||||
:::info
|
||||
Ensure that the authentik and SharePoint Server clocks are synchronized.
|
||||
:::
|
||||
|
||||
These guidelines use the following placeholders for the overall setup:
|
||||
|
||||
| Name | Placeholder | Sample value |
|
||||
| -------------------------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------- |
|
||||
| authentik Application Name | `auth.applicationName` | SharePoint SE |
|
||||
| authentik Application Slug | `auth.applicationSlug` | sharepoint-se |
|
||||
| authentik OIDC Name | `auth.providerName` | OIDC-SP |
|
||||
| authentik OIDC Configuration URL | `auth.providerConfigURL` | https://authentik.company/application/o/sharepoint-se/.well-known/openid-configuration |
|
||||
| authentik OIDC Client ID | `auth.providerClientID` | 0ab1c234d567ef8a90123bc4567890e12fa3b45c |
|
||||
| authentik OIDC Redirect URIs | `auth.providerRedirectURI` | https://sharepoint.company/.\* |
|
||||
| (Optional) authentik LDAP Outpost URI | `ldap.outpostURI` | ak-outpost-ldap.authentik.svc.cluster.local |
|
||||
| (Optional) authentik LDAP Service Account | `ldap.outpostServiceAccount` | cn=ldapservice,ou=users,dc=ldap,dc=goauthentik,dc=io |
|
||||
| (Optional) authentik LDAP Service Account Password | `ldap.outpostServiceAccountPassword` | mystrongpassword |
|
||||
| SharePoint Default Web Application URL | `sp.webAppURL` | https://sharepoint.company |
|
||||
| SharePoint Trusted Token Issuer Name | `sp.issuerName` | Authentik |
|
||||
| SharePoint Trusted Token Issuer Description | `sp.issuerDesc` | authentik IDP |
|
||||
|
||||
## authentik configuration
|
||||
|
||||
### Step 1: Create authentik OpenID Property Mappings
|
||||
|
||||
SharePoint requires additional properties within the OpenID and profile scopes in order to operate OIDC properly and be able to map incoming authentik OID Claims with Microsoft Claims.
|
||||
|
||||
Additional information from Microsoft documentation:
|
||||
|
||||
- https://learn.microsoft.com/en-us/entra/identity-platform/id-tokens#validate-tokens
|
||||
- https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference#payload-claims
|
||||
|
||||
#### Add an OpenID scope mapping for SharePoint
|
||||
|
||||
From the authentik Admin Dashboard:
|
||||
|
||||
1. Open **Customisation > Property Mappings** page from the sidebar.
|
||||
2. Click **Create** from the property mapping list command bar.
|
||||
3. Within the new property mapping form, select **Scope Mapping**.
|
||||
4. Click **Next** and enter the following values:
|
||||
- **Name**: SPopenid
|
||||
- **Scope name**: openid
|
||||
- **Expression**:
|
||||
|
||||
```python
|
||||
return {
|
||||
"nbf": "0", # Identifies the time before which the JWT can't be accepted for processing.
|
||||
# 0 stand for the date 1970-01-01 in unix timestamp
|
||||
"oid": user.uid, # This ID uniquely identifies the user across applications - two different applications signing in the same user receives the same value in the oid claim.
|
||||
"upn": user.username # (Optional) User Principal Name, used for troubleshooting within JWT tokens or to setup SharePoint like ADFS
|
||||
}
|
||||
```
|
||||
|
||||
5. Click **Finish**.
|
||||
|
||||
#### Add a profile scope mapping for SharePoint
|
||||
|
||||
From the authentik Admin Dashboard:
|
||||
|
||||
1. Open **Customisation > Property Mappings** page from the sidebar.
|
||||
2. Click **Create** from the property mapping list command bar.
|
||||
3. Within the new property mapping form, select **Scope Mapping**.
|
||||
4. Click **Next** and enter the following values:
|
||||
- **Name**: SPprofile
|
||||
- **Scope name**: profile
|
||||
- **Expression**:
|
||||
|
||||
```python
|
||||
return {
|
||||
"name": request.user.name, # The name claim provides a human-readable value that identifies the subject of the token.
|
||||
"given_name": request.user.name, # Interoperability with Microsoft Entra ID
|
||||
"unique_name": request.user.name, # (Optional) Used for troubleshooting within JWT tokens or to setup SharePoint like ADFS
|
||||
"preferred_username": request.user.username, # (Optional) The primary username that represents the user.
|
||||
"nickname": request.user.username, # (Optional) Used for troubleshooting within JWT tokens or to setup SharePoint like ADFS
|
||||
"roles": [group.name for group in request.user.ak_groups.all()], # The set of roles that were assigned to the user who is logging in.
|
||||
}
|
||||
```
|
||||
|
||||
5. Click **Finish**.
|
||||
|
||||
### Step 2: Create authentik Open ID Connect Provider
|
||||
|
||||
From the authentik Admin Dashboard:
|
||||
|
||||
1. Open **Applications > Providers** page from the sidebar.
|
||||
2. Click **Create** from the provider list command bar.
|
||||
3. Within the new provider form, select **OAuth2/OpenID Provider**.
|
||||
4. Click **Next** and enter the following values:
|
||||
- **Name**: `auth.providerName`
|
||||
- **Authentication flow**: default-authentication-flow
|
||||
- **Authorization flow**: default-provider-authorization-implicit-consent
|
||||
:::note
|
||||
use the explicit flow if user consents are required
|
||||
:::
|
||||
- **Redirect URIs / Origins**: `auth.providerRedirectURI`
|
||||
- **Signing Key**: authentik Self-signed Certificate
|
||||
:::note
|
||||
The certificate is used for signing JWT tokens;, if you change it after the integration do not forget to update your SharePoint Trusted Certificate.
|
||||
:::
|
||||
- **Access code validity**: minutes=5
|
||||
:::note
|
||||
The minimum is 5 minutes, otherwise SharePoint backend might consider the access code expired.
|
||||
:::
|
||||
- **Access Token validity**: minutes=15
|
||||
:::note
|
||||
The minimum is 15 minutes, otherwise SharePoint backend will consider the access token expired.
|
||||
:::
|
||||
- **Scopes**: select default email, SPopenid and SPprofile
|
||||
- **Subject mode**: Based on the User's hashed ID
|
||||
5. Click **Finish**.
|
||||
|
||||
### Step 3: Create an application in authentik
|
||||
|
||||
From the authentik Admin Dashboard:
|
||||
|
||||
1. Open **Applications > Applications** page from the sidebar.
|
||||
2. Click **Create** from the application list command bar.
|
||||
3. Within the new application form, enter the following values:
|
||||
- **Name**: `auth.applicationName`
|
||||
- **Slug**: `auth.applicationSlug`
|
||||
- **Provider**: `auth.providerName`
|
||||
- (Optional) **Launch URL**: `sp.webAppURL`
|
||||
- (Optional) **Icon**: https://res-1.cdn.office.net/files/fabric-cdn-prod_20221209.001/assets/brand-icons/product/svg/sharepoint_48x1.svg
|
||||
4. Click **Create**.
|
||||
|
||||
### Step 4: Setup OIDC authentication in SharePoint Server
|
||||
|
||||
#### Pre-requisites
|
||||
|
||||
##### Update SharePoint farm properties
|
||||
|
||||
The following PowerShell script must be updated according to your environment and executed as **Farm Admin account** with **elevated privileges** on a SharePoint Server.
|
||||
|
||||
:::caution
|
||||
|
||||
- Update placeholders
|
||||
- Read all script's comments
|
||||
|
||||
:::
|
||||
|
||||
```PowerShell
|
||||
Add-PSSnapin microsoft.sharepoint.powershell
|
||||
|
||||
# Setup farm properties to work with OIDC
|
||||
$cert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -Provider 'Microsoft Enhanced RSA and AES Cryptographic Provider' -Subject "CN=SharePoint Cookie Cert"
|
||||
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
|
||||
$fileName = $rsaCert.key.UniqueName
|
||||
|
||||
#If you have multiple SharePoint servers in the farm, you need to export certificate by Export-PfxCertificate and import certificate to all other SharePoint servers in the farm by Import-PfxCertificate and apply the same permissions as below.
|
||||
|
||||
#After certificate is successfully imported to SharePoint Server, we will need to grant access permission to certificate private key.
|
||||
|
||||
$path = "$env:ALLUSERSPROFILE\Microsoft\Crypto\RSA\MachineKeys\$fileName"
|
||||
$permissions = Get-Acl -Path $path
|
||||
|
||||
#Please replace the <web application pool account> with the real application pool account of your web application.
|
||||
$access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule("$($env:computername)\WSS_WPG", 'Read', 'None', 'None', 'Allow')
|
||||
$permissions.AddAccessRule($access_rule)
|
||||
Set-Acl -Path $path -AclObject $permissions
|
||||
|
||||
#Then we update farm properties only once.
|
||||
$f = Get-SPFarm
|
||||
$f.Farm.Properties['SP-NonceCookieCertificateThumbprint']=$cert.Thumbprint
|
||||
$f.Farm.Properties['SP-NonceCookieHMACSecretKey']='seed'
|
||||
$f.Farm.Update()
|
||||
```
|
||||
|
||||
##### SharePoint settings in case of SSL offloading
|
||||
|
||||
Update the SharePoint farm to accept OAuth authentication over HTTP.
|
||||
|
||||
The following PowerShell script must be updated according to your environment and executed as **Farm Admin account** with **elevated privileges** on a SharePoint Server.
|
||||
|
||||
```PowerShell
|
||||
Add-PSSnapin microsoft.sharepoint.powershell
|
||||
$c = get-spsecuritytokenserviceconfig
|
||||
$c.AllowOAuthOverHttp = $true
|
||||
$c.update()
|
||||
```
|
||||
|
||||
#### Create SharePoint authentication provider
|
||||
|
||||
The following PowerShell script must be updated according to your environment and executed as **Farm Admin account** with **elevated privileges** on a SharePoint Server.
|
||||
|
||||
:::caution
|
||||
|
||||
- Update placeholders
|
||||
- Read all script's comments.
|
||||
|
||||
:::
|
||||
|
||||
```PowerShell
|
||||
Add-PSSnapin microsoft.sharepoint.powershell
|
||||
|
||||
# OIDC Settings
|
||||
$metadataendpointurl = "auth.providerConfigURL"
|
||||
$clientIdentifier = "auth.providerClientID"
|
||||
$trustedTokenIssuerName = "sp.issuerName"
|
||||
$trustedTokenIssuerDescription = "sp.issuerDesc"
|
||||
|
||||
# OIDC Claims Mapping
|
||||
## Identity claim: oid => defined within the Authentik scope mapping
|
||||
$idClaim = New-SPClaimTypeMapping "http://schemas.microsoft.com/identity/claims/objectidentifier" -IncomingClaimTypeDisplayName "oid" -SameAsIncoming
|
||||
|
||||
## User claims mappings
|
||||
$claims = @(
|
||||
$idClaim
|
||||
## User Roles (Group membership)
|
||||
,(New-SPClaimTypeMapping ([System.Security.Claims.ClaimTypes]::Role) -IncomingClaimTypeDisplayName "Role" -SameAsIncoming)
|
||||
## User email
|
||||
,(New-SPClaimTypeMapping ([System.Security.Claims.ClaimTypes]::Email) -IncomingClaimTypeDisplayName "Email" -SameAsIncoming)
|
||||
## User given_name
|
||||
,(New-SPClaimTypeMapping ([System.Security.Claims.ClaimTypes]::GivenName) -IncomingClaimTypeDisplayName "GivenName" -SameAsIncoming )
|
||||
## (Optional) User account name
|
||||
#,(New-SPClaimTypeMapping ([System.Security.Claims.ClaimTypes]::NameIdentifier) -IncomingClaimTypeDisplayName "Username" -SameAsIncoming)
|
||||
|
||||
)
|
||||
|
||||
# Trust 3rd party identity token issuer
|
||||
$trustedTokenIssuer = New-SPTrustedIdentityTokenIssuer -Name $trustedTokenIssuerName -Description $trustedTokenIssuerDescription -ClaimsMappings $claims -IdentifierClaim $idClaim.InputClaimType -DefaultClientIdentifier $clientIdentifier -MetadataEndPoint $metadataendpointurl -Scope "openid email profile"
|
||||
#Note: Remove the profile scope if you plan to use the LDAPCP claims augmentation.
|
||||
|
||||
# Create the SharePoint authentication provider based on the trusted token issuer
|
||||
New-SPAuthenticationProvider -TrustedIdentityTokenIssuer $trustedTokenIssuer
|
||||
|
||||
```
|
||||
|
||||
#### Configure SharePoint web applications
|
||||
|
||||
From the Central Administration opened as a Farm Administrator:
|
||||
|
||||
1. Open the **Application Management > Manage web applications** page.
|
||||
2. Select your web application `sp.webAppURL`.
|
||||
3. Click **Authentication Providers** from the ribbon bar.
|
||||
4. According to your environment, click on the target zone such as "Default".
|
||||
5. Update the authentication provider form as following:
|
||||
- Check **Trusted Identity Provider**
|
||||
- Check the newly created provider named `sp.issuerName`
|
||||
- (Optional) Set **Custom Sign In Page**: /\_trust/default.aspx
|
||||
6. Click **Save**.
|
||||
|
||||
Repeat all steps for each target web applications that matches with `auth.providerRedirectURI`.
|
||||
|
||||
## (Optional) SharePoint enhancements
|
||||
|
||||
Objectives :
|
||||
|
||||
- Integrate SharePoint People Picker with authentik to search users and groups
|
||||
- Augment SharePoint user claims at login stage
|
||||
- Resolve user's membership
|
||||
|
||||
:::caution
|
||||
[LDAPCP](https://www.ldapcp.com/docs/overview/introduction/) must be installed on the target SharePoint farm.
|
||||
:::
|
||||
|
||||
### Step 1: Assign LDAPCP as claim provider for the identity token issuer
|
||||
|
||||
The following PowerShell script must be updated according to your environment and executed as **Farm Admin account** with **elevated privileges** on a SharePoint Server.
|
||||
|
||||
:::caution
|
||||
|
||||
- Update placeholders
|
||||
- Read all script's comments
|
||||
|
||||
:::
|
||||
|
||||
```PowerShell
|
||||
Add-PSSnapin microsoft.sharepoint.powershell
|
||||
$trustedTokenIssuerName = "sp.issuerName"
|
||||
|
||||
$sptrust = Get-SPTrustedIdentityTokenIssuer $trustedTokenIssuerName
|
||||
$sptrust.ClaimProviderName = "LDAPCP"
|
||||
$sptrust.Update()
|
||||
```
|
||||
|
||||
### Step 2: Configure LDAPCP claim types
|
||||
|
||||
From the SharePoint Central Administration opened as a Farm Administrator:
|
||||
|
||||
1. Open **Security > LDAPCP Configuration > Claim types configuration** page.
|
||||
2. Update the mapping table to match these value:
|
||||
|
||||
| Claim type | Entity type | LDAP class | LDAP Attribute to query | LDAP attribute to display | PickerEntity metadata |
|
||||
| ------------------------------------------------------------- | ----------- | ---------- | ----------------------- | ------------------------- | --------------------- |
|
||||
| http://schemas.microsoft.com/identity/claims/objectidentifier | User | user | uid | sn | UserId |
|
||||
| LDAP attribute linked to the main mapping for object User | User | user | mail | | Email |
|
||||
| LDAP attribute linked to the main mapping for object User | User | user | sn | | DisplayName |
|
||||
| http://schemas.microsoft.com/ws/2008/06/identity/claims/role | Group | group | cn | | DisplayName |
|
||||
| LDAP attribute linked to the main mapping for object Group | Group | group | uid | | SPGroupID |
|
||||
|
||||
### Step 3: Create an authentik LDAP Outpost
|
||||
|
||||
From the authentik Admin Dashboard:
|
||||
|
||||
:::note
|
||||
The following procedure apply to an authentik deployment within Kubernetes.
|
||||
|
||||
For other kinds of deployment, please refer to the [authentik documentation](https://goauthentik.io/docs/).
|
||||
:::
|
||||
|
||||
1. Follow authentik [LDAP Provider Generic Setup](https://version-2023-10.goauthentik.io/docs/providers/ldap/generic_setup) with the following steps :
|
||||
- **Create User/Group** to create a "service account" for `ldap.outpostServiceAccount` and a searchable group of users & groups
|
||||
- **LDAP Flow** to create the authentication flow for the LDAP Provider
|
||||
- **LDAP Provider** to create an LDAP provider which can be consumed by the LDAP Application
|
||||
2. Open **Applications > Applications** page from the sidebar.
|
||||
3. Open the edit form of your application `auth.applicationName`.
|
||||
4. In the edit form:
|
||||
- **Backchannel Providers**: add the LDAP provider previously created
|
||||
5. Click **Update**.
|
||||
|
||||
### Step 4: Configure LDAPCP global configuration
|
||||
|
||||
From the SharePoint Central Administration opened as a Farm Administrator:
|
||||
|
||||
1. Open the **Security > LDAPCP Configuration > Global configuration** page.
|
||||
2. Add an LDAP connection with th following properties:
|
||||
- **LDAP Path**: LDAP://`ldap.outpostURI`/dc=ldap,dc=goauthentik,dc=io
|
||||
- **Username**: `ldap.outpostServiceAccount`
|
||||
- **Password**: `ldap.outpostServiceAccountPassword`
|
||||
- **Authentication types**: check ServerBind
|
||||
3. Augmentation - Check **Enable augmentation**
|
||||
4. Augmentation - Select the Role claim "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
|
||||
5. Augmentation - Check only "**Query this server**" for your `ldap.outpostURI`
|
||||
6. User identifier properties:
|
||||
- **LDAP class**: user
|
||||
- **LDAP attribute**: uid
|
||||
7. Display of user identifier results:
|
||||
- Tick **Show the value of another LDAP attribute**: sn
|
||||
8. Click on "**OK**"
|
||||
|
||||
_Note: The `ldap.outpostURI` should be the IP, hostname, or FQDN of the LDAP Outpost service deployed accessible by your SharePoint farm_.
|
Binary file not shown.
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 88 KiB |
|
@ -38,10 +38,9 @@ Use the following settings:
|
|||
- Consumer key: `*Application (client) ID* value from above`
|
||||
- Consumer secret: `*Value* of the secret from above`
|
||||
|
||||
If you kept the default _Supported account types_ selection of _Single tenant_, then you must change the URLs below as well:
|
||||
If you kept the default _Supported account types_ selection of _Single tenant_, then you must change the URL below as well:
|
||||
|
||||
- Authorization URL: `https://login.microsoftonline.com/*Directory (tenant) ID* from above/oauth2/v2.0/authorize`
|
||||
- Access token URL: `https://login.microsoftonline.com/*Directory (tenant) ID* from above/oauth2/v2.0/token`
|
||||
- OIDC Well-known URL: `https://login.microsoftonline.com/*Directory (tenant) ID* from above/v2.0/.well-known/openid-configuration`
|
||||
|
||||
![](./authentik_01.png)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"clsx": "^2.0.0",
|
||||
"disqus-react": "^1.1.5",
|
||||
"postcss": "^8.4.32",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"rapidoc": "^9.3.4",
|
||||
"react": "^18.2.0",
|
||||
"react-before-after-slider-component": "^1.1.8",
|
||||
|
@ -13786,9 +13786,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/prism-react-renderer": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.0.tgz",
|
||||
"integrity": "sha512-UYRg2TkVIaI6tRVHC5OJ4/BxqPUxJkJvq/odLT/ykpt1zGYXooNperUxQcCvi87LyRnR4nCh81ceOA+e7nrydg==",
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz",
|
||||
"integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==",
|
||||
"dependencies": {
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"clsx": "^2.0.0"
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
"clsx": "^2.0.0",
|
||||
"disqus-react": "^1.1.5",
|
||||
"postcss": "^8.4.32",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"rapidoc": "^9.3.4",
|
||||
"react-before-after-slider-component": "^1.1.8",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
@ -32,6 +32,7 @@ module.exports = {
|
|||
"services/paperless-ng/index",
|
||||
"services/rocketchat/index",
|
||||
"services/roundcube/index",
|
||||
"services/sharepoint-se/index",
|
||||
"services/vikunja/index",
|
||||
"services/wekan/index",
|
||||
"services/wiki-js/index",
|
||||
|
|
Reference in New Issue