mirror of
https://github.com/goauthentik/authentik
synced 2026-05-06 07:02:51 +02:00
Compare commits
4 Commits
web/bundle
...
docs-remov
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1cb339f3a | ||
|
|
e124e21119 | ||
|
|
dc0c7a858a | ||
|
|
3fddbb918e |
@@ -4,7 +4,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
GITHUB_OUTPUT=/dev/stdout \
|
||||
GITHUB_REF=ref \
|
||||
GITHUB_SHA=sha \
|
||||
IMAGE_NAME=ghcr.io/goauthentik/server,authentik/server \
|
||||
IMAGE_NAME=ghcr.io/goauthentik/server,beryju/authentik \
|
||||
GITHUB_REPOSITORY=goauthentik/authentik \
|
||||
python $SCRIPT_DIR/push_vars.py
|
||||
|
||||
@@ -12,7 +12,7 @@ GITHUB_OUTPUT=/dev/stdout \
|
||||
GITHUB_OUTPUT=/dev/stdout \
|
||||
GITHUB_REF=ref \
|
||||
GITHUB_SHA=sha \
|
||||
IMAGE_NAME=ghcr.io/goauthentik/server,authentik/server \
|
||||
IMAGE_NAME=ghcr.io/goauthentik/server,beryju/authentik \
|
||||
GITHUB_REPOSITORY=goauthentik/authentik \
|
||||
DOCKER_USERNAME=foo \
|
||||
python $SCRIPT_DIR/push_vars.py
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
# Re-usable workflow for a single-architecture build
|
||||
name: Reusable - Single-arch Container build
|
||||
name: Single-arch Container build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
# Re-usable workflow for a multi-architecture build
|
||||
name: Reusable - Multi-arch container build
|
||||
name: Multi-arch container build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
5
.github/workflows/api-py-publish.yml
vendored
5
.github/workflows/api-py-publish.yml
vendored
@@ -1,13 +1,10 @@
|
||||
---
|
||||
name: API - Publish Python client
|
||||
|
||||
name: authentik-api-py-publish
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "schema.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
|
||||
5
.github/workflows/api-ts-publish.yml
vendored
5
.github/workflows/api-ts-publish.yml
vendored
@@ -1,13 +1,10 @@
|
||||
---
|
||||
name: API - Publish Typescript client
|
||||
|
||||
name: authentik-api-ts-publish
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "schema.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
|
||||
5
.github/workflows/ci-api-docs.yml
vendored
5
.github/workflows/ci-api-docs.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: CI - API Docs
|
||||
name: authentik-ci-api-docs
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -67,7 +66,7 @@ jobs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: api-docs
|
||||
path: website/api/build
|
||||
|
||||
3
.github/workflows/ci-aws-cfn.yml
vendored
3
.github/workflows/ci-aws-cfn.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: CI - AWS cfn
|
||||
name: authentik-ci-aws-cfn
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
3
.github/workflows/ci-docs.yml
vendored
3
.github/workflows/ci-docs.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: CI - Docs
|
||||
name: authentik-ci-docs
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
2
.github/workflows/ci-main-daily.yml
vendored
2
.github/workflows/ci-main-daily.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: CI - Main daily
|
||||
name: authentik-ci-main-daily
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
20
.github/workflows/ci-main.yml
vendored
20
.github/workflows/ci-main.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: CI - Main
|
||||
name: authentik-ci-main
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -17,12 +17,6 @@ env:
|
||||
POSTGRES_USER: authentik
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
|
||||
permissions:
|
||||
# Needed for checkout
|
||||
contents: read
|
||||
# Needed for codecov OIDC token
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
strategy:
|
||||
@@ -142,13 +136,13 @@ jobs:
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
flags: unit
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- if: ${{ !cancelled() }}
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
flags: unit
|
||||
file: unittest.xml
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
test-integration:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
@@ -166,13 +160,13 @@ jobs:
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
flags: integration
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- if: ${{ !cancelled() }}
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
flags: integration
|
||||
file: unittest.xml
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
test-e2e:
|
||||
name: test-e2e (${{ matrix.job.name }})
|
||||
runs-on: ubuntu-latest
|
||||
@@ -225,13 +219,13 @@ jobs:
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
flags: e2e
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- if: ${{ !cancelled() }}
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
flags: e2e
|
||||
file: unittest.xml
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
ci-core-mark:
|
||||
if: always()
|
||||
needs:
|
||||
|
||||
2
.github/workflows/ci-outpost.yml
vendored
2
.github/workflows/ci-outpost.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: CI - Outpost
|
||||
name: authentik-ci-outpost
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
3
.github/workflows/ci-web.yml
vendored
3
.github/workflows/ci-web.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: CI - Web
|
||||
name: authentik-ci-web
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: QA - CodeQL
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -1,6 +1,4 @@
|
||||
---
|
||||
name: Gen - Webauthn MDS
|
||||
|
||||
name: authentik-gen-update-webauthn-mds
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
# See https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
|
||||
name: GH - Cleanup actions cache after PR is closed
|
||||
|
||||
name: Cleanup cache after PR is closed
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: GH - GHCR retention
|
||||
name: ghcr-retention
|
||||
|
||||
on:
|
||||
# schedule:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: Gen - Compress images
|
||||
name: authentik-compress-images
|
||||
|
||||
on:
|
||||
push:
|
||||
5
.github/workflows/packages-npm-publish.yml
vendored
5
.github/workflows/packages-npm-publish.yml
vendored
@@ -1,6 +1,4 @@
|
||||
---
|
||||
name: Packages - Publish NPM packages
|
||||
|
||||
name: authentik-packages-npm-publish
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
@@ -11,7 +9,6 @@ on:
|
||||
- packages/tsconfig/**
|
||||
- packages/esbuild-plugin-live-reload/**
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: CI - Source code docs
|
||||
name: authentik-publish-source-docs
|
||||
|
||||
on:
|
||||
push:
|
||||
3
.github/workflows/release-next-branch.yml
vendored
3
.github/workflows/release-next-branch.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: Release - Update next branch
|
||||
name: authentik-on-release-next-branch
|
||||
|
||||
on:
|
||||
schedule:
|
||||
|
||||
16
.github/workflows/release-publish.yml
vendored
16
.github/workflows/release-publish.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: Release - On publish
|
||||
name: authentik-on-release
|
||||
|
||||
on:
|
||||
release:
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
id-token: write
|
||||
attestations: write
|
||||
with:
|
||||
image_name: ghcr.io/goauthentik/server,authentik/server
|
||||
image_name: ghcr.io/goauthentik/server,beryju/authentik
|
||||
release: true
|
||||
registry_dockerhub: true
|
||||
registry_ghcr: true
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/docs
|
||||
- name: Login to GitHub Container Registry
|
||||
@@ -92,9 +92,9 @@ jobs:
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/${{ matrix.type }},authentik/${{ matrix.type }}
|
||||
image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }}
|
||||
- name: make empty clients
|
||||
run: |
|
||||
mkdir -p ./gen-ts-api
|
||||
@@ -102,8 +102,8 @@ jobs:
|
||||
- name: Docker Login Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -220,7 +220,7 @@ jobs:
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/server
|
||||
- name: Get static files from docker image
|
||||
|
||||
2
.github/workflows/release-tag.yml
vendored
2
.github/workflows/release-tag.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: Release - On tag
|
||||
name: authentik-on-tag
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
3
.github/workflows/repo-mirror-cleanup.yml
vendored
3
.github/workflows/repo-mirror-cleanup.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: Repo - Cleanup internal mirror
|
||||
name: "authentik-repo-mirror-cleanup"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
3
.github/workflows/repo-mirror.yml
vendored
3
.github/workflows/repo-mirror.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: Repo - Mirror to internal
|
||||
name: "authentik-repo-mirror"
|
||||
|
||||
on: [push, delete]
|
||||
|
||||
|
||||
3
.github/workflows/repo-stale.yml
vendored
3
.github/workflows/repo-stale.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: Repo - Mark and close stale issues
|
||||
name: "authentik-repo-stale"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
---
|
||||
name: QA - Semgrep
|
||||
|
||||
name: authentik-semgrep
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
pull_request: {}
|
||||
@@ -9,11 +7,10 @@ on:
|
||||
- main
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/qa-semgrep.yml
|
||||
- .github/workflows/semgrep.yml
|
||||
schedule:
|
||||
# random HH:MM to avoid a load spike on GitHub Actions at 00:00
|
||||
- cron: '12 15 * * *'
|
||||
|
||||
jobs:
|
||||
semgrep:
|
||||
name: semgrep/ci
|
||||
3
.github/workflows/translation-advice.yml
vendored
3
.github/workflows/translation-advice.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: Translation - Post advice
|
||||
name: authentik-translation-advice
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
name: Translation - Extract and compile
|
||||
|
||||
name: authentik-translate-extract-compile
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # every day at midnight
|
||||
|
||||
3
.github/workflows/translation-rename.yml
vendored
3
.github/workflows/translation-rename.yml
vendored
@@ -1,7 +1,6 @@
|
||||
---
|
||||
# Rename transifex pull requests to have a correct naming
|
||||
# Also enables auto squash-merge
|
||||
name: Translation - Auto-rename Transifex PRs
|
||||
name: authentik-translation-transifex-rename
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
@@ -76,7 +76,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 4: Download uv
|
||||
FROM ghcr.io/astral-sh/uv:0.8.6 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.8.4 AS uv
|
||||
# Stage 5: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
[](https://github.com/goauthentik/authentik/actions/workflows/ci-outpost.yml)
|
||||
[](https://github.com/goauthentik/authentik/actions/workflows/ci-web.yml)
|
||||
[](https://codecov.io/gh/goauthentik/authentik)
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://www.transifex.com/authentik/authentik/)
|
||||
|
||||
## What is authentik?
|
||||
|
||||
@@ -8,6 +8,8 @@ API Browser - {{ brand.branding_title }}
|
||||
|
||||
{% block head %}
|
||||
<script src="{% versioned_script 'dist/standalone/api-browser/index-%v.js' %}" type="module"></script>
|
||||
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: light)">
|
||||
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)">
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
@@ -154,8 +154,7 @@ class UserSerializer(ModelSerializer):
|
||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
||||
self.fields["password"] = CharField(required=False, allow_null=True)
|
||||
self.fields["permissions"] = ListField(
|
||||
required=False,
|
||||
child=ChoiceField(choices=get_permission_choices()),
|
||||
required=False, child=ChoiceField(choices=get_permission_choices())
|
||||
)
|
||||
|
||||
def create(self, validated_data: dict) -> User:
|
||||
@@ -270,10 +269,7 @@ class UserSelfSerializer(ModelSerializer):
|
||||
ListSerializer(
|
||||
child=inline_serializer(
|
||||
"UserSelfGroups",
|
||||
{
|
||||
"name": CharField(read_only=True),
|
||||
"pk": CharField(read_only=True),
|
||||
},
|
||||
{"name": CharField(read_only=True), "pk": CharField(read_only=True)},
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -321,8 +317,7 @@ class UserSelfSerializer(ModelSerializer):
|
||||
|
||||
class SessionUserSerializer(PassiveSerializer):
|
||||
"""Response for the /user/me endpoint, returns the currently active user (as `user` property)
|
||||
and, if this user is being impersonated, the original user in the `original` property.
|
||||
"""
|
||||
and, if this user is being impersonated, the original user in the `original` property."""
|
||||
|
||||
user = UserSelfSerializer()
|
||||
original = UserSelfSerializer(required=False)
|
||||
@@ -410,15 +405,21 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
ordering = ["username", "date_joined", "last_updated"]
|
||||
serializer_class = UserSerializer
|
||||
filterset_class = UsersFilter
|
||||
search_fields = ["email", "name", "uuid", "username"]
|
||||
search_fields = [
|
||||
"username",
|
||||
"name",
|
||||
"is_active",
|
||||
"email",
|
||||
"uuid",
|
||||
"attributes",
|
||||
"date_joined",
|
||||
"last_updated",
|
||||
]
|
||||
|
||||
def get_ql_fields(self):
|
||||
from djangoql.schema import BoolField, StrField
|
||||
|
||||
from authentik.enterprise.search.fields import (
|
||||
ChoiceSearchField,
|
||||
JSONSearchField,
|
||||
)
|
||||
from authentik.enterprise.search.fields import ChoiceSearchField, JSONSearchField
|
||||
|
||||
return [
|
||||
StrField(User, "username"),
|
||||
@@ -513,12 +514,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
)
|
||||
},
|
||||
)
|
||||
@action(
|
||||
detail=False,
|
||||
methods=["POST"],
|
||||
pagination_class=None,
|
||||
filter_backends=[],
|
||||
)
|
||||
@action(detail=False, methods=["POST"], pagination_class=None, filter_backends=[])
|
||||
def service_account(self, request: Request) -> Response:
|
||||
"""Create a new user account that is marked as a service account"""
|
||||
username = request.data.get("name")
|
||||
@@ -562,13 +558,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
return Response(data={"non_field_errors": [str(exc)]}, status=400)
|
||||
|
||||
@extend_schema(responses={200: SessionUserSerializer(many=False)})
|
||||
@action(
|
||||
url_path="me",
|
||||
url_name="me",
|
||||
detail=False,
|
||||
pagination_class=None,
|
||||
filter_backends=[],
|
||||
)
|
||||
@action(url_path="me", url_name="me", detail=False, pagination_class=None, filter_backends=[])
|
||||
def user_me(self, request: Request) -> Response:
|
||||
"""Get information about current user"""
|
||||
context = {"request": request}
|
||||
@@ -694,18 +684,14 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
if not request.user.has_perm(
|
||||
"authentik_core.impersonate", user_to_be
|
||||
) and not request.user.has_perm("authentik_core.impersonate"):
|
||||
LOGGER.debug(
|
||||
"User attempted to impersonate without permissions",
|
||||
user=request.user,
|
||||
)
|
||||
LOGGER.debug("User attempted to impersonate without permissions", user=request.user)
|
||||
return Response(status=401)
|
||||
if user_to_be.pk == self.request.user.pk:
|
||||
LOGGER.debug("User attempted to impersonate themselves", user=request.user)
|
||||
return Response(status=401)
|
||||
if not reason and request.tenant.impersonation_require_reason:
|
||||
LOGGER.debug(
|
||||
"User attempted to impersonate without providing a reason",
|
||||
user=request.user,
|
||||
"User attempted to impersonate without providing a reason", user=request.user
|
||||
)
|
||||
return Response(status=401)
|
||||
|
||||
@@ -744,8 +730,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
@extend_schema(
|
||||
responses={
|
||||
200: inline_serializer(
|
||||
"UserPathSerializer",
|
||||
{"paths": ListField(child=CharField(), read_only=True)},
|
||||
"UserPathSerializer", {"paths": ListField(child=CharField(), read_only=True)}
|
||||
)
|
||||
},
|
||||
parameters=[
|
||||
|
||||
@@ -15,11 +15,7 @@
|
||||
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
|
||||
{% block head_before %}
|
||||
{% endblock %}
|
||||
|
||||
{% include "base/theme.html" %}
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
||||
|
||||
<style>{{ brand_css }}</style>
|
||||
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
|
||||
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{% if ui_theme == "dark" %}
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<meta name="theme-color" content="#18191a">
|
||||
{% elif ui_theme == "light" %}
|
||||
<meta name="color-scheme" content="light" />
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
{% else %}
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
||||
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
|
||||
{% endif %}
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
{% block head %}
|
||||
<script src="{% versioned_script 'dist/admin/AdminInterface-%v.js' %}" type="module"></script>
|
||||
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
|
||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
||||
{% include "base/header_js.html" %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
{% block head %}
|
||||
<script src="{% versioned_script 'dist/user/UserInterface-%v.js' %}" type="module"></script>
|
||||
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: light)">
|
||||
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: dark)">
|
||||
{% include "base/header_js.html" %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -46,10 +46,8 @@ class InterfaceView(TemplateView):
|
||||
"""Base interface view"""
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
brand = CurrentBrandSerializer(self.request.brand)
|
||||
kwargs["config_json"] = dumps(ConfigView(request=Request(self.request)).get_config().data)
|
||||
kwargs["ui_theme"] = brand.data["ui_theme"]
|
||||
kwargs["brand_json"] = dumps(brand.data)
|
||||
kwargs["brand_json"] = dumps(CurrentBrandSerializer(self.request.brand).data)
|
||||
kwargs["version_family"] = f"{LOCAL_VERSION.major}.{LOCAL_VERSION.minor}"
|
||||
kwargs["version_subdomain"] = f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}"
|
||||
kwargs["build"] = get_build_hash()
|
||||
|
||||
@@ -301,7 +301,6 @@ class SessionEndStage(ChallengeStageView):
|
||||
"flow_slug": self.request.brand.flow_invalidation.slug,
|
||||
},
|
||||
)
|
||||
|
||||
return SessionEndChallenge(data=data)
|
||||
|
||||
# This can never be reached since this challenge is created on demand and only the
|
||||
|
||||
@@ -70,7 +70,6 @@ class OAuth2ProviderSerializer(ProviderSerializer):
|
||||
"signing_key",
|
||||
"encryption_key",
|
||||
"redirect_uris",
|
||||
"backchannel_logout_uri",
|
||||
"sub_mode",
|
||||
"property_mappings",
|
||||
"issuer_mode",
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""OAuth/OpenID Constants"""
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code"
|
||||
GRANT_TYPE_IMPLICIT = "implicit"
|
||||
GRANT_TYPE_REFRESH_TOKEN = "refresh_token" # nosec
|
||||
@@ -54,23 +51,3 @@ AMR_MFA = "mfa"
|
||||
AMR_OTP = "otp"
|
||||
AMR_WEBAUTHN = "user"
|
||||
AMR_SMART_CARD = "sc"
|
||||
|
||||
|
||||
class SubModes(models.TextChoices):
|
||||
"""Mode after which 'sub' attribute is generated, for compatibility reasons"""
|
||||
|
||||
HASHED_USER_ID = "hashed_user_id", _("Based on the Hashed User ID")
|
||||
USER_ID = "user_id", _("Based on user ID")
|
||||
USER_UUID = "user_uuid", _("Based on user UUID")
|
||||
USER_USERNAME = "user_username", _("Based on the username")
|
||||
USER_EMAIL = (
|
||||
"user_email",
|
||||
_("Based on the User's Email. This is recommended over the UPN method."),
|
||||
)
|
||||
USER_UPN = (
|
||||
"user_upn",
|
||||
_(
|
||||
"Based on the User's UPN, only works if user has a 'upn' attribute set. "
|
||||
"Use this method only if you have different UPN and Mail domains."
|
||||
),
|
||||
)
|
||||
|
||||
@@ -4,8 +4,10 @@ from dataclasses import asdict, dataclass, field
|
||||
from hashlib import sha256
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from authentik.core.models import default_token_duration
|
||||
from authentik.events.signals import get_login_event
|
||||
@@ -16,7 +18,6 @@ from authentik.providers.oauth2.constants import (
|
||||
AMR_PASSWORD,
|
||||
AMR_SMART_CARD,
|
||||
AMR_WEBAUTHN,
|
||||
SubModes,
|
||||
)
|
||||
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
|
||||
|
||||
@@ -29,6 +30,26 @@ def hash_session_key(session_key: str) -> str:
|
||||
return sha256(session_key.encode("ascii")).hexdigest()
|
||||
|
||||
|
||||
class SubModes(models.TextChoices):
|
||||
"""Mode after which 'sub' attribute is generated, for compatibility reasons"""
|
||||
|
||||
HASHED_USER_ID = "hashed_user_id", _("Based on the Hashed User ID")
|
||||
USER_ID = "user_id", _("Based on user ID")
|
||||
USER_UUID = "user_uuid", _("Based on user UUID")
|
||||
USER_USERNAME = "user_username", _("Based on the username")
|
||||
USER_EMAIL = (
|
||||
"user_email",
|
||||
_("Based on the User's Email. This is recommended over the UPN method."),
|
||||
)
|
||||
USER_UPN = (
|
||||
"user_upn",
|
||||
_(
|
||||
"Based on the User's UPN, only works if user has a 'upn' attribute set. "
|
||||
"Use this method only if you have different UPN and Mail domains."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class IDToken:
|
||||
"""The primary extension that OpenID Connect makes to OAuth 2.0 to enable End-Users to be
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 5.1.11 on 2025-07-04 03:23
|
||||
|
||||
import authentik.lib.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_oauth2", "0028_migrate_session"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="oauth2provider",
|
||||
name="backchannel_logout_uri",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
validators=[authentik.lib.models.DomainlessURLValidator(schemes=("http", "https"))],
|
||||
verbose_name="Back-Channel Logout URI",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="oauth2provider",
|
||||
name="_redirect_uris",
|
||||
field=models.JSONField(default=list, verbose_name="Redirect URIs"),
|
||||
),
|
||||
]
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
from dataclasses import asdict, dataclass
|
||||
from functools import cached_property
|
||||
from hashlib import sha256
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import (
|
||||
@@ -42,14 +42,11 @@ from authentik.core.models import (
|
||||
)
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.generators import generate_code_fixed_length, generate_id, generate_key
|
||||
from authentik.lib.models import DomainlessURLValidator, SerializerModel
|
||||
from authentik.lib.models import SerializerModel
|
||||
from authentik.lib.utils.time import timedelta_string_validator
|
||||
from authentik.providers.oauth2.constants import SubModes
|
||||
from authentik.providers.oauth2.id_token import IDToken, SubModes
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@@ -196,14 +193,9 @@ class OAuth2Provider(WebfingerProvider, Provider):
|
||||
default=generate_client_secret,
|
||||
)
|
||||
_redirect_uris = models.JSONField(
|
||||
default=list,
|
||||
default=dict,
|
||||
verbose_name=_("Redirect URIs"),
|
||||
)
|
||||
backchannel_logout_uri = models.TextField(
|
||||
validators=[DomainlessURLValidator(schemes=("http", "https"))],
|
||||
verbose_name=_("Back-Channel Logout URI"),
|
||||
blank=True,
|
||||
)
|
||||
|
||||
include_claims_in_id_token = models.BooleanField(
|
||||
default=True,
|
||||
@@ -488,15 +480,13 @@ class AccessToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
return f"Access Token for {self.provider_id} for user {self.user_id}"
|
||||
|
||||
@property
|
||||
def id_token(self) -> "IDToken":
|
||||
def id_token(self) -> IDToken:
|
||||
"""Load ID Token from json"""
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
|
||||
raw_token = json.loads(self._id_token)
|
||||
return from_dict(IDToken, raw_token)
|
||||
|
||||
@id_token.setter
|
||||
def id_token(self, value: "IDToken"):
|
||||
def id_token(self, value: IDToken):
|
||||
self.token = value.to_access_token(self.provider)
|
||||
self._id_token = json.dumps(asdict(value))
|
||||
|
||||
@@ -541,15 +531,13 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
return f"Refresh Token for {self.provider_id} for user {self.user_id}"
|
||||
|
||||
@property
|
||||
def id_token(self) -> "IDToken":
|
||||
def id_token(self) -> IDToken:
|
||||
"""Load ID Token from json"""
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
|
||||
raw_token = json.loads(self._id_token)
|
||||
return from_dict(IDToken, raw_token)
|
||||
|
||||
@id_token.setter
|
||||
def id_token(self, value: "IDToken"):
|
||||
def id_token(self, value: IDToken):
|
||||
self._id_token = json.dumps(asdict(value))
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,34 +1,17 @@
|
||||
from django.db.models.signals import post_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import AuthenticatedSession, User
|
||||
from authentik.providers.oauth2.models import AccessToken, DeviceToken, RefreshToken
|
||||
from authentik.providers.oauth2.tasks import backchannel_logout_notification_dispatch
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=AuthenticatedSession)
|
||||
def user_session_deleted_oauth_backchannel_logout_and_tokens_removal(
|
||||
sender, instance: AuthenticatedSession, **_
|
||||
):
|
||||
def user_session_deleted_oauth_tokens_removal(sender, instance: AuthenticatedSession, **_):
|
||||
"""Revoke tokens upon user logout"""
|
||||
LOGGER.debug("Sending back-channel logout notifications signal!", session=instance)
|
||||
|
||||
access_tokens = AccessToken.objects.filter(
|
||||
AccessToken.objects.filter(
|
||||
user=instance.user,
|
||||
session__session__session_key=instance.session.session_key,
|
||||
)
|
||||
|
||||
backchannel_logout_notification_dispatch.send(
|
||||
revocations=[
|
||||
(token.provider_id, token.id_token.iss, token.session.user.uid)
|
||||
for token in access_tokens
|
||||
],
|
||||
)
|
||||
|
||||
access_tokens.delete()
|
||||
).delete()
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
"""OAuth2 Provider Tasks"""
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_dramatiq_postgres.middleware import CurrentTask
|
||||
from dramatiq.actor import actor
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
from authentik.providers.oauth2.utils import create_logout_token
|
||||
from authentik.tasks.models import Task
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@actor(description=_("Send a back-channel logout request to the registered client"))
|
||||
def send_backchannel_logout_request(provider_pk: int, iss: str, sub: str = None) -> bool:
|
||||
"""Send a back-channel logout request to the registered client
|
||||
|
||||
Args:
|
||||
provider_pk: The OAuth2 provider's primary key
|
||||
iss: The issuer URL for the logout token
|
||||
sub: The subject identifier to include in the logout token
|
||||
|
||||
Returns:
|
||||
bool: True if the request was sent successfully, False otherwise
|
||||
"""
|
||||
self: Task = CurrentTask.get_task()
|
||||
LOGGER.debug("Sending back-channel logout request", provider_pk=provider_pk, sub=sub)
|
||||
|
||||
provider = OAuth2Provider.objects.filter(pk=provider_pk).first()
|
||||
if provider is None:
|
||||
return
|
||||
|
||||
# Generate the logout token
|
||||
logout_token = create_logout_token(iss, provider, None, sub)
|
||||
|
||||
# Get the back-channel logout URI from the provider's dedicated backchannel_logout_uri field
|
||||
# Back-channel logout requires explicit configuration - no fallback to redirect URIs
|
||||
|
||||
backchannel_logout_uri = provider.backchannel_logout_uri
|
||||
if not backchannel_logout_uri:
|
||||
self.info("No back-channel logout URI found for provider")
|
||||
return
|
||||
|
||||
# Send the back-channel logout request
|
||||
response = get_http_session().post(
|
||||
backchannel_logout_uri,
|
||||
data={"logout_token": logout_token},
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
allow_redirects=True,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
self.info("Back-channel logout successful", sub=sub)
|
||||
return True
|
||||
|
||||
|
||||
@actor(description=_("Handle backchannel logout notifications dispatched via signal"))
|
||||
def backchannel_logout_notification_dispatch(revocations: list, **kwargs):
|
||||
"""Handle backchannel logout notifications dispatched via signal"""
|
||||
for revocation in revocations:
|
||||
provider_pk, iss, sub = revocation
|
||||
provider = OAuth2Provider.objects.filter(pk=provider_pk).first()
|
||||
send_backchannel_logout_request.send_with_options(
|
||||
args=(provider_pk, iss, sub),
|
||||
rel_obj=provider,
|
||||
)
|
||||
@@ -81,46 +81,4 @@ class TestAPI(APITestCase):
|
||||
},
|
||||
)
|
||||
self.assertJSONEqual(response.content, {"redirect_uris": ["Invalid Regex Pattern: **"]})
|
||||
|
||||
def test_backchannel_logout_uri_validation(self):
|
||||
"""Test backchannel_logout_uri API validation"""
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:oauth2provider-list"),
|
||||
data={
|
||||
"name": generate_id(),
|
||||
"authorization_flow": create_test_flow().pk,
|
||||
"invalidation_flow": create_test_flow().pk,
|
||||
"redirect_uris": [
|
||||
{"matching_mode": "strict", "url": "http://goauthentik.io"},
|
||||
],
|
||||
"backchannel_logout_uri": "invalid-url",
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_backchannel_logout_uri_create_and_retrieve(self):
|
||||
"""Test creating and retrieving backchannel logout URI"""
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:oauth2provider-list"),
|
||||
data={
|
||||
"name": generate_id(),
|
||||
"authorization_flow": create_test_flow().pk,
|
||||
"invalidation_flow": create_test_flow().pk,
|
||||
"redirect_uris": [
|
||||
{"matching_mode": "strict", "url": "http://goauthentik.io"},
|
||||
],
|
||||
"backchannel_logout_uri": "http://goauthentik.io/logout",
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
provider_data = response.json()
|
||||
self.assertEqual(provider_data["backchannel_logout_uri"], "http://goauthentik.io/logout")
|
||||
|
||||
# Test retrieving the provider
|
||||
provider_pk = provider_data["pk"]
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:oauth2provider-detail", kwargs={"pk": provider_pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
retrieved_data = response.json()
|
||||
self.assertEqual(retrieved_data["backchannel_logout_uri"], "http://goauthentik.io/logout")
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
"""Test OAuth2 Back-Channel Logout implementation"""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import jwt
|
||||
from django.test import RequestFactory
|
||||
from django.utils import timezone
|
||||
from dramatiq.results.errors import ResultFailure
|
||||
from requests import Response
|
||||
from requests.exceptions import HTTPError, Timeout
|
||||
|
||||
from authentik.core.models import Application, AuthenticatedSession, Session
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.id_token import hash_session_key
|
||||
from authentik.providers.oauth2.models import (
|
||||
AccessToken,
|
||||
OAuth2Provider,
|
||||
RedirectURI,
|
||||
RedirectURIMatchingMode,
|
||||
RefreshToken,
|
||||
)
|
||||
from authentik.providers.oauth2.tasks import send_backchannel_logout_request
|
||||
from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
||||
from authentik.providers.oauth2.utils import create_logout_token
|
||||
|
||||
|
||||
class TestBackChannelLogout(OAuthTestCase):
|
||||
"""Test Back-Channel Logout functionality"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.factory = RequestFactory()
|
||||
self.user = create_test_admin_user()
|
||||
self.app = Application.objects.create(name=generate_id(), slug="test-app")
|
||||
self.provider = OAuth2Provider.objects.create(
|
||||
name=generate_id(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris=[
|
||||
RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver/callback"),
|
||||
],
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
self.app.provider = self.provider
|
||||
self.app.save()
|
||||
|
||||
def _create_session(self, session_key=None):
|
||||
"""Create a session with the given key or a generated one"""
|
||||
session_key = session_key or f"session-{generate_id()}"
|
||||
session = Session.objects.create(
|
||||
session_key=session_key,
|
||||
expires=timezone.now() + timezone.timedelta(hours=1),
|
||||
last_ip="255.255.255.255",
|
||||
)
|
||||
auth_session = AuthenticatedSession.objects.create(
|
||||
session=session,
|
||||
user=self.user,
|
||||
)
|
||||
return auth_session
|
||||
|
||||
def _create_token(
|
||||
self, provider, user, session=None, token_type="access", token_id=None
|
||||
): # nosec
|
||||
"""Create a token of the specified type"""
|
||||
token_id = token_id or f"{token_type}-token-{generate_id()}"
|
||||
kwargs = {
|
||||
"provider": provider,
|
||||
"user": user,
|
||||
"session": session,
|
||||
"token": token_id,
|
||||
"_id_token": "{}",
|
||||
"auth_time": timezone.now(),
|
||||
}
|
||||
|
||||
if token_type == "access": # nosec
|
||||
return AccessToken.objects.create(**kwargs)
|
||||
else: # refresh
|
||||
return RefreshToken.objects.create(**kwargs)
|
||||
|
||||
def _create_provider(self, name=None):
|
||||
"""Create an OAuth2 provider"""
|
||||
name = name or f"provider-{generate_id()}"
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name=name,
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris=[
|
||||
RedirectURI(RedirectURIMatchingMode.STRICT, f"http://{name}/callback"),
|
||||
],
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
return provider
|
||||
|
||||
def _create_logout_token(
|
||||
self,
|
||||
provider: OAuth2Provider | None = None,
|
||||
session_id: str | None = None,
|
||||
sub: str | None = None,
|
||||
):
|
||||
"""Create a logout token with the given parameters"""
|
||||
provider = provider or self.provider
|
||||
|
||||
# Create a token with the same issuer that the view will expect
|
||||
# Use the same request object that will be used in the test
|
||||
request = self.factory.post("/backchannel_logout")
|
||||
|
||||
return create_logout_token(
|
||||
iss=provider.get_issuer(request),
|
||||
provider=provider,
|
||||
session_key=session_id,
|
||||
sub=sub,
|
||||
)
|
||||
|
||||
def _decode_token(self, token, provider=None):
|
||||
"""Helper to decode and validate a JWT token"""
|
||||
provider = provider or self.provider
|
||||
key, alg = provider.jwt_key
|
||||
if alg != "HS256":
|
||||
key = provider.signing_key.public_key
|
||||
return jwt.decode(
|
||||
token, key, algorithms=[alg], options={"verify_exp": False, "verify_aud": False}
|
||||
)
|
||||
|
||||
def test_create_logout_token_variants(self):
|
||||
"""Test creating logout tokens with different combinations of parameters"""
|
||||
# Test case 1: With session_id only
|
||||
session_id = "test-session-123"
|
||||
token1 = self._create_logout_token(session_id=session_id)
|
||||
decoded1 = self._decode_token(token1)
|
||||
|
||||
self.assertIn("iss", decoded1)
|
||||
self.assertEqual(decoded1["aud"], self.provider.client_id)
|
||||
self.assertIn("iat", decoded1)
|
||||
self.assertIn("jti", decoded1)
|
||||
self.assertEqual(decoded1["sid"], hash_session_key(session_id))
|
||||
self.assertIn("events", decoded1)
|
||||
self.assertIn("http://schemas.openid.net/event/backchannel-logout", decoded1["events"])
|
||||
self.assertNotIn("sub", decoded1)
|
||||
|
||||
# Test case 2: With sub only
|
||||
sub = "user-123"
|
||||
token2 = self._create_logout_token(sub=sub)
|
||||
decoded2 = self._decode_token(token2)
|
||||
|
||||
self.assertEqual(decoded2["sub"], sub)
|
||||
self.assertIn("events", decoded2)
|
||||
self.assertIn("http://schemas.openid.net/event/backchannel-logout", decoded2["events"])
|
||||
self.assertNotIn("sid", decoded2)
|
||||
|
||||
# Test case 3: With both session_id and sub
|
||||
token3 = self._create_logout_token(session_id=session_id, sub=sub)
|
||||
decoded3 = self._decode_token(token3)
|
||||
|
||||
self.assertEqual(decoded3["sid"], hash_session_key(session_id))
|
||||
self.assertEqual(decoded3["sub"], sub)
|
||||
self.assertIn("events", decoded3)
|
||||
|
||||
@patch("authentik.providers.oauth2.tasks.get_http_session")
|
||||
def test_send_backchannel_logout_request_scenarios(self, mock_get_session):
|
||||
"""Test various scenarios for backchannel logout request task"""
|
||||
# Setup provider with backchannel logout URI
|
||||
self.provider.backchannel_logout_uri = "http://testserver/backchannel_logout"
|
||||
self.provider.save()
|
||||
|
||||
# Setup mock session and response
|
||||
mock_session = Mock()
|
||||
mock_get_session.return_value = mock_session
|
||||
mock_response = Mock(spec=Response)
|
||||
mock_response.status_code = 200
|
||||
mock_response.raise_for_status.return_value = None # No exception for successful request
|
||||
mock_session.post.return_value = mock_response
|
||||
|
||||
result = send_backchannel_logout_request.send(
|
||||
self.provider.pk, "http://testserver", sub="test-user-uid"
|
||||
)
|
||||
self.assertTrue(result)
|
||||
mock_session.post.assert_called_once()
|
||||
call_args = mock_session.post.call_args
|
||||
self.assertIn("logout_token", call_args[1]["data"])
|
||||
self.assertEqual(
|
||||
call_args[1]["headers"]["Content-Type"], "application/x-www-form-urlencoded"
|
||||
)
|
||||
|
||||
# Scenario 2: Failed request (400 response) - should raise exception
|
||||
mock_session.post.reset_mock()
|
||||
error_response = Mock(spec=Response)
|
||||
error_response.status_code = 400
|
||||
error_response.raise_for_status.side_effect = HTTPError("HTTP 400")
|
||||
mock_session.post.return_value = error_response
|
||||
with self.assertRaises(ResultFailure):
|
||||
send_backchannel_logout_request.send(
|
||||
self.provider.pk, "http://testserver", sub="test-user-uid"
|
||||
).get_result()
|
||||
|
||||
# Scenario 3: No URI configured
|
||||
mock_session.post.reset_mock()
|
||||
self.provider.backchannel_logout_uri = ""
|
||||
self.provider.save()
|
||||
result = send_backchannel_logout_request.send(
|
||||
self.provider.pk, "http://testserver", sub="test-user-uid"
|
||||
).get_result()
|
||||
self.assertIsNone(result)
|
||||
mock_session.post.assert_not_called()
|
||||
|
||||
# Scenario 4: No sub provided - should fail
|
||||
result = send_backchannel_logout_request.send(
|
||||
self.provider.pk, "http://testserver"
|
||||
).get_result()
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Scenario 5: Non-existent provider
|
||||
result = send_backchannel_logout_request.send(
|
||||
99999, "http://testserver", sub="test-user-uid"
|
||||
).get_result()
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Scenario 6: Request timeout
|
||||
mock_session.post.side_effect = Timeout("Request timed out")
|
||||
self.provider.backchannel_logout_uri = "http://testserver/backchannel_logout"
|
||||
self.provider.save()
|
||||
with self.assertRaises(ResultFailure):
|
||||
send_backchannel_logout_request.send(
|
||||
self.provider.pk, "http://testserver", sub="test-user-uid"
|
||||
).get_result()
|
||||
@@ -11,9 +11,9 @@ from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.constants import ACR_AUTHENTIK_DEFAULT
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
from authentik.providers.oauth2.models import (
|
||||
AccessToken,
|
||||
IDToken,
|
||||
OAuth2Provider,
|
||||
RedirectURI,
|
||||
RedirectURIMatchingMode,
|
||||
|
||||
@@ -10,11 +10,11 @@ from django.utils import timezone
|
||||
from authentik.core.models import Application, AuthenticatedSession, Session
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
from authentik.providers.oauth2.models import (
|
||||
AccessToken,
|
||||
ClientTypes,
|
||||
DeviceToken,
|
||||
IDToken,
|
||||
OAuth2Provider,
|
||||
RedirectURI,
|
||||
RedirectURIMatchingMode,
|
||||
|
||||
@@ -11,9 +11,9 @@ from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
from authentik.providers.oauth2.models import (
|
||||
AccessToken,
|
||||
IDToken,
|
||||
OAuth2Provider,
|
||||
RedirectURI,
|
||||
RedirectURIMatchingMode,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""OAuth2/OpenID Utils"""
|
||||
|
||||
import re
|
||||
import uuid
|
||||
from base64 import b64decode
|
||||
from binascii import Error
|
||||
from time import time
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@@ -16,7 +14,6 @@ from structlog.stdlib import get_logger
|
||||
from authentik.core.middleware import CTX_AUTH_VIA, KEY_USER
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.providers.oauth2.errors import BearerTokenError
|
||||
from authentik.providers.oauth2.id_token import hash_session_key
|
||||
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider
|
||||
|
||||
LOGGER = get_logger()
|
||||
@@ -214,36 +211,3 @@ class HttpResponseRedirectScheme(HttpResponseRedirect):
|
||||
) -> None:
|
||||
self.allowed_schemes = allowed_schemes or ["http", "https", "ftp"]
|
||||
super().__init__(redirect_to, *args, **kwargs)
|
||||
|
||||
|
||||
def create_logout_token(
|
||||
iss: str,
|
||||
provider: OAuth2Provider,
|
||||
session_key: str | None = None,
|
||||
sub: str | None = None,
|
||||
) -> str:
|
||||
"""Create a logout token for Back-Channel Logout
|
||||
|
||||
As per https://openid.net/specs/openid-connect-backchannel-1_0.html
|
||||
"""
|
||||
|
||||
LOGGER.debug("Creating logout token", provider=provider, session_key=session_key, sub=sub)
|
||||
|
||||
# Create the logout token payload
|
||||
payload = {
|
||||
"iss": str(iss),
|
||||
"aud": provider.client_id,
|
||||
"iat": int(time()),
|
||||
"jti": str(uuid.uuid4()),
|
||||
"events": {
|
||||
"http://schemas.openid.net/event/backchannel-logout": {},
|
||||
},
|
||||
}
|
||||
|
||||
# Add either sub or sid (or both)
|
||||
if sub:
|
||||
payload["sub"] = sub
|
||||
if session_key:
|
||||
payload["sid"] = hash_session_key(session_key)
|
||||
# Encode the token
|
||||
return provider.encode(payload)
|
||||
|
||||
@@ -9,8 +9,7 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.providers.oauth2.errors import TokenIntrospectionError
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider, RefreshToken
|
||||
from authentik.providers.oauth2.models import AccessToken, IDToken, OAuth2Provider, RefreshToken
|
||||
from authentik.providers.oauth2.utils import TokenResponse, authenticate_provider
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@@ -72,8 +72,6 @@ class ProviderInfoView(View):
|
||||
"device_authorization_endpoint": self.request.build_absolute_uri(
|
||||
reverse("authentik_providers_oauth2:device")
|
||||
),
|
||||
"backchannel_logout_supported": True,
|
||||
"backchannel_logout_session_supported": True,
|
||||
"response_types_supported": [
|
||||
ResponseTypes.CODE,
|
||||
ResponseTypes.ID_TOKEN,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
{% block head %}
|
||||
<script src="{% versioned_script 'dist/rac/index-%v.js' %}" type="module"></script>
|
||||
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
|
||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
||||
<link rel="icon" href="{{ tenant.branding_favicon_url }}">
|
||||
<link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}">
|
||||
{% include "base/header_js.html" %}
|
||||
|
||||
@@ -44,8 +44,6 @@ class EmailStageSerializer(StageSerializer):
|
||||
"subject",
|
||||
"template",
|
||||
"activate_user_on_success",
|
||||
"recovery_max_attempts",
|
||||
"recovery_cache_timeout",
|
||||
]
|
||||
extra_kwargs = {"password": {"write_only": True}}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 5.1.11 on 2025-07-23 11:26
|
||||
|
||||
import authentik.lib.utils.time
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_stages_email", "0005_alter_emailstage_token_expiry"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="emailstage",
|
||||
name="recovery_cache_timeout",
|
||||
field=models.TextField(
|
||||
default="minutes=5",
|
||||
help_text="The time window used to count recent account recovery attempts. If the number of attempts exceed recovery_max_attempts within this period, further attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3).",
|
||||
validators=[authentik.lib.utils.time.timedelta_string_validator],
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="emailstage",
|
||||
name="recovery_max_attempts",
|
||||
field=models.PositiveIntegerField(default=5),
|
||||
),
|
||||
]
|
||||
@@ -16,8 +16,6 @@ from authentik.flows.models import Stage
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.utils.time import timedelta_string_validator
|
||||
|
||||
EMAIL_RECOVERY_MAX_ATTEMPTS = 5
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@@ -72,17 +70,6 @@ class EmailStage(Stage):
|
||||
use_ssl = models.BooleanField(default=False)
|
||||
timeout = models.IntegerField(default=10)
|
||||
from_address = models.EmailField(default="system@authentik.local")
|
||||
recovery_max_attempts = models.PositiveIntegerField(default=EMAIL_RECOVERY_MAX_ATTEMPTS)
|
||||
recovery_cache_timeout = models.TextField(
|
||||
default="minutes=5",
|
||||
validators=[timedelta_string_validator],
|
||||
help_text=_(
|
||||
"The time window used to count recent account recovery attempts. "
|
||||
"If the number of attempts exceed recovery_max_attempts within "
|
||||
"this period, further attempts will be rate-limited. "
|
||||
"(Format: hours=1;minutes=2;seconds=3)."
|
||||
),
|
||||
)
|
||||
|
||||
activate_user_on_success = models.BooleanField(
|
||||
default=False, help_text=_("Activate users upon completion of stage.")
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
"""authentik multi-stage authentication engine"""
|
||||
|
||||
import math
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from hashlib import sha256
|
||||
from datetime import timedelta
|
||||
from uuid import uuid4
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.request import QueryDict
|
||||
from django.template.exceptions import TemplateSyntaxError
|
||||
@@ -30,8 +27,6 @@ from authentik.stages.email.models import EmailStage
|
||||
from authentik.stages.email.tasks import send_mails
|
||||
from authentik.stages.email.utils import TemplateEmailMessage
|
||||
|
||||
EMAIL_RECOVERY_CACHE_KEY = "goauthentik.io/stages/email/stage/"
|
||||
|
||||
PLAN_CONTEXT_EMAIL_SENT = "email_sent"
|
||||
PLAN_CONTEXT_EMAIL_OVERRIDE = "email"
|
||||
|
||||
@@ -175,66 +170,10 @@ class EmailStageView(ChallengeStageView):
|
||||
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
|
||||
return super().challenge_invalid(response)
|
||||
|
||||
def _get_cache_key(self) -> str:
|
||||
"""Return the cache key used for rate limiting email recovery attempts."""
|
||||
user = self.get_pending_user()
|
||||
user_email_hashed = sha256(user.email.lower().encode("utf-8")).hexdigest()
|
||||
return EMAIL_RECOVERY_CACHE_KEY + user_email_hashed
|
||||
|
||||
def _is_rate_limited(self) -> int | None:
|
||||
"""Check whether the email recovery attempt should be rate limited.
|
||||
|
||||
If the request should be rate limited, update the cache and return the
|
||||
remaining time in minutes before the user is allowed to try again.
|
||||
Otherwise, return None."""
|
||||
cache_key = self._get_cache_key()
|
||||
attempts = cache.get(cache_key, [])
|
||||
|
||||
stage = self.executor.current_stage
|
||||
stage.refresh_from_db()
|
||||
max_attempts = stage.recovery_max_attempts
|
||||
cache_timeout_delta = timedelta_from_string(stage.recovery_cache_timeout)
|
||||
|
||||
_now = now()
|
||||
start_window = _now - cache_timeout_delta
|
||||
|
||||
# Convert unix timestamps to datetime objects for comparison
|
||||
recent_attempts_in_window = [
|
||||
datetime.fromtimestamp(attempt, UTC)
|
||||
for attempt in attempts
|
||||
if datetime.fromtimestamp(attempt, UTC) > start_window
|
||||
]
|
||||
|
||||
if len(recent_attempts_in_window) >= max_attempts:
|
||||
retry_after = (min(recent_attempts_in_window) + cache_timeout_delta) - _now
|
||||
minutes_left = max(1, math.ceil(retry_after.total_seconds() / 60))
|
||||
return minutes_left
|
||||
|
||||
recent_attempts_in_window.append(_now)
|
||||
|
||||
# Convert datetime objects back to unix timestamps to update cache
|
||||
recent_attempts_in_window = [attempt.timestamp() for attempt in recent_attempts_in_window]
|
||||
|
||||
cache.set(
|
||||
cache_key,
|
||||
recent_attempts_in_window,
|
||||
int(cache_timeout_delta.total_seconds()),
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def challenge_invalid(self, response: ChallengeResponse) -> HttpResponse:
|
||||
if minutes_left := self._is_rate_limited():
|
||||
error = _(
|
||||
"Too many account verification attempts. Please try again after {minutes} minutes."
|
||||
).format(minutes=minutes_left)
|
||||
messages.error(self.request, error)
|
||||
return super().challenge_invalid(response)
|
||||
|
||||
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
|
||||
messages.error(self.request, _("No pending user."))
|
||||
return super().challenge_invalid(response)
|
||||
|
||||
self.send_email()
|
||||
messages.success(self.request, _("Email Successfully sent."))
|
||||
# We can't call stage_ok yet, as we're still waiting
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""email tests"""
|
||||
|
||||
from hashlib import sha256
|
||||
from unittest.mock import MagicMock, PropertyMock, patch
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core import mail
|
||||
from django.core.mail.backends.locmem import EmailBackend
|
||||
from django.core.mail.backends.smtp import EmailBackend as SMTPEmailBackend
|
||||
@@ -11,7 +9,6 @@ from django.test import RequestFactory
|
||||
from django.urls import reverse
|
||||
from django.utils.http import urlencode
|
||||
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.flows.markers import StageMarker
|
||||
from authentik.flows.models import FlowDesignation, FlowStageBinding, FlowToken
|
||||
@@ -20,7 +17,6 @@ from authentik.flows.tests import FlowTestCase
|
||||
from authentik.flows.views.executor import QS_KEY_TOKEN, SESSION_KEY_PLAN, FlowExecutorView
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.tests.utils import get_request
|
||||
from authentik.stages.consent.stage import SESSION_KEY_CONSENT_TOKEN
|
||||
from authentik.stages.email.models import EmailStage
|
||||
from authentik.stages.email.stage import PLAN_CONTEXT_EMAIL_OVERRIDE, EmailStageView
|
||||
@@ -295,173 +291,3 @@ class TestEmailStage(FlowTestCase):
|
||||
stage_view.get_full_url(**{QS_KEY_TOKEN: token}),
|
||||
f"http://testserver/if/flow/{self.flow.slug}/?foo=bar&flow_token={token}",
|
||||
)
|
||||
|
||||
def test_get_cache_key(self):
|
||||
"""Test to ensure that the correct cache key is returned."""
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
request = self.factory.post(url)
|
||||
request.user = self.user
|
||||
request.session = session
|
||||
|
||||
executor = FlowExecutorView(request=request, flow=self.flow)
|
||||
executor.plan = plan
|
||||
|
||||
stage_view = EmailStageView(executor, request=request)
|
||||
|
||||
cache_key = stage_view._get_cache_key()
|
||||
|
||||
expected_hash = sha256(self.user.email.lower().encode("utf-8")).hexdigest()
|
||||
expected_cache_key = "goauthentik.io/stages/email/stage/" + expected_hash
|
||||
|
||||
self.assertEqual(cache_key, expected_cache_key)
|
||||
|
||||
def test_is_rate_limited_returns_none(self):
|
||||
"""Test to ensure None is returned if the request shouldn't be rate limited."""
|
||||
self.stage.recovery_max_attempts = 2
|
||||
self.stage.recovery_cache_timeout = "minutes=10"
|
||||
self.stage.save()
|
||||
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
request = self.factory.post(url)
|
||||
request.user = self.user
|
||||
request.session = session
|
||||
|
||||
executor = FlowExecutorView(request=request, flow=self.flow)
|
||||
executor.current_stage = self.stage
|
||||
executor.plan = plan
|
||||
|
||||
stage_view = EmailStageView(executor, request=request)
|
||||
|
||||
result = stage_view._is_rate_limited()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_is_rate_limited_returns_remaining_time(self):
|
||||
"""Test to ensure the remaining time is returned if the request
|
||||
should be rate limited."""
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
request = self.factory.post(url)
|
||||
request.user = self.user
|
||||
request.session = session
|
||||
|
||||
executor = FlowExecutorView(request=request, flow=self.flow)
|
||||
executor.current_stage = self.stage
|
||||
executor.plan = plan
|
||||
|
||||
stage_view = EmailStageView(executor, request=request)
|
||||
|
||||
test_cases = [
|
||||
# 2 attempts within 2 minutes
|
||||
(2, "seconds=120", 2),
|
||||
# 4 attempts within 5 minutes
|
||||
(4, "minutes=5", 5),
|
||||
# 6 attempts within 5 minutes. Although 299 seconds is less than
|
||||
# 5 minutes, the user is intentionally shown "5 minutes". This is
|
||||
# because an initial rate limiting message like "Try again after 4 minutes"
|
||||
# can be confusing.
|
||||
(6, "seconds=299", 5),
|
||||
]
|
||||
for test_case in test_cases:
|
||||
max_attempts, cache_timeout, minutes_remaining = test_case
|
||||
with self.subTest(
|
||||
f"Test recovery with {max_attempts} max attempts and "
|
||||
f"{cache_timeout} cache timeout seconds"
|
||||
):
|
||||
self.stage.recovery_max_attempts = max_attempts
|
||||
self.stage.recovery_cache_timeout = cache_timeout
|
||||
self.stage.save()
|
||||
|
||||
# Simulate multiple requests
|
||||
for _ in range(max_attempts):
|
||||
stage_view._is_rate_limited()
|
||||
|
||||
# The following request should be rate-limited
|
||||
result = stage_view._is_rate_limited()
|
||||
|
||||
self.assertEqual(result, minutes_remaining)
|
||||
|
||||
def _challenge_invalid_helper(self):
|
||||
"""Helper to test the challenge_invalid() method."""
|
||||
self.stage.recovery_max_attempts = 1
|
||||
self.stage.recovery_cache_timeout = "seconds=300"
|
||||
self.stage.save()
|
||||
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
request = get_request(url, user=self.user)
|
||||
request.session = session
|
||||
|
||||
request.brand = Brand.objects.create(domain="foo-domain.com", default=True)
|
||||
|
||||
executor = FlowExecutorView(request=request, flow=self.flow)
|
||||
executor.current_stage = self.stage
|
||||
executor.plan = plan
|
||||
|
||||
stage_view = EmailStageView(executor, request=request)
|
||||
challenge_response = stage_view.get_response_instance(data={})
|
||||
challenge_response.is_valid()
|
||||
|
||||
return challenge_response, stage_view, request
|
||||
|
||||
def test_challenge_invalid_not_rate_limited(self):
|
||||
"""Tests that the request is not rate limited and email is sent."""
|
||||
challenge_response, stage_view, request = self._challenge_invalid_helper()
|
||||
|
||||
with patch.object(stage_view, "send_email") as mock_send_email:
|
||||
result = stage_view.challenge_invalid(challenge_response)
|
||||
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
mock_send_email.assert_called_once()
|
||||
|
||||
message_list = list(messages.get_messages(request))
|
||||
self.assertEqual(len(message_list), 1)
|
||||
self.assertEqual(
|
||||
"Email Successfully sent.",
|
||||
message_list[-1].message,
|
||||
)
|
||||
|
||||
def test_challenge_invalid_returns_error_if_rate_limited(self):
|
||||
"""Tests that an error is returned if the request is rate limited. Ensure
|
||||
that an email is not sent."""
|
||||
challenge_response, stage_view, request = self._challenge_invalid_helper()
|
||||
|
||||
# Initial request that shouldn't be rate limited
|
||||
stage_view.challenge_invalid(challenge_response)
|
||||
|
||||
with patch.object(stage_view, "send_email") as mock_send_email:
|
||||
# This next request should be rate limited
|
||||
result = stage_view.challenge_invalid(challenge_response)
|
||||
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
mock_send_email.assert_not_called()
|
||||
|
||||
message_list = list(messages.get_messages(request))
|
||||
self.assertEqual(len(message_list), 2)
|
||||
self.assertEqual(
|
||||
"Too many account verification attempts. Please try again after 5 minutes.",
|
||||
message_list[-1].message,
|
||||
)
|
||||
|
||||
@@ -61,8 +61,6 @@ entries:
|
||||
subject: authentik
|
||||
template: email/password_reset.html
|
||||
activate_user_on_success: true
|
||||
recovery_max_attempts: 5
|
||||
recovery_cache_timeout: minutes=5
|
||||
- identifiers:
|
||||
name: default-recovery-user-write
|
||||
id: default-recovery-user-write
|
||||
|
||||
@@ -8473,10 +8473,6 @@
|
||||
},
|
||||
"title": "Redirect uris"
|
||||
},
|
||||
"backchannel_logout_uri": {
|
||||
"type": "string",
|
||||
"title": "Back-Channel Logout URI"
|
||||
},
|
||||
"sub_mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -14336,18 +14332,6 @@
|
||||
"type": "boolean",
|
||||
"title": "Activate user on success",
|
||||
"description": "Activate users upon completion of stage."
|
||||
},
|
||||
"recovery_max_attempts": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 2147483647,
|
||||
"title": "Recovery max attempts"
|
||||
},
|
||||
"recovery_cache_timeout": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Recovery cache timeout",
|
||||
"description": "The time window used to count recent account recovery attempts. If the number of attempts exceed recovery_max_attempts within this period, further attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3)."
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
||||
2
go.mod
2
go.mod
@@ -29,7 +29,7 @@ require (
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2025064.7
|
||||
goauthentik.io/api/v3 v3.2025064.3
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.16.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -185,8 +185,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
goauthentik.io/api/v3 v3.2025064.7 h1:nh7Uh9K/XsHEz6hmPvZCK+aQC9uqwOto5hTCvtdvXPc=
|
||||
goauthentik.io/api/v3 v3.2025064.7/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
|
||||
goauthentik.io/api/v3 v3.2025064.3 h1:REfDBEjswP2id2WRRDUajRxX+6u+XZ7e/smYq7jw5Z0=
|
||||
goauthentik.io/api/v3 v3.2025064.3/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"goauthentik.io/internal/config"
|
||||
"goauthentik.io/internal/outpost/proxyv2/codecs"
|
||||
"goauthentik.io/internal/outpost/proxyv2/constants"
|
||||
"goauthentik.io/internal/outpost/proxyv2/filesystemstore"
|
||||
"goauthentik.io/internal/outpost/proxyv2/redisstore"
|
||||
"goauthentik.io/internal/utils"
|
||||
)
|
||||
@@ -91,10 +90,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL)
|
||||
return rs, nil
|
||||
}
|
||||
dir := os.TempDir()
|
||||
cs, err := filesystemstore.GetPersistentStore(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs := sessions.NewFilesystemStore(dir)
|
||||
cs.Codecs = codecs.CodecsFromPairs(maxAge, []byte(*p.CookieSecret))
|
||||
// https://github.com/markbates/goth/commit/7276be0fdf719ddff753f3574ef0f967e4a5a5f7
|
||||
// set the maxLength of the cookies stored on the disk to a larger number to prevent issues with:
|
||||
@@ -127,7 +123,7 @@ func (a *Application) getAllCodecs() []securecookie.Codec {
|
||||
}
|
||||
|
||||
func (a *Application) Logout(ctx context.Context, filter func(c Claims) bool) error {
|
||||
if _, ok := a.sessions.(*filesystemstore.Store); ok {
|
||||
if _, ok := a.sessions.(*sessions.FilesystemStore); ok {
|
||||
files, err := os.ReadDir(os.TempDir())
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
package filesystemstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
SessionCleanupInterval = 5 * time.Minute
|
||||
SessionCleanupLockFileName = "session-cleanup.lock"
|
||||
SessionFilePrefix = "session_"
|
||||
SessionTestFile = SessionFilePrefix + "write_test"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSessionCleanupAlreadyRunning = errors.New("session cleanup is already running by another instance")
|
||||
ErrSessionStoreNoPermission = errors.New("path is not writable")
|
||||
ErrSessionStorePathNotExist = errors.New("path does not exist")
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
*sessions.FilesystemStore
|
||||
storePath string
|
||||
log *log.Entry
|
||||
}
|
||||
|
||||
// NewStore checks if the specified store path exists, is writable and creates a new filesystem session store.
|
||||
func NewStore(storePath string, keyPairs ...[]byte) (*Store, error) {
|
||||
if storePath == "" {
|
||||
storePath = os.TempDir()
|
||||
}
|
||||
|
||||
// check if path exists
|
||||
_, err := os.ReadDir(storePath)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, ErrSessionStorePathNotExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if path is writable
|
||||
testPath := path.Join(storePath, SessionTestFile)
|
||||
testFile, err := os.OpenFile(testPath, os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
return nil, ErrSessionStoreNoPermission
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err = testFile.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = os.Remove(testPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Store{
|
||||
FilesystemStore: sessions.NewFilesystemStore(storePath, keyPairs...),
|
||||
storePath: storePath,
|
||||
log: log.WithField("logger", "authentik.outpost.proxyv2.filesystemstore"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SessionCleanup acquires a file lock to ensure only one instance runs at a time,
|
||||
// then checks and deletes expired session files from the filesystem session store.
|
||||
// It supports context-based cancellation to allow graceful shutdowns or timeouts.
|
||||
func (s *Store) SessionCleanup(ctx context.Context) error {
|
||||
s.log.Info("Starting session cleanup")
|
||||
lockPath := path.Join(s.storePath, SessionCleanupLockFileName)
|
||||
lockFile, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := lockFile.Close(); closeErr != nil {
|
||||
s.log.WithError(closeErr).Warn("failed to close lock file")
|
||||
}
|
||||
}()
|
||||
|
||||
err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EWOULDBLOCK {
|
||||
return ErrSessionCleanupAlreadyRunning
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if flockErr := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN); flockErr != nil {
|
||||
s.log.WithError(flockErr).Warn("failed to unlock file")
|
||||
}
|
||||
|
||||
if removeErr := os.Remove(lockPath); removeErr != nil {
|
||||
s.log.WithError(removeErr).Warn("failed to remove lock file")
|
||||
}
|
||||
}()
|
||||
|
||||
return s.sessionCleanup(ctx)
|
||||
}
|
||||
|
||||
// sessionCleanup checks the modification time of all session files and removes them
|
||||
// when they reach the configured maximum age in the session store.
|
||||
// Since the FilesystemStore from Gorilla does not have a session cleanup function,
|
||||
// it is only necessary for the filesystem session store.
|
||||
func (s *Store) sessionCleanup(ctx context.Context) error {
|
||||
files, err := os.ReadDir(s.storePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for _, file := range files {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
s.log.Warn("session cleanup interrupted during file processing")
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(file.Name(), SessionFilePrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
fullPath := path.Join(s.storePath, file.Name())
|
||||
stat, err := os.Lstat(fullPath)
|
||||
if err != nil {
|
||||
s.log.WithError(err).WithField("path", fullPath).Warning("failed to read stats from file")
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
modTime := stat.ModTime()
|
||||
if time.Since(modTime) <= time.Duration(s.Options.MaxAge)*time.Second {
|
||||
s.log.WithField("max-age", s.Options.MaxAge).WithField("modified", modTime.String()).Debug("session still valid")
|
||||
continue
|
||||
}
|
||||
|
||||
s.log.WithField("path", fullPath).WithField("modified", modTime.String()).Info("cleanup expired session")
|
||||
if err = os.Remove(fullPath); err != nil {
|
||||
s.log.WithError(err).WithField("path", fullPath).Warn("failed to delete session")
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
var (
|
||||
cancelCleanup context.CancelFunc
|
||||
doneCleanup chan struct{}
|
||||
globalStore *Store
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
// GetPersistentStore creates a new filesystem store if it is the first time the function has been called,
|
||||
// or if the path string has changed. It then stores this in the globalStore variable.
|
||||
// If the function is called multiple times, the store from the variable is returned to ensure that only one instance is running.
|
||||
func GetPersistentStore(path string) (*Store, error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if globalStore == nil || globalStore.storePath != path {
|
||||
if cancelCleanup != nil {
|
||||
cancelCleanup()
|
||||
if doneCleanup != nil {
|
||||
<-doneCleanup
|
||||
}
|
||||
}
|
||||
store, err := NewStore(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
globalStore = store
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancelCleanup = cancel
|
||||
doneCleanup = make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(doneCleanup)
|
||||
globalStore.log.Info("Scheduling session cleanup job")
|
||||
ticker := time.NewTicker(SessionCleanupInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
err := globalStore.SessionCleanup(ctx)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, ErrSessionCleanupAlreadyRunning) {
|
||||
globalStore.log.WithError(err).Warn("Session cleanup is locked by another job")
|
||||
continue
|
||||
}
|
||||
globalStore.log.WithError(err).Warn("Session cleanup returned error")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return globalStore, nil
|
||||
}
|
||||
|
||||
// StopPersistentStore stops the cleanup background job and clears the globalStore variable.
|
||||
func StopPersistentStore() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if cancelCleanup != nil {
|
||||
cancelCleanup()
|
||||
if doneCleanup != nil {
|
||||
<-doneCleanup
|
||||
}
|
||||
}
|
||||
cancelCleanup = nil
|
||||
doneCleanup = nil
|
||||
globalStore = nil
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package filesystemstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func createTempSessionFile(t *testing.T, dir string, modTime time.Time) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(dir, "session_test")
|
||||
err := os.WriteFile(path, []byte("session data"), 0600)
|
||||
require.NoError(t, err)
|
||||
err = os.Chtimes(path, modTime, modTime)
|
||||
require.NoError(t, err)
|
||||
return path
|
||||
}
|
||||
|
||||
func TestNewStore_PathNotExist(t *testing.T) {
|
||||
_, err := NewStore("/invalid_path")
|
||||
assert.ErrorIs(t, err, ErrSessionStorePathNotExist)
|
||||
}
|
||||
|
||||
func TestNewStore_PathNotWritable(t *testing.T) {
|
||||
storePath := path.Join(os.TempDir(), "test")
|
||||
err := os.Mkdir(storePath, 0400)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = NewStore(storePath)
|
||||
assert.ErrorIs(t, err, ErrSessionStoreNoPermission)
|
||||
|
||||
_ = os.RemoveAll(storePath)
|
||||
}
|
||||
|
||||
func TestNewStore(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
store, err := NewStore(tmpDir)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, store)
|
||||
}
|
||||
|
||||
func TestSessionCleanup_RemovesExpired(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
store, err := NewStore(tmpDir)
|
||||
require.NoError(t, err)
|
||||
store.Options.MaxAge = 1 // 1 second
|
||||
|
||||
// Create an expired session file
|
||||
oldTime := time.Now().Add(-10 * time.Second)
|
||||
createTempSessionFile(t, tmpDir, oldTime)
|
||||
|
||||
ctx := context.Background()
|
||||
err = store.SessionCleanup(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// File should be deleted
|
||||
files, _ := os.ReadDir(tmpDir)
|
||||
assert.Empty(t, files)
|
||||
}
|
||||
|
||||
func TestSessionCleanup_PreservesValid(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
store, err := NewStore(tmpDir)
|
||||
require.NoError(t, err)
|
||||
store.Options.MaxAge = 3600 // 1 hour
|
||||
|
||||
// Create a valid (non-expired) session file
|
||||
modTime := time.Now().Add(-10 * time.Second)
|
||||
createTempSessionFile(t, tmpDir, modTime)
|
||||
|
||||
ctx := context.Background()
|
||||
err = store.SessionCleanup(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// File should still exist
|
||||
files, _ := os.ReadDir(tmpDir)
|
||||
assert.Len(t, files, 1)
|
||||
}
|
||||
|
||||
func TestSessionCleanup_ContextCancel(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
store, err := NewStore(tmpDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // cancel immediately
|
||||
|
||||
err = store.SessionCleanup(ctx)
|
||||
assert.ErrorIs(t, err, context.Canceled)
|
||||
}
|
||||
|
||||
func TestSessionCleanup_AlreadyRunning(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
store, err := NewStore(tmpDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Manually acquire the lock before calling SessionCleanup
|
||||
lockPath := path.Join(tmpDir, SessionCleanupLockFileName)
|
||||
lockFile, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
|
||||
require.NoError(t, err, "failed to create lock file")
|
||||
|
||||
err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
require.NoError(t, err, "failed to acquire lock for test")
|
||||
|
||||
// Run SessionCleanup while lock is held
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err = store.SessionCleanup(ctx)
|
||||
assert.ErrorIs(t, err, ErrSessionCleanupAlreadyRunning)
|
||||
|
||||
// Unlock and clean up
|
||||
_ = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN)
|
||||
_ = lockFile.Close()
|
||||
_ = os.Remove(lockPath)
|
||||
}
|
||||
|
||||
func TestPersistentStore_ReusesStore(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
store1, err := GetPersistentStore(tmpDir)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, store1)
|
||||
|
||||
store2, err := GetPersistentStore(tmpDir)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, store1, store2)
|
||||
|
||||
StopPersistentStore()
|
||||
}
|
||||
|
||||
func TestStopPersistentStore(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_, err := GetPersistentStore(tmpDir)
|
||||
require.NoError(t, err)
|
||||
StopPersistentStore()
|
||||
|
||||
// call again should not panic
|
||||
StopPersistentStore()
|
||||
}
|
||||
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1024.0",
|
||||
"aws-cdk": "^2.1023.0",
|
||||
"cross-env": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -24,9 +24,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.1024.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1024.0.tgz",
|
||||
"integrity": "sha512-hY0iVT2gPX/QOQXL7RSP2sqIRI/4BYU27vSmbhZxLEj//c3pkMkd9QpIHj7gOhyWC2gf6n5JuYPw27Dgw8FEdA==",
|
||||
"version": "2.1023.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1023.0.tgz",
|
||||
"integrity": "sha512-DWMA+IrAsBUNF2RvH7ujpDp7wSJkqTkRL8yfK4AYpEjoGY1KMaKIfxz3M3+Nk3ogM7VhZiW3OGWEOgyDF47HOQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1024.0",
|
||||
"aws-cdk": "^2.1023.0",
|
||||
"cross-env": "^10.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-08-07 00:12+0000\n"
|
||||
"POT-Creation-Date: 2025-07-28 16:09+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"
|
||||
@@ -1483,27 +1483,27 @@ msgstr ""
|
||||
msgid "Invalid Regex Pattern: {url}"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on the Hashed User ID"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on user ID"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on user UUID"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on the username"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on the User's Email. This is recommended over the UPN method."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid ""
|
||||
"Based on the User's UPN, only works if user has a 'upn' attribute set. Use "
|
||||
"this method only if you have different UPN and Mail domains."
|
||||
@@ -1617,10 +1617,6 @@ msgstr ""
|
||||
msgid "Redirect URIs"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Back-Channel Logout URI"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Include claims in id_token"
|
||||
msgstr ""
|
||||
@@ -1736,14 +1732,6 @@ msgstr ""
|
||||
msgid "Device Tokens"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/tasks.py
|
||||
msgid "Send a back-channel logout request to the registered client"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/tasks.py
|
||||
msgid "Handle backchannel logout notifications dispatched via signal"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/views/authorize.py
|
||||
#: authentik/providers/saml/views/flows.py
|
||||
#, python-brace-format
|
||||
@@ -3250,13 +3238,6 @@ msgstr ""
|
||||
msgid "Account Confirmation"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"The time window used to count recent account recovery attempts. If the "
|
||||
"number of attempts exceed recovery_max_attempts within this period, further "
|
||||
"attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3)."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid "Activate users upon completion of stage."
|
||||
msgstr ""
|
||||
@@ -3281,13 +3262,6 @@ msgstr ""
|
||||
msgid "Email sent."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Too many account verification attempts. Please try again after {minutes} "
|
||||
"minutes."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Email Successfully sent."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -19,7 +19,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-08-07 00:12+0000\n"
|
||||
"POT-Creation-Date: 2025-06-04 00:12+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Marc Schmitt, 2025\n"
|
||||
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
||||
@@ -33,10 +33,6 @@ msgstr ""
|
||||
msgid "Version history"
|
||||
msgstr "Historique des versions"
|
||||
|
||||
#: authentik/admin/tasks.py
|
||||
msgid "Update latest version info."
|
||||
msgstr "Mettre à jour les dernières informations de version."
|
||||
|
||||
#: authentik/admin/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "New version {version} available!"
|
||||
@@ -92,25 +88,10 @@ msgstr "Instances du plan"
|
||||
msgid "authentik Export - {date}"
|
||||
msgstr "Export authentik - {date}"
|
||||
|
||||
#: authentik/blueprints/v1/tasks.py
|
||||
msgid "Find blueprints as `blueprints_find` does, but return a safe dict."
|
||||
msgstr ""
|
||||
"Cherche les plans comme le fait `blueprints_find`, mais renvoie un safe "
|
||||
"dict."
|
||||
|
||||
#: authentik/blueprints/v1/tasks.py
|
||||
msgid "Find blueprints and check if they need to be created in the database."
|
||||
msgstr ""
|
||||
"Cherche les plans et vérifie s'ils doivent être créés dans la base de "
|
||||
"données."
|
||||
|
||||
#: authentik/blueprints/v1/tasks.py
|
||||
msgid "Apply single blueprint."
|
||||
msgstr "Applique un seul plan."
|
||||
|
||||
#: authentik/blueprints/v1/tasks.py
|
||||
msgid "Remove blueprints which couldn't be fetched."
|
||||
msgstr "Supprime les plans qui n'ont pas pu être récupérés."
|
||||
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Successfully imported {count} files."
|
||||
msgstr "{count} fichiers importés avec succès."
|
||||
|
||||
#: authentik/brands/models.py
|
||||
msgid ""
|
||||
@@ -148,6 +129,10 @@ msgstr "Marques"
|
||||
msgid "User does not have access to application."
|
||||
msgstr "L'utilisateur n'a pas accès à l'application."
|
||||
|
||||
#: authentik/core/api/devices.py
|
||||
msgid "Extra description not available"
|
||||
msgstr "Description supplémentaire indisponible"
|
||||
|
||||
#: authentik/core/api/groups.py
|
||||
msgid "Cannot set group as parent of itself."
|
||||
msgstr "Impossible de définir le groupe en tant que parent de lui-même."
|
||||
@@ -394,10 +379,6 @@ msgstr "Jetons"
|
||||
msgid "View token's key"
|
||||
msgstr "Voir la clé du jeton"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Set a token's key"
|
||||
msgstr "Définir la clé d'un jeton"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Property Mapping"
|
||||
msgstr "Mappage de propriété"
|
||||
@@ -453,14 +434,6 @@ msgstr "{source} liée avec succès !"
|
||||
msgid "Source is not configured for enrollment."
|
||||
msgstr "La source n'est pas configurée pour l'inscription."
|
||||
|
||||
#: authentik/core/tasks.py
|
||||
msgid "Remove expired objects."
|
||||
msgstr "Supprime les objets expirés"
|
||||
|
||||
#: authentik/core/tasks.py
|
||||
msgid "Remove temporary users created by SAML Sources."
|
||||
msgstr "Supprime les utilisateurs temporaires créés par les sources SAML."
|
||||
|
||||
#: authentik/core/templates/if/error.html
|
||||
msgid "Go home"
|
||||
msgstr "Retourner à l'accueil"
|
||||
@@ -513,12 +486,6 @@ msgstr "Paire de clé/certificat"
|
||||
msgid "Certificate-Key Pairs"
|
||||
msgstr "Paires de clé/certificat"
|
||||
|
||||
#: authentik/crypto/tasks.py
|
||||
msgid "Discover, import and update certificates from the filesystem."
|
||||
msgstr ""
|
||||
"Découvre, importe et met à jour les certificats depuis le système de "
|
||||
"fichiers."
|
||||
|
||||
#: authentik/enterprise/api.py
|
||||
msgid "Enterprise is required to create/update this object."
|
||||
msgstr "Entreprise est requis pour créer/mettre à jour cet objet."
|
||||
@@ -571,18 +538,6 @@ msgstr "Politiques d'unicité des mots de passe"
|
||||
msgid "User Password History"
|
||||
msgstr "Historique des mots de passe utilisateur"
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/tasks.py
|
||||
msgid ""
|
||||
"Check if any UniquePasswordPolicy exists, and if not, purge the password "
|
||||
"history table."
|
||||
msgstr ""
|
||||
"Vérifie si une politique de mot de passe unique existe et, si ce n'est pas "
|
||||
"le cas, purge la table de l'historique des mots de passe."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/tasks.py
|
||||
msgid "Remove user password history that are too old."
|
||||
msgstr "Supprime l'historique des mots de passe utilisateur trop anciens."
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
msgstr "Entreprise est requis pour accéder à cette fonctionnalité."
|
||||
@@ -631,42 +586,6 @@ msgstr "Mappage de propriété Google Workspace"
|
||||
msgid "Google Workspace Provider Mappings"
|
||||
msgstr "Mappages de propriété Google Workspace"
|
||||
|
||||
#: authentik/enterprise/providers/google_workspace/tasks.py
|
||||
msgid "Sync Google Workspace provider objects."
|
||||
msgstr "Synchronise les objets du fournisseur Google Workspace."
|
||||
|
||||
#: authentik/enterprise/providers/google_workspace/tasks.py
|
||||
msgid "Full sync for Google Workspace provider."
|
||||
msgstr "Synchronisation complète pour le fournisseur Google Workspace."
|
||||
|
||||
#: authentik/enterprise/providers/google_workspace/tasks.py
|
||||
msgid "Sync a direct object (user, group) for Google Workspace provider."
|
||||
msgstr ""
|
||||
"Synchronise un objet direct (utilisateur, groupe) pour le fournisseur Google"
|
||||
" Workspace."
|
||||
|
||||
#: authentik/enterprise/providers/google_workspace/tasks.py
|
||||
msgid ""
|
||||
"Dispatch syncs for a direct object (user, group) for Google Workspace "
|
||||
"providers."
|
||||
msgstr ""
|
||||
"Déclenche des synchronisations pour un objet direct (utilisateur, groupe) "
|
||||
"pour les fournisseurs Google Workspace."
|
||||
|
||||
#: authentik/enterprise/providers/google_workspace/tasks.py
|
||||
msgid "Sync a related object (memberships) for Google Workspace provider."
|
||||
msgstr ""
|
||||
"Synchronise un objet lié (appartenances) pour le fournisseur Google "
|
||||
"Workspace."
|
||||
|
||||
#: authentik/enterprise/providers/google_workspace/tasks.py
|
||||
msgid ""
|
||||
"Dispatch syncs for a related object (memberships) for Google Workspace "
|
||||
"providers."
|
||||
msgstr ""
|
||||
"Déclenche des synchronisations pour un objet lié (appartenances) pour les "
|
||||
"fournisseurs Google Workspace."
|
||||
|
||||
#: authentik/enterprise/providers/microsoft_entra/models.py
|
||||
msgid "Microsoft Entra Provider User"
|
||||
msgstr "Utilisateur du fournisseur Microsoft Entra"
|
||||
@@ -695,42 +614,6 @@ msgstr "Mappage de propriété Microsoft Entra"
|
||||
msgid "Microsoft Entra Provider Mappings"
|
||||
msgstr "Mappages de propriété Microsoft Entra"
|
||||
|
||||
#: authentik/enterprise/providers/microsoft_entra/tasks.py
|
||||
msgid "Sync Microsoft Entra provider objects."
|
||||
msgstr "Synchronise les objets du fournisseur Microsoft Entra."
|
||||
|
||||
#: authentik/enterprise/providers/microsoft_entra/tasks.py
|
||||
msgid "Full sync for Microsoft Entra provider."
|
||||
msgstr "Synchronisation complète pour le fournisseur Microsoft Entra."
|
||||
|
||||
#: authentik/enterprise/providers/microsoft_entra/tasks.py
|
||||
msgid "Sync a direct object (user, group) for Microsoft Entra provider."
|
||||
msgstr ""
|
||||
"Synchronise un objet direct (utilisateur, groupe) pour le fournisseur "
|
||||
"Microsoft Entra."
|
||||
|
||||
#: authentik/enterprise/providers/microsoft_entra/tasks.py
|
||||
msgid ""
|
||||
"Dispatch syncs for a direct object (user, group) for Microsoft Entra "
|
||||
"providers."
|
||||
msgstr ""
|
||||
"Déclenche les synchronisations pour un objet direct (utilisateur, groupe) "
|
||||
"pour les fournisseurs Microsoft Entra."
|
||||
|
||||
#: authentik/enterprise/providers/microsoft_entra/tasks.py
|
||||
msgid "Sync a related object (memberships) for Microsoft Entra provider."
|
||||
msgstr ""
|
||||
"Synchronise un objet lié (appartenances) pour le fournisseur Microsoft "
|
||||
"Entra."
|
||||
|
||||
#: authentik/enterprise/providers/microsoft_entra/tasks.py
|
||||
msgid ""
|
||||
"Dispatch syncs for a related object (memberships) for Microsoft Entra "
|
||||
"providers."
|
||||
msgstr ""
|
||||
"Déclenche des synchronisations pour un objet lié (appartenances) pour les "
|
||||
"fournisseurs Microsoft Entra."
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Signing Key"
|
||||
@@ -769,12 +652,8 @@ msgid "SSF Stream Events"
|
||||
msgstr "Évènements du flux SSF"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/tasks.py
|
||||
msgid "Dispatch SSF events."
|
||||
msgstr "Distribue les événements SSF."
|
||||
|
||||
#: authentik/enterprise/providers/ssf/tasks.py
|
||||
msgid "Send an SSF event."
|
||||
msgstr "Envoye un événement SSF."
|
||||
msgid "Failed to send request"
|
||||
msgstr "Échec de l'envoi de la requête"
|
||||
|
||||
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||
@@ -846,9 +725,10 @@ msgstr "Étape Source"
|
||||
msgid "Source Stages"
|
||||
msgstr "Étapes Source"
|
||||
|
||||
#: authentik/enterprise/tasks.py
|
||||
msgid "Update enterprise license status."
|
||||
msgstr "Mettre à jour le statut de licence entreprise."
|
||||
#: authentik/events/api/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Successfully started task {name}."
|
||||
msgstr "La tâche {name} a été démarrée avec succès."
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid "Event"
|
||||
@@ -960,15 +840,6 @@ msgstr ""
|
||||
"Définir à quel groupe d'utilisateur cette notification doit être envoyée et "
|
||||
"affichée. Si laissé vide, les notifications ne seront pas envoyées."
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
"When enabled, notification will be sent to user the user that triggered the "
|
||||
"event.When destination_group is configured, notification is sent to both."
|
||||
msgstr ""
|
||||
"Lorsque cette option est activée, une notification est envoyée à "
|
||||
"l'utilisateur qui a déclenché l'événement. Si destination_group est "
|
||||
"configuré, la notification est envoyée aux deux."
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid "Notification Rule"
|
||||
msgstr "Règle de Notification"
|
||||
@@ -985,6 +856,10 @@ msgstr "Mappage de Webhook"
|
||||
msgid "Webhook Mappings"
|
||||
msgstr "Mappages de Webhook"
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid "Run task"
|
||||
msgstr "Lancer la tâche"
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid "System Task"
|
||||
msgstr "Tâches du système"
|
||||
@@ -993,31 +868,9 @@ msgstr "Tâches du système"
|
||||
msgid "System Tasks"
|
||||
msgstr "Tâches du système"
|
||||
|
||||
#: authentik/events/tasks.py
|
||||
msgid "Dispatch new event notifications."
|
||||
msgstr "Envoye les notifications d'un nouvel événement."
|
||||
|
||||
#: authentik/events/tasks.py
|
||||
msgid ""
|
||||
"Check if policies attached to NotificationRule match event and dispatch "
|
||||
"notification tasks."
|
||||
msgstr ""
|
||||
"Vérifier si les politiques attachées à une règle de notifications "
|
||||
"correspondent à l'événement et déclenche les tâches de notification."
|
||||
|
||||
#: authentik/events/tasks.py
|
||||
msgid "Send notification."
|
||||
msgstr "Envoye une notification."
|
||||
|
||||
#: authentik/events/tasks.py
|
||||
msgid "Cleanup events for GDPR compliance."
|
||||
msgstr "Nettoye les événements pour la conformité au RGPD."
|
||||
|
||||
#: authentik/events/tasks.py
|
||||
msgid "Cleanup seen notifications and notifications whose event expired."
|
||||
msgstr ""
|
||||
"Nettoye les notifications vues et les notifications dont l'événement a "
|
||||
"expiré."
|
||||
#: authentik/events/system_tasks.py
|
||||
msgid "Task has not been run yet."
|
||||
msgstr "Tâche pas encore exécutée."
|
||||
|
||||
#: authentik/flows/api/flows.py
|
||||
#, python-brace-format
|
||||
@@ -1198,6 +1051,32 @@ msgstr ""
|
||||
"Si activé, le fournisseur ne changera ou ne créera pas d'objets auprès du "
|
||||
"système distant."
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
msgid "Starting full provider sync"
|
||||
msgstr "Démarrage d'une synchronisation complète du fournisseur"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
msgid "Syncing users"
|
||||
msgstr "Synchronisation des utilisateurs"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
msgid "Syncing groups"
|
||||
msgstr "Synchronisation des groupes"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Syncing page {page} of {object_type}"
|
||||
msgstr "Synchronisation de la page {page} de {object_type}"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
msgid "Dropping mutating request due to dry run"
|
||||
msgstr "Abandon de la requête de mutation en raison d'une simulation"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Stopping sync due to error: {error}"
|
||||
msgstr "Arrêt de la synchronisation due à l'erreur : {error}"
|
||||
|
||||
#: authentik/lib/utils/time.py
|
||||
#, python-format
|
||||
msgid "%(value)s is not in the correct format of 'hours=3;minutes=1'."
|
||||
@@ -1304,32 +1183,6 @@ msgstr "Avant-poste"
|
||||
msgid "Outposts"
|
||||
msgstr "Avant-postes"
|
||||
|
||||
#: authentik/outposts/tasks.py
|
||||
msgid "Update cached state of service connection."
|
||||
msgstr "Met à jour l'état mis en cache de la connexion de service."
|
||||
|
||||
#: authentik/outposts/tasks.py
|
||||
msgid "Create/update/monitor/delete the deployment of an Outpost."
|
||||
msgstr "Crée/met à jour/surveille/supprime le déploiement d'un avant-poste."
|
||||
|
||||
#: authentik/outposts/tasks.py
|
||||
msgid "Ensure that all Outposts have valid Service Accounts and Tokens."
|
||||
msgstr ""
|
||||
"S'assure que tous les avant-postes ont des comptes de service et des jetons "
|
||||
"valides."
|
||||
|
||||
#: authentik/outposts/tasks.py
|
||||
msgid "Send update to outpost"
|
||||
msgstr "Envoye une mise à jour à un avant-poste"
|
||||
|
||||
#: authentik/outposts/tasks.py
|
||||
msgid "Checks the local environment and create Service connections."
|
||||
msgstr "Vérifie l'environnement local et crée les connexions de service."
|
||||
|
||||
#: authentik/outposts/tasks.py
|
||||
msgid "Terminate session on all outposts."
|
||||
msgstr "Met fin à la session sur tous les avant-postes."
|
||||
|
||||
#: authentik/policies/denied.py
|
||||
msgid "Access denied"
|
||||
msgstr "Accès refusé"
|
||||
@@ -1664,29 +1517,29 @@ msgstr "Rechercher dans l'annuaire LDAP complet"
|
||||
msgid "Invalid Regex Pattern: {url}"
|
||||
msgstr "Pattern de regex invalide : {url}"
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on the Hashed User ID"
|
||||
msgstr "Basé sur le hash de l'ID utilisateur"
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on user ID"
|
||||
msgstr "Basé sur l'ID de l'utilisateur"
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on user UUID"
|
||||
msgstr "Basé sur le UUID de l'utilisateur"
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on the username"
|
||||
msgstr "Basé sur le nom d'utilisateur"
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid "Based on the User's Email. This is recommended over the UPN method."
|
||||
msgstr ""
|
||||
"Basé sur le courriel utilisateur. Ceci est recommandé par rapport à la "
|
||||
"méthode UPN."
|
||||
|
||||
#: authentik/providers/oauth2/constants.py
|
||||
#: authentik/providers/oauth2/id_token.py
|
||||
msgid ""
|
||||
"Based on the User's UPN, only works if user has a 'upn' attribute set. Use "
|
||||
"this method only if you have different UPN and Mail domains."
|
||||
@@ -1809,10 +1662,6 @@ msgstr "Secret du client"
|
||||
msgid "Redirect URIs"
|
||||
msgstr "URIs de redirection"
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Back-Channel Logout URI"
|
||||
msgstr "URI de déconnexion Back-Channel"
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Include claims in id_token"
|
||||
msgstr "Include les demandes utilisateurs dans id_token"
|
||||
@@ -1941,15 +1790,6 @@ msgstr "Jeton d'équipement"
|
||||
msgid "Device Tokens"
|
||||
msgstr "Jetons d'équipement"
|
||||
|
||||
#: authentik/providers/oauth2/tasks.py
|
||||
msgid "Send a back-channel logout request to the registered client"
|
||||
msgstr "Envoyer une requête de déconnexion Back-Channel au client enregistré"
|
||||
|
||||
#: authentik/providers/oauth2/tasks.py
|
||||
msgid "Handle backchannel logout notifications dispatched via signal"
|
||||
msgstr ""
|
||||
"Gérer les notifications de déconnexion Back-Channel envoyées via un signal"
|
||||
|
||||
#: authentik/providers/oauth2/views/authorize.py
|
||||
#: authentik/providers/saml/views/flows.py
|
||||
#, python-brace-format
|
||||
@@ -2061,10 +1901,6 @@ msgstr "Fournisseur Proxy"
|
||||
msgid "Proxy Providers"
|
||||
msgstr "Fournisseur de Proxy"
|
||||
|
||||
#: authentik/providers/proxy/tasks.py
|
||||
msgid "Terminate session on Proxy outpost."
|
||||
msgstr "Met fin à la session sur l'avant-poste Proxy."
|
||||
|
||||
#: authentik/providers/rac/models.py authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
@@ -2409,35 +2245,6 @@ msgstr "Mappage fournisseur SCIM"
|
||||
msgid "SCIM Provider Mappings"
|
||||
msgstr "Mappages fournisseur SCIM"
|
||||
|
||||
#: authentik/providers/scim/tasks.py
|
||||
msgid "Sync SCIM provider objects."
|
||||
msgstr "Synchronise les objets du fournisseur SCIM."
|
||||
|
||||
#: authentik/providers/scim/tasks.py
|
||||
msgid "Full sync for SCIM provider."
|
||||
msgstr "Synchronisation complète pour le fournisseur SCIM."
|
||||
|
||||
#: authentik/providers/scim/tasks.py
|
||||
msgid "Sync a direct object (user, group) for SCIM provider."
|
||||
msgstr ""
|
||||
"Synchronise un objet direct (utilisateur, groupe) pour le fournisseur SCIM."
|
||||
|
||||
#: authentik/providers/scim/tasks.py
|
||||
msgid "Dispatch syncs for a direct object (user, group) for SCIM providers."
|
||||
msgstr ""
|
||||
"Déclenche les synchronisations pour un objet direct (utilisateur, groupe) "
|
||||
"pour les fournisseurs SCIM."
|
||||
|
||||
#: authentik/providers/scim/tasks.py
|
||||
msgid "Sync a related object (memberships) for SCIM provider."
|
||||
msgstr "Synchronise un objet lié (appartenances) pour le fournisseur SCIM."
|
||||
|
||||
#: authentik/providers/scim/tasks.py
|
||||
msgid "Dispatch syncs for a related object (memberships) for SCIM providers."
|
||||
msgstr ""
|
||||
"Déclenche des synchronisations pour un objet lié (appartenances) pour les "
|
||||
"fournisseurs SCIM."
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Role"
|
||||
msgstr "Rôle"
|
||||
@@ -2592,14 +2399,6 @@ msgstr "Connexion du groupe à la source Kerberos"
|
||||
msgid "Group Kerberos Source Connections"
|
||||
msgstr "Connexions du groupe à la source Kerberos"
|
||||
|
||||
#: authentik/sources/kerberos/tasks.py
|
||||
msgid "Check connectivity for Kerberos sources."
|
||||
msgstr "Vérifie la connectivité des sources Kerberos."
|
||||
|
||||
#: authentik/sources/kerberos/tasks.py
|
||||
msgid "Sync Kerberos source."
|
||||
msgstr "Synchronise la source Kerberos."
|
||||
|
||||
#: authentik/sources/kerberos/views.py
|
||||
msgid "SPNEGO authentication required"
|
||||
msgstr "Authentification SPNEGO requise"
|
||||
@@ -2767,18 +2566,6 @@ msgstr "Connexions du groupe à la source LDAP"
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
msgstr "Le mot de passe ne correspond pas à la complexité d'Active Directory."
|
||||
|
||||
#: authentik/sources/ldap/tasks.py
|
||||
msgid "Check connectivity for LDAP source."
|
||||
msgstr "Vérifie la connectivité des sources LDAP."
|
||||
|
||||
#: authentik/sources/ldap/tasks.py
|
||||
msgid "Sync LDAP source."
|
||||
msgstr "Synchronise la source LDAP."
|
||||
|
||||
#: authentik/sources/ldap/tasks.py
|
||||
msgid "Sync page for LDAP source."
|
||||
msgstr "Synchronise une page pour la source LDAP."
|
||||
|
||||
#: authentik/sources/oauth/clients/oauth2.py
|
||||
msgid "No token received."
|
||||
msgstr "Pas de jeton reçu."
|
||||
@@ -2928,14 +2715,6 @@ msgstr "Source d'OAuth Azure AD"
|
||||
msgid "Azure AD OAuth Sources"
|
||||
msgstr "Source d'OAuth Azure AD"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Entra ID OAuth Source"
|
||||
msgstr "Source d'OAuth Entra ID"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Entra ID OAuth Sources"
|
||||
msgstr "Sources d'OAuth Entra ID"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OpenID OAuth Source"
|
||||
msgstr "Source d'OAuth OpenID"
|
||||
@@ -2992,14 +2771,6 @@ msgstr "Connexion du groupe à la source OAuth"
|
||||
msgid "Group OAuth Source Connections"
|
||||
msgstr "Connexions du groupe à la source OAuth"
|
||||
|
||||
#: authentik/sources/oauth/tasks.py
|
||||
msgid ""
|
||||
"Update OAuth sources' config from well_known, and JWKS info from the "
|
||||
"configured URL."
|
||||
msgstr ""
|
||||
"Met à jour la configuration des sources OAuth à partir de well_known, et les"
|
||||
" informations JWKS à partir de l'URL configurée."
|
||||
|
||||
#: authentik/sources/oauth/views/callback.py
|
||||
#, python-brace-format
|
||||
msgid "Authentication failed: {reason}"
|
||||
@@ -3058,10 +2829,6 @@ msgstr "Connexion du groupe à la source Plex"
|
||||
msgid "Group Plex Source Connections"
|
||||
msgstr "Connexions du groupe à la source OAuth"
|
||||
|
||||
#: authentik/sources/plex/tasks.py
|
||||
msgid "Check the validity of a Plex source."
|
||||
msgstr "Vérifie la validité d'une source Plex."
|
||||
|
||||
#: authentik/sources/saml/models.py
|
||||
msgid "Redirect Binding"
|
||||
msgstr "Liaison de Redirection"
|
||||
@@ -3506,13 +3273,6 @@ msgstr "Type d'appareil WebAuthn"
|
||||
msgid "WebAuthn Device types"
|
||||
msgstr "Types d'appareil WebAuthn"
|
||||
|
||||
#: authentik/stages/authenticator_webauthn/tasks.py
|
||||
msgid ""
|
||||
"Background task to import FIDO Alliance MDS blob and AAGUIDs into database."
|
||||
msgstr ""
|
||||
"Tâche de fond pour importer le blob MDS de la FIDO Alliance et les AAGUID "
|
||||
"dans la base de données."
|
||||
|
||||
#: authentik/stages/captcha/models.py
|
||||
msgid "Public key, acquired your captcha Provider."
|
||||
msgstr "Clé publique, acquise auprès de votre fournisseur captcha."
|
||||
@@ -3611,17 +3371,6 @@ msgstr "Réinitialiser le Mot de Passe"
|
||||
msgid "Account Confirmation"
|
||||
msgstr "Confirmation du Compte"
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"The time window used to count recent account recovery attempts. If the "
|
||||
"number of attempts exceed recovery_max_attempts within this period, further "
|
||||
"attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3)."
|
||||
msgstr ""
|
||||
"La fenêtre de temps utilisée pour compter les tentatives récentes de "
|
||||
"récupération de compte. Si le nombre de tentatives dépasse "
|
||||
"recovery_max_attempts au cours de cette période, les tentatives "
|
||||
"supplémentaires seront limitées. (Format : hours=1;minutes=2;seconds=3)."
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid "Activate users upon completion of stage."
|
||||
msgstr "Activer les utilisateurs à la complétion de l'étape."
|
||||
@@ -3646,23 +3395,10 @@ msgstr "Pas d'utilisateurs en attente."
|
||||
msgid "Email sent."
|
||||
msgstr "Email envoyé."
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Too many account verification attempts. Please try again after {minutes} "
|
||||
"minutes."
|
||||
msgstr ""
|
||||
"Trop de tentatives de vérification de compte. Veuillez réessayer après "
|
||||
"{minutes} minutes."
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Email Successfully sent."
|
||||
msgstr "Couriel envoyé avec succès."
|
||||
|
||||
#: authentik/stages/email/tasks.py
|
||||
msgid "Send email."
|
||||
msgstr "Envoye un courriel."
|
||||
|
||||
#: authentik/stages/email/templates/email/account_confirmation.html
|
||||
#: authentik/stages/email/templates/email/account_confirmation.txt
|
||||
msgid "Welcome!"
|
||||
@@ -4131,16 +3867,6 @@ msgstr ""
|
||||
"souvenir de moi ne sera pas proposée. (Format: "
|
||||
"hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"When set to a non-zero value, authentik will save a cookie with a longer "
|
||||
"expiry,to remember the device the user is logging in from. (Format: "
|
||||
"hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr ""
|
||||
"Si cette valeur est différente de zéro, authentik enregistrera un cookie "
|
||||
"avec une expiration plus longue, afin de se souvenir de l'appareil à partir "
|
||||
"duquel l'utilisateur se connecte. (Format : hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/stages/user_login/models.py
|
||||
msgid "User Login Stage"
|
||||
msgstr "Étape de connexion utlisateur"
|
||||
@@ -4192,38 +3918,6 @@ msgid "Failed to update user. Please try again later."
|
||||
msgstr ""
|
||||
"Échec de mise à jour de l'utilisateur. Merci de réessayer ultérieurement,"
|
||||
|
||||
#: authentik/tasks/models.py
|
||||
msgid "Tenant this task belongs to"
|
||||
msgstr "Tenant auquel cette tâche appartient"
|
||||
|
||||
#: authentik/tasks/models.py
|
||||
msgid "Retry failed task"
|
||||
msgstr "Relancer la tâche échouée"
|
||||
|
||||
#: authentik/tasks/models.py
|
||||
msgid "Worker status"
|
||||
msgstr "État du worker"
|
||||
|
||||
#: authentik/tasks/models.py
|
||||
msgid "Worker statuses"
|
||||
msgstr "États du worker"
|
||||
|
||||
#: authentik/tasks/schedules/models.py
|
||||
msgid "Unique schedule identifier"
|
||||
msgstr "Identifiant unique des planifications"
|
||||
|
||||
#: authentik/tasks/schedules/models.py
|
||||
msgid "User schedule identifier"
|
||||
msgstr "Identifiant utilisateur des planifications"
|
||||
|
||||
#: authentik/tasks/schedules/models.py
|
||||
msgid "Manually trigger a schedule"
|
||||
msgstr "Déclencher manuellement une planification"
|
||||
|
||||
#: authentik/tasks/tasks.py
|
||||
msgid "Remove old worker statuses."
|
||||
msgstr "Supprime les anciens statuts des workers."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid ""
|
||||
"Schema name must start with t_, only contain lowercase letters and numbers "
|
||||
@@ -4316,76 +4010,3 @@ msgstr "Domaine"
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Domains"
|
||||
msgstr "Domaines"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Queue name"
|
||||
msgstr "Nom de la file"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Dramatiq actor name"
|
||||
msgstr "Nom de l'acteur Dramatiq"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Message body"
|
||||
msgstr "Corps du message"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Task status"
|
||||
msgstr "État de la tâche"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Task last modified time"
|
||||
msgstr "Heure de dernière modification de la tâche"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Task result"
|
||||
msgstr "Résultat de la tâche"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Result expiry time"
|
||||
msgstr "Délai d'expiration du résultat"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Task"
|
||||
msgstr "Tâche"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Tasks"
|
||||
msgstr "Tâches"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
#, python-format
|
||||
msgid "%(value)s is not a valid crontab"
|
||||
msgstr "%(value)s n'est pas un crontab valide"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Dramatiq actor to call"
|
||||
msgstr "Acteur Dramatiq à invoquer"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Args to send to the actor"
|
||||
msgstr "Args à passer à l'acteur"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Kwargs to send to the actor"
|
||||
msgstr "Kwargs à passer à l'acteur"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Options to send to the actor"
|
||||
msgstr "Options à passer à l'acteur"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "When to schedule tasks"
|
||||
msgstr "Quand planifier les tâches"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Pause this schedule"
|
||||
msgstr "Mettre cette planification en pause"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Schedule"
|
||||
msgstr "Planification"
|
||||
|
||||
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
|
||||
msgid "Schedules"
|
||||
msgstr "Planifications"
|
||||
|
||||
29
packages/docusaurus-config/package-lock.json
generated
29
packages/docusaurus-config/package-lock.json
generated
@@ -10,8 +10,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deepmerge-ts": "^7.1.5",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react-dom": ">=18"
|
||||
"prism-react-renderer": "^2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/theme-common": "^3.8.1",
|
||||
@@ -35,7 +34,8 @@
|
||||
"@docusaurus/theme-common": "^3.8.1",
|
||||
"@docusaurus/theme-search-algolia": "^3.8.1",
|
||||
"@docusaurus/types": "^3.8.0",
|
||||
"react": ">=18"
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@docusaurus/theme-search-algolia": {
|
||||
@@ -43,6 +43,9 @@
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4653,9 +4656,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
|
||||
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
||||
"version": "19.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz",
|
||||
"integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
@@ -15818,25 +15821,25 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
||||
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.1.1"
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-fast-compare": {
|
||||
|
||||
@@ -661,16 +661,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@gerrit0/mini-shiki": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.9.2.tgz",
|
||||
"integrity": "sha512-Tvsj+AOO4Z8xLRJK900WkyfxHsZQu+Zm1//oT1w443PO6RiYMoq/4NGOhaNuZoUMYsjKIAPVQ6eOFMddj6yphQ==",
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.7.0.tgz",
|
||||
"integrity": "sha512-7iY9wg4FWXmeoFJpUL2u+tsmh0d0jcEJHAIzVxl3TG4KL493JNnisdLAILZ77zcD+z3J0keEXZ+lFzUgzQzPDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/engine-oniguruma": "^3.9.2",
|
||||
"@shikijs/langs": "^3.9.2",
|
||||
"@shikijs/themes": "^3.9.2",
|
||||
"@shikijs/types": "^3.9.2",
|
||||
"@shikijs/engine-oniguruma": "^3.7.0",
|
||||
"@shikijs/langs": "^3.7.0",
|
||||
"@shikijs/themes": "^3.7.0",
|
||||
"@shikijs/types": "^3.7.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2"
|
||||
}
|
||||
},
|
||||
@@ -845,40 +845,40 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/engine-oniguruma": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.9.2.tgz",
|
||||
"integrity": "sha512-Vn/w5oyQ6TUgTVDIC/BrpXwIlfK6V6kGWDVVz2eRkF2v13YoENUvaNwxMsQU/t6oCuZKzqp9vqtEtEzKl9VegA==",
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.7.0.tgz",
|
||||
"integrity": "sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.9.2",
|
||||
"@shikijs/types": "3.7.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/langs": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.9.2.tgz",
|
||||
"integrity": "sha512-X1Q6wRRQXY7HqAuX3I8WjMscjeGjqXCg/Sve7J2GWFORXkSrXud23UECqTBIdCSNKJioFtmUGJQNKtlMMZMn0w==",
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.7.0.tgz",
|
||||
"integrity": "sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.9.2"
|
||||
"@shikijs/types": "3.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/themes": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.9.2.tgz",
|
||||
"integrity": "sha512-6z5lBPBMRfLyyEsgf6uJDHPa6NAGVzFJqH4EAZ+03+7sedYir2yJBRu2uPZOKmj43GyhVHWHvyduLDAwJQfDjA==",
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.7.0.tgz",
|
||||
"integrity": "sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.9.2"
|
||||
"@shikijs/types": "3.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/types": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.9.2.tgz",
|
||||
"integrity": "sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw==",
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.7.0.tgz",
|
||||
"integrity": "sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -904,13 +904,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
|
||||
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
|
||||
"version": "24.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
|
||||
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.10.0"
|
||||
"undici-types": "~7.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
@@ -2668,9 +2668,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
|
||||
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
|
||||
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2704,13 +2704,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc": {
|
||||
"version": "0.28.9",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.9.tgz",
|
||||
"integrity": "sha512-aw45vwtwOl3QkUAmWCnLV9QW1xY+FSX2zzlit4MAfE99wX+Jij4ycnpbAWgBXsRrxmfs9LaYktg/eX5Bpthd3g==",
|
||||
"version": "0.28.8",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.8.tgz",
|
||||
"integrity": "sha512-16GfLopc8icHfdvqZDqdGBoS2AieIRP2rpf9mU+MgN+gGLyEQvAO0QgOa6NJ5QNmQi0LFrDY9in4F2fUNKgJKA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@gerrit0/mini-shiki": "^3.9.0",
|
||||
"@gerrit0/mini-shiki": "^3.7.0",
|
||||
"lunr": "^2.3.9",
|
||||
"markdown-it": "^14.1.0",
|
||||
"minimatch": "^9.0.5",
|
||||
@@ -2724,7 +2724,7 @@
|
||||
"pnpm": ">= 10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x"
|
||||
"typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc-plugin-markdown": {
|
||||
@@ -2741,9 +2741,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"version": "5.9.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -2762,9 +2762,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
|
||||
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
||||
482
packages/eslint-config/package-lock.json
generated
482
packages/eslint-config/package-lock.json
generated
@@ -500,102 +500,15 @@
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz",
|
||||
"integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.0",
|
||||
"@typescript-eslint/type-utils": "8.39.0",
|
||||
"@typescript-eslint/utils": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.39.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
|
||||
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz",
|
||||
"integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz",
|
||||
"integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.39.0",
|
||||
"@typescript-eslint/types": "^8.39.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz",
|
||||
"integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz",
|
||||
"integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0"
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -605,52 +518,10 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz",
|
||||
"integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz",
|
||||
"integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0",
|
||||
"@typescript-eslint/utils": "8.39.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz",
|
||||
"integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
|
||||
"integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -661,106 +532,14 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz",
|
||||
"integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.39.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^9.0.4",
|
||||
"semver": "^7.6.0",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz",
|
||||
"integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz",
|
||||
"integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz",
|
||||
"integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4708,16 +4487,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.0.tgz",
|
||||
"integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.38.0.tgz",
|
||||
"integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.39.0",
|
||||
"@typescript-eslint/parser": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0",
|
||||
"@typescript-eslint/utils": "8.39.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0",
|
||||
"@typescript-eslint/utils": "8.38.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -4728,7 +4507,228 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz",
|
||||
"integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/type-utils": "8.38.0",
|
||||
"@typescript-eslint/utils": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.38.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz",
|
||||
"integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0",
|
||||
"@typescript-eslint/utils": "8.38.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
|
||||
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
|
||||
"integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.38.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^9.0.4",
|
||||
"semver": "^7.6.0",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
|
||||
"integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.38.0",
|
||||
"@typescript-eslint/types": "^8.38.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
|
||||
"integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
|
||||
"integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/ignore": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
|
||||
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
|
||||
92
packages/prettier-config/package-lock.json
generated
92
packages/prettier-config/package-lock.json
generated
@@ -73,48 +73,48 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@dozerg/condition": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/condition/-/condition-1.0.11.tgz",
|
||||
"integrity": "sha512-+rKdLoe9mKmiXz4JuigIYSHhKKJFIiFkr6CVV0ilS7UqJjk5KHCSycb88cyGQIA0/p5UpgzVdzxbwESh93/Xrg==",
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/condition/-/condition-1.0.10.tgz",
|
||||
"integrity": "sha512-TenWvtppkVU/MnGEG/Z8O62ZmAjvR45vNXdtV8YkJb6d+RCrqqmo4vgcrWjre6WO4u+wciTOtFa6Xvafp/LicA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@dozerg/end-of-line": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/end-of-line/-/end-of-line-1.0.20.tgz",
|
||||
"integrity": "sha512-DuKvHM/02lJldJDAExcvtsJEp7wteGyE9KbnGWX2Y/lrsAMca0PZY7kDs3xunGHh+rlWogMwf1jbR6rW4Qh5/w==",
|
||||
"version": "1.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/end-of-line/-/end-of-line-1.0.19.tgz",
|
||||
"integrity": "sha512-l8lOHu9O8tv/HxKknlRLVZ563mTSRuMfGGD1rVBUPCphzCV8J9z4epu10AWCye61m9nMllm0EvcgpoUZ7GKqkg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@dozerg/find-up": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/find-up/-/find-up-1.0.9.tgz",
|
||||
"integrity": "sha512-lWaeo2wMcTR5y40TIKao4d2eTKfUEaWDTXFRNigzyd4V/3o0HrvlpnP9Twoz+8ErLgW5qg9FpIR0ocNOYbPqIg==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/find-up/-/find-up-1.0.8.tgz",
|
||||
"integrity": "sha512-mIOqSViLKe+Mpz7hcCHPwuuNnmfwmQ1DGUWTJowO3ilHZYC8ET9bxIkKygEn6qavmXxcLIgeqcnOec4yHtakrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dozerg/condition": "^1.0.11"
|
||||
"@dozerg/condition": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@dozerg/merge-options": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/merge-options/-/merge-options-1.0.12.tgz",
|
||||
"integrity": "sha512-RK50IaS0R+CzIzNicZqxmnIjz8TyWoN8y/RfeOanTBeAR4DnRtGV/1Ee3Mbcodrvta1eBcSQvgEbPcKI9u40PQ==",
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/merge-options/-/merge-options-1.0.11.tgz",
|
||||
"integrity": "sha512-4JTleitLzGXhLaZ39fxM5pLdQNHu7FX/udJx0p/vsbHUgthw3gM+eQtEOUO4J+IC6HRGpQ7m0qKEc4P276HWNQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dozerg/condition": "^1.0.11"
|
||||
"@dozerg/condition": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@dozerg/no-new": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/no-new/-/no-new-0.0.9.tgz",
|
||||
"integrity": "sha512-0hTRFDQOSDq4obHYSS/6M2N1YgXbxr2RF2L1ONVMLEtfLsJP7UPJ7CHUkTV3RCv1sNgmPCJEfIgCm79PrXGcmg==",
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/no-new/-/no-new-0.0.7.tgz",
|
||||
"integrity": "sha512-ElmuHoVmQrTA0feh1lQRHWVNeh2g6lYJXV+R2ciKmNQCdQwADgsoGAQ8BCp7PTZck4Q/3SZuy5j41Br41E6UGA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@dozerg/require-module": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/require-module/-/require-module-0.0.10.tgz",
|
||||
"integrity": "sha512-3CE3tPYeHxoDVctWE5xxjYSgGOB+NWThRnXqX3lMU76Rn2Njk7VCiPu9UVTlTD2SN9NiFmTLmxYZGFOf88noNw==",
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@dozerg/require-module/-/require-module-0.0.8.tgz",
|
||||
"integrity": "sha512-tnW9TQzCSbZ2UNDeLjlsilihS6BdtNY1TIfiCT6r6qQ4h+OHEKaNSFG/k0FJIZDPFE/fmEVlZErHdkPK1j5VYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dozerg/find-up": "^1.0.9",
|
||||
"@dozerg/find-up": "^1.0.7",
|
||||
"log4js": "^6.9.1"
|
||||
}
|
||||
},
|
||||
@@ -385,13 +385,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
|
||||
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
|
||||
"version": "24.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
|
||||
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.10.0"
|
||||
"undici-types": "~7.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
@@ -958,32 +958,32 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/format-imports": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/format-imports/-/format-imports-4.0.8.tgz",
|
||||
"integrity": "sha512-lHZlTxZlfqqiCzlLUe3Lx1d7vzqHHM5nW4xsLnZzc7gcrhqA6lCJSuUnjKGaYVpkTFgp4435RyamEXSU0YxvZg==",
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/format-imports/-/format-imports-4.0.7.tgz",
|
||||
"integrity": "sha512-0zT3DL9wtFjqR0kKAI2rBLBZlrDxPoihbxrLicC3wqtjEGRL8ojjpSzPBnKqgJkzYR2TDY8DIuaYfcGqWHhePA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dozerg/condition": "^1.0.11",
|
||||
"@dozerg/end-of-line": "^1.0.20",
|
||||
"@dozerg/find-up": "^1.0.9",
|
||||
"@dozerg/merge-options": "^1.0.12",
|
||||
"@dozerg/no-new": "^0.0.9",
|
||||
"@dozerg/require-module": "^0.0.10",
|
||||
"@dozerg/condition": "^1.0.9",
|
||||
"@dozerg/end-of-line": "^1.0.18",
|
||||
"@dozerg/find-up": "^1.0.7",
|
||||
"@dozerg/merge-options": "^1.0.10",
|
||||
"@dozerg/no-new": "^0.0.7",
|
||||
"@dozerg/require-module": "^0.0.8",
|
||||
"@vue/compiler-sfc": "3.3.11",
|
||||
"eslint": "^8.57.1",
|
||||
"fs-extra": "^11.3.0",
|
||||
"immutable": "^5.1.3",
|
||||
"fs-extra": "^11.2.0",
|
||||
"immutable": "^5.0.3",
|
||||
"is-builtin-module": "^3.2.1",
|
||||
"log4js": "^6.9.1",
|
||||
"minimatch": "^10.0.3",
|
||||
"minimatch": "^10.0.1",
|
||||
"node-cache": "^5.1.2",
|
||||
"optionator": "^0.9.4",
|
||||
"prettier": "^3.6.2",
|
||||
"segment-sort": "^1.0.9",
|
||||
"prettier": "^3.4.2",
|
||||
"segment-sort": "^1.0.7",
|
||||
"tmp": "^0.2.3",
|
||||
"typescript": "^5.7.2",
|
||||
"utility-types": "^3.11.0",
|
||||
"validator": "^13.15.15"
|
||||
"validator": "^13.12.0"
|
||||
},
|
||||
"bin": {
|
||||
"format-imports": "dist/bin/main.js"
|
||||
@@ -1509,9 +1509,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/segment-sort": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/segment-sort/-/segment-sort-1.0.9.tgz",
|
||||
"integrity": "sha512-MnxEGYVhbmQ66R1WlCH+NVJGAwsJorio6pNO8IRpUWZiv1lF601Mi6s1P0qr6q+PHnjc6IKO2XEnXB6c+6eUrQ==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/segment-sort/-/segment-sort-1.0.8.tgz",
|
||||
"integrity": "sha512-CUNjTlN5/Q7kmIGiyK6wFcfHgnQOIb88qRgCMJ61U3KCgLbPmRdkft8QW7b+Aoh4T1iEII54/e5cf3uSQwoGvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
@@ -1724,9 +1724,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
|
||||
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
||||
41
schema.yml
41
schema.yml
@@ -44845,15 +44845,6 @@ components:
|
||||
activate_user_on_success:
|
||||
type: boolean
|
||||
description: Activate users upon completion of stage.
|
||||
recovery_max_attempts:
|
||||
type: integer
|
||||
maximum: 2147483647
|
||||
minimum: 0
|
||||
recovery_cache_timeout:
|
||||
type: string
|
||||
description: 'The time window used to count recent account recovery attempts.
|
||||
If the number of attempts exceed recovery_max_attempts within this period,
|
||||
further attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3).'
|
||||
required:
|
||||
- component
|
||||
- meta_model_name
|
||||
@@ -44914,16 +44905,6 @@ components:
|
||||
activate_user_on_success:
|
||||
type: boolean
|
||||
description: Activate users upon completion of stage.
|
||||
recovery_max_attempts:
|
||||
type: integer
|
||||
maximum: 2147483647
|
||||
minimum: 0
|
||||
recovery_cache_timeout:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: 'The time window used to count recent account recovery attempts.
|
||||
If the number of attempts exceed recovery_max_attempts within this period,
|
||||
further attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3).'
|
||||
required:
|
||||
- name
|
||||
Endpoint:
|
||||
@@ -49522,10 +49503,6 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RedirectURI'
|
||||
backchannel_logout_uri:
|
||||
type: string
|
||||
title: Back-Channel Logout URI
|
||||
format: uri
|
||||
sub_mode:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SubModeEnum'
|
||||
@@ -49633,10 +49610,6 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RedirectURIRequest'
|
||||
backchannel_logout_uri:
|
||||
type: string
|
||||
title: Back-Channel Logout URI
|
||||
format: uri
|
||||
sub_mode:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SubModeEnum'
|
||||
@@ -53413,16 +53386,6 @@ components:
|
||||
activate_user_on_success:
|
||||
type: boolean
|
||||
description: Activate users upon completion of stage.
|
||||
recovery_max_attempts:
|
||||
type: integer
|
||||
maximum: 2147483647
|
||||
minimum: 0
|
||||
recovery_cache_timeout:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: 'The time window used to count recent account recovery attempts.
|
||||
If the number of attempts exceed recovery_max_attempts within this period,
|
||||
further attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3).'
|
||||
PatchedEndpointDeviceRequest:
|
||||
type: object
|
||||
description: Serializer for Endpoint authenticator devices
|
||||
@@ -54556,10 +54519,6 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RedirectURIRequest'
|
||||
backchannel_logout_uri:
|
||||
type: string
|
||||
title: Back-Channel Logout URI
|
||||
format: uri
|
||||
sub_mode:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SubModeEnum'
|
||||
|
||||
31
uv.lock
generated
31
uv.lock
generated
@@ -86,15 +86,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.10.0"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -557,30 +557,30 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.40.2"
|
||||
version = "1.40.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "botocore" },
|
||||
{ name = "jmespath" },
|
||||
{ name = "s3transfer" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/c0/9ceff05d2243f169765ae9db08fa6f085d026af71a778cd083dc972f0f2b/boto3-1.40.2.tar.gz", hash = "sha256:2dfbc214fdbf94abfd61eec687ea39089d05af43bb00be792c76f3a6c1393f7b", size = 111826, upload-time = "2025-08-04T19:31:51.959Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/48/4d/70d209fdebf0377db233f80dfdf26ca2bc25d2b2e89d4882e0edccd2227f/boto3-1.40.1.tar.gz", hash = "sha256:985ed4bf64729807f870eadbc46ad98baf93096917f7194ec39d743ff75b3f1d", size = 111817, upload-time = "2025-08-01T19:24:18.017Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/66/01bccaaebcd1365ce1334be042765e49ccf23787887afb8e43c6d4bc2f6e/boto3-1.40.2-py3-none-any.whl", hash = "sha256:3d99325ee874190e8f3bfd38823987327c826cdfbab943420851bdb7684d727c", size = 139882, upload-time = "2025-08-04T19:31:50.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/0e/f0cb4f71c40ba07e6ed5b47699a737a080d3c4f4b7b26657d5671de48621/boto3-1.40.1-py3-none-any.whl", hash = "sha256:7c007d5c8ee549e9fcad0927536502da199b27891006ef515330f429aca9671f", size = 139880, upload-time = "2025-08-01T19:24:16.581Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.40.2"
|
||||
version = "1.40.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jmespath" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/e5/e7d68381042a6d50510c8d4629f39922ce27ff32f45baf852ba6534342c5/botocore-1.40.2.tar.gz", hash = "sha256:77c4710bf37b28e897833b5b1f47d6a83e45a29985cd01a560dfdb8b6ad524e5", size = 14284599, upload-time = "2025-08-04T19:31:42.064Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/d2/d914999f4a128f0f840f2a9cc8327cd98aa661d6b33b331a81a8111ab970/botocore-1.40.1.tar.gz", hash = "sha256:bdf30e2c0e8cdb939d81fc243182a6d1dd39c416694b406c5f2ea079b1c2f3f5", size = 14280398, upload-time = "2025-08-01T19:24:08.599Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/56/dd25fb9e47060e8f7e353208678fefb65d1b06704ea30983cad8bdd81370/botocore-1.40.2-py3-none-any.whl", hash = "sha256:a31e6269af05498f8dc1c7f2b3f34448a0f16c79a8601c0389ecddab51b2c2ab", size = 13944886, upload-time = "2025-08-04T19:31:37.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/c1/aa7922c9bf74b6d6594d2430af6f854d234faff23187e269aaba89c326c8/botocore-1.40.1-py3-none-any.whl", hash = "sha256:e039774b55fbd6fe59f0f4fea51d156a2433bd4d8faa64fc1b87aee9a03f415d", size = 13940950, upload-time = "2025-08-01T19:24:03.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -603,15 +603,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "cattrs"
|
||||
version = "25.1.1"
|
||||
version = "24.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/2b/561d78f488dcc303da4639e02021311728fb7fda8006dd2835550cddd9ed/cattrs-25.1.1.tar.gz", hash = "sha256:c914b734e0f2d59e5b720d145ee010f1fd9a13ee93900922a2f3f9d593b8382c", size = 435016, upload-time = "2025-06-04T20:27:15.44Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/29/7b/da4aa2f95afb2f28010453d03d6eedf018f9e085bd001f039e15731aba89/cattrs-24.1.3.tar.gz", hash = "sha256:981a6ef05875b5bb0c7fb68885546186d306f10f0f6718fe9b96c226e68821ff", size = 426684, upload-time = "2025-03-25T15:01:00.325Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/b0/215274ef0d835bbc1056392a367646648b6084e39d489099959aefcca2af/cattrs-25.1.1-py3-none-any.whl", hash = "sha256:1b40b2d3402af7be79a7e7e097a9b4cd16d4c06e6d526644b0b26a063a1cc064", size = 69386, upload-time = "2025-06-04T20:27:13.969Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/ee/d68a3de23867a9156bab7e0a22fb9a0305067ee639032a22982cf7f725e7/cattrs-24.1.3-py3-none-any.whl", hash = "sha256:adf957dddd26840f27ffbd060a6c4dd3b2192c5b7c2c0525ef1bd8131d8a83f5", size = 66462, upload-time = "2025-03-25T15:00:58.663Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -632,11 +631,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.8.3"
|
||||
version = "2025.7.14"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
1948
web/package-lock.json
generated
1948
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -83,7 +83,6 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@codecov/bundle-analyzer": "^1.9.1",
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
"@codemirror/lang-javascript": "^6.2.4",
|
||||
@@ -95,7 +94,7 @@
|
||||
"@floating-ui/dom": "^1.7.3",
|
||||
"@formatjs/intl-listformat": "^7.7.11",
|
||||
"@fortawesome/fontawesome-free": "^7.0.0",
|
||||
"@goauthentik/api": "^2025.6.4-1754491498",
|
||||
"@goauthentik/api": "^2025.6.4-1754241870",
|
||||
"@goauthentik/core": "^1.0.0",
|
||||
"@goauthentik/esbuild-plugin-live-reload": "^1.1.0",
|
||||
"@goauthentik/eslint-config": "^1.0.5",
|
||||
@@ -114,19 +113,19 @@
|
||||
"@openlayers-elements/maps": "^0.4.0",
|
||||
"@patternfly/elements": "^4.1.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^10.2.0",
|
||||
"@spotlightjs/spotlight": "^3.0.2",
|
||||
"@storybook/addon-docs": "^9.1.1",
|
||||
"@storybook/addon-links": "^9.1.1",
|
||||
"@storybook/web-components": "^9.1.1",
|
||||
"@storybook/web-components-vite": "^9.1.1",
|
||||
"@sentry/browser": "^10.0.0",
|
||||
"@spotlightjs/spotlight": "^3.0.1",
|
||||
"@storybook/addon-docs": "^9.1.0",
|
||||
"@storybook/addon-links": "^9.1.0",
|
||||
"@storybook/web-components": "^9.1.0",
|
||||
"@storybook/web-components-vite": "^9.1.0",
|
||||
"@types/codemirror": "^5.60.16",
|
||||
"@types/grecaptcha": "^3.0.9",
|
||||
"@types/guacamole-common-js": "^1.5.3",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "^24.2.0",
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
||||
"@typescript-eslint/parser": "^8.38.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
@@ -163,7 +162,7 @@
|
||||
"pseudolocale": "^2.1.0",
|
||||
"rapidoc": "^9.3.8",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-mermaid": "^3.0.0",
|
||||
"rehype-parse": "^9.0.1",
|
||||
@@ -178,7 +177,7 @@
|
||||
"ts-pattern": "^5.8.0",
|
||||
"turnstile-types": "^1.2.3",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.39.0",
|
||||
"typescript-eslint": "^8.38.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"webcomponent-qr-code": "^1.3.0",
|
||||
"wireit": "^0.14.12",
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"dependencies": {
|
||||
"@goauthentik/prettier-config": "^3.1.0",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@types/node": "^24.2.0",
|
||||
"@types/node": "^24.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
|
||||
@@ -14,7 +14,6 @@ import { NodeEnvironment } from "@goauthentik/core/environment/node";
|
||||
import { MonoRepoRoot, resolvePackage } from "@goauthentik/core/paths/node";
|
||||
import { readBuildIdentifier } from "@goauthentik/core/version/node";
|
||||
|
||||
import { createAndUploadReport } from "@codecov/bundle-analyzer";
|
||||
import { deepmerge } from "deepmerge-ts";
|
||||
import esbuild from "esbuild";
|
||||
import { copy } from "esbuild-plugin-copy";
|
||||
@@ -206,54 +205,6 @@ async function doBuild() {
|
||||
await esbuild.build(buildOptions);
|
||||
|
||||
console.log("Build complete");
|
||||
|
||||
if (process.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
|
||||
await doBundleSizeReport(buildOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {esbuild.BuildOptions} buildOpts
|
||||
*/
|
||||
async function doBundleSizeReport(buildOpts) {
|
||||
/**
|
||||
* @type {import("@codecov/bundle-analyzer").BundleAnalyzerOptions}
|
||||
*/
|
||||
const bundleAnalyzerOpts = {
|
||||
ignorePatterns: ["*.map"],
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {import("@codecov/bundle-analyzer").Options}
|
||||
*/
|
||||
const coreOpts = {
|
||||
enableBundleAnalysis: true,
|
||||
gitService: "github",
|
||||
oidc: {
|
||||
useGitHubOIDC: true,
|
||||
},
|
||||
};
|
||||
console.group(`${logPrefix} 🚀 Uploading bundle report:`);
|
||||
|
||||
await Promise.all(
|
||||
Object.entries(EntryPoint)
|
||||
.filter(([id]) => id !== "Polyfill")
|
||||
.map(([entrypointID, target]) => {
|
||||
return createAndUploadReport(
|
||||
[path.dirname(target.out)],
|
||||
deepmerge(coreOpts, {
|
||||
bundleName: `@goauthentik/authentik-web-${entrypointID.toLowerCase()}`,
|
||||
}),
|
||||
bundleAnalyzerOpts,
|
||||
)
|
||||
.then((reportAsJson) =>
|
||||
console.log(`Report successfully generated and uploaded: ${reportAsJson}`),
|
||||
)
|
||||
.catch((error) => console.error("Failed to generate or upload report:", error));
|
||||
}),
|
||||
);
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
async function doProxy() {
|
||||
|
||||
@@ -20,7 +20,6 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
import { CapabilitiesEnum, WithCapabilitiesConfig } from "#elements/mixins/capabilities";
|
||||
import { navigate } from "#elements/router/RouterOutlet";
|
||||
|
||||
import { iconHelperText } from "#admin/helperText";
|
||||
import { policyEngineModes } from "#admin/policies/PolicyEngineModes";
|
||||
@@ -34,90 +33,83 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@customElement("ak-application-form")
|
||||
export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Application, string>) {
|
||||
#api = new CoreApi(DEFAULT_CONFIG);
|
||||
constructor() {
|
||||
super();
|
||||
this.handleConfirmBackchannelProviders = this.handleConfirmBackchannelProviders.bind(this);
|
||||
this.makeRemoveBackchannelProviderHandler =
|
||||
this.makeRemoveBackchannelProviderHandler.bind(this);
|
||||
}
|
||||
|
||||
protected override async loadInstance(pk: string): Promise<Application> {
|
||||
const app = await this.#api.coreApplicationsRetrieve({
|
||||
async loadInstance(pk: string): Promise<Application> {
|
||||
const app = await new CoreApi(DEFAULT_CONFIG).coreApplicationsRetrieve({
|
||||
slug: pk,
|
||||
});
|
||||
|
||||
this.clearIcon = false;
|
||||
this.backchannelProviders = app.backchannelProvidersObj || [];
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
public provider?: number;
|
||||
provider?: number;
|
||||
|
||||
@state()
|
||||
protected backchannelProviders: Provider[] = [];
|
||||
backchannelProviders: Provider[] = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
public clearIcon = false;
|
||||
clearIcon = false;
|
||||
|
||||
protected override getSuccessMessage(): string {
|
||||
getSuccessMessage(): string {
|
||||
return this.instance
|
||||
? msg("Successfully updated application.")
|
||||
: msg("Successfully created application.");
|
||||
}
|
||||
|
||||
public override async send(applicationRequest: Application): Promise<Application | void> {
|
||||
applicationRequest.backchannelProviders = this.backchannelProviders.map((p) => p.pk);
|
||||
|
||||
const currentSlug = this.instance?.slug;
|
||||
|
||||
const app = await (currentSlug
|
||||
? this.#api.coreApplicationsUpdate({
|
||||
applicationRequest,
|
||||
slug: currentSlug,
|
||||
})
|
||||
: this.#api.coreApplicationsCreate({ applicationRequest }));
|
||||
|
||||
const nextSlug = app.slug;
|
||||
|
||||
async send(data: Application): Promise<Application | void> {
|
||||
let app: Application;
|
||||
data.backchannelProviders = this.backchannelProviders.map((p) => p.pk);
|
||||
if (this.instance) {
|
||||
app = await new CoreApi(DEFAULT_CONFIG).coreApplicationsUpdate({
|
||||
slug: this.instance.slug,
|
||||
applicationRequest: data,
|
||||
});
|
||||
} else {
|
||||
app = await new CoreApi(DEFAULT_CONFIG).coreApplicationsCreate({
|
||||
applicationRequest: data,
|
||||
});
|
||||
}
|
||||
if (this.can(CapabilitiesEnum.CanSaveMedia)) {
|
||||
const icon = this.files().get("metaIcon");
|
||||
|
||||
if (icon || this.clearIcon) {
|
||||
await this.#api.coreApplicationsSetIconCreate({
|
||||
slug: nextSlug,
|
||||
await new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconCreate({
|
||||
slug: app.slug,
|
||||
file: icon,
|
||||
clear: this.clearIcon,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await this.#api.coreApplicationsSetIconUrlCreate({
|
||||
slug: nextSlug,
|
||||
await new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconUrlCreate({
|
||||
slug: app.slug,
|
||||
filePathRequest: {
|
||||
url: applicationRequest.metaIcon || "",
|
||||
url: data.metaIcon || "",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (currentSlug && currentSlug !== nextSlug) {
|
||||
// TODO: This needs refining.
|
||||
this.instancePk = nextSlug;
|
||||
navigate(`/core/applications/${nextSlug}`);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
#handleConfirmBackchannelProviders = (items: Provider[]) => {
|
||||
handleConfirmBackchannelProviders(items: Provider[]) {
|
||||
this.backchannelProviders = items;
|
||||
this.requestUpdate();
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
||||
|
||||
#makeRemoveBackchannelProviderHandler = (provider: Provider) => {
|
||||
makeRemoveBackchannelProviderHandler(provider: Provider) {
|
||||
return () => {
|
||||
const idx = this.backchannelProviders.indexOf(provider);
|
||||
this.backchannelProviders.splice(idx, 1);
|
||||
this.requestUpdate();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
handleClearIcon(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
@@ -127,25 +119,22 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
this.clearIcon = !!(ev.target as HTMLInputElement).checked;
|
||||
}
|
||||
|
||||
public override renderForm(): TemplateResult {
|
||||
renderForm(): TemplateResult {
|
||||
const alertMsg = msg(
|
||||
"Using this form will only create an Application. In order to authenticate with the application, you will have to manually pair it with a Provider.",
|
||||
);
|
||||
|
||||
return html`
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.instance ? nothing : html`<ak-alert level="pf-m-info">${alertMsg}</ak-alert>`}
|
||||
<ak-text-input
|
||||
name="name"
|
||||
autocomplete="off"
|
||||
placeholder=${msg("Application name")}
|
||||
value=${ifDefined(this.instance?.name)}
|
||||
label=${msg("Name")}
|
||||
required
|
||||
help=${msg("The name displayed in the application library.")}
|
||||
help=${msg("Application's display Name.")}
|
||||
></ak-text-input>
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
autocomplete="off"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
@@ -156,7 +145,6 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
name="group"
|
||||
value=${ifDefined(this.instance?.group)}
|
||||
label=${msg("Group")}
|
||||
placeholder=${msg("e.g. Collaboration, Communication, Internal, etc.")}
|
||||
help=${msg(
|
||||
"Optionally enter a group name. Applications with identical groups are shown grouped together.",
|
||||
)}
|
||||
@@ -176,8 +164,8 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
"Select backchannel providers which augment the functionality of the main provider.",
|
||||
)}
|
||||
.providers=${this.backchannelProviders}
|
||||
.confirm=${this.#handleConfirmBackchannelProviders}
|
||||
.remover=${this.#makeRemoveBackchannelProviderHandler}
|
||||
.confirm=${this.handleConfirmBackchannelProviders}
|
||||
.remover=${this.makeRemoveBackchannelProviderHandler}
|
||||
.tooltip=${html`<pf-tooltip
|
||||
position="top"
|
||||
content=${msg("Add provider")}
|
||||
@@ -196,7 +184,6 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
<ak-text-input
|
||||
name="metaLaunchUrl"
|
||||
label=${msg("Launch URL")}
|
||||
placeholder="https://..."
|
||||
value=${ifDefined(this.instance?.metaLaunchUrl)}
|
||||
help=${msg(
|
||||
"If left empty, authentik will try to extract the launch URL based on the selected provider.",
|
||||
@@ -248,7 +235,7 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
></ak-textarea-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
`;
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import "#elements/buttons/SpinnerButton/ak-spinner-button";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { PFSize } from "#common/enums";
|
||||
import { APIError, parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
|
||||
@@ -24,7 +23,7 @@ import {
|
||||
RbacPermissionsAssignedByUsersListModelEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, html, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
@@ -41,6 +40,15 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
@customElement("ak-application-view")
|
||||
export class ApplicationViewPage extends AKElement {
|
||||
@property({ type: String })
|
||||
applicationSlug?: string;
|
||||
|
||||
@state()
|
||||
application?: Application;
|
||||
|
||||
@state()
|
||||
missingOutpost = false;
|
||||
|
||||
static styles: CSSResult[] = [
|
||||
PFBase,
|
||||
PFList,
|
||||
@@ -53,28 +61,7 @@ export class ApplicationViewPage extends AKElement {
|
||||
PFCard,
|
||||
];
|
||||
|
||||
//#region Properties
|
||||
|
||||
@property({ type: String })
|
||||
public applicationSlug?: string;
|
||||
//#endregion
|
||||
|
||||
//#region State
|
||||
|
||||
@state()
|
||||
protected application?: Application;
|
||||
|
||||
@state()
|
||||
protected error?: APIError;
|
||||
|
||||
@state()
|
||||
protected missingOutpost = false;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
protected fetchIsMissingOutpost(providersByPk: Array<number>) {
|
||||
fetchIsMissingOutpost(providersByPk: Array<number>) {
|
||||
new OutpostsApi(DEFAULT_CONFIG)
|
||||
.outpostsInstancesList({
|
||||
providersByPk,
|
||||
@@ -87,34 +74,27 @@ export class ApplicationViewPage extends AKElement {
|
||||
});
|
||||
}
|
||||
|
||||
protected fetchApplication(slug: string) {
|
||||
new CoreApi(DEFAULT_CONFIG)
|
||||
.coreApplicationsRetrieve({ slug })
|
||||
.then((app) => {
|
||||
this.application = app;
|
||||
if (
|
||||
app.providerObj &&
|
||||
[
|
||||
RbacPermissionsAssignedByUsersListModelEnum.AuthentikProvidersProxyProxyprovider.toString(),
|
||||
RbacPermissionsAssignedByUsersListModelEnum.AuthentikProvidersLdapLdapprovider.toString(),
|
||||
].includes(app.providerObj.metaModelName)
|
||||
) {
|
||||
this.fetchIsMissingOutpost([app.provider || 0]);
|
||||
}
|
||||
})
|
||||
.catch(async (error) => {
|
||||
this.error = await parseAPIResponseError(error);
|
||||
});
|
||||
fetchApplication(slug: string) {
|
||||
new CoreApi(DEFAULT_CONFIG).coreApplicationsRetrieve({ slug }).then((app) => {
|
||||
this.application = app;
|
||||
if (
|
||||
app.providerObj &&
|
||||
[
|
||||
RbacPermissionsAssignedByUsersListModelEnum.AuthentikProvidersProxyProxyprovider.toString(),
|
||||
RbacPermissionsAssignedByUsersListModelEnum.AuthentikProvidersLdapLdapprovider.toString(),
|
||||
].includes(app.providerObj.metaModelName)
|
||||
) {
|
||||
this.fetchIsMissingOutpost([app.provider || 0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override willUpdate(changedProperties: PropertyValues<this>) {
|
||||
willUpdate(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("applicationSlug") && this.applicationSlug) {
|
||||
this.fetchApplication(this.applicationSlug);
|
||||
}
|
||||
}
|
||||
|
||||
//#region Render
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<ak-page-header
|
||||
header=${this.application?.name || msg("Loading")}
|
||||
@@ -131,17 +111,9 @@ export class ApplicationViewPage extends AKElement {
|
||||
}
|
||||
|
||||
renderApp(): TemplateResult {
|
||||
if (this.error) {
|
||||
return html`<ak-empty-state icon="fa-ban"
|
||||
><span>${msg(str`Failed to fetch application "${this.applicationSlug}".`)}</span>
|
||||
<div slot="body">${pluckErrorDetail(this.error)}</div>
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
|
||||
if (!this.application) {
|
||||
return html`<ak-empty-state default-label></ak-empty-state>`;
|
||||
}
|
||||
|
||||
return html`<ak-tabs>
|
||||
${this.missingOutpost
|
||||
? html`<div slot="header" class="pf-c-banner pf-m-warning">
|
||||
@@ -216,7 +188,7 @@ export class ApplicationViewPage extends AKElement {
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text pf-m-monospace">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.application.policyEngineMode?.toUpperCase()}
|
||||
</div>
|
||||
</dd>
|
||||
|
||||
@@ -15,12 +15,12 @@ import { WizardStep } from "#components/ak-wizard/WizardStep";
|
||||
|
||||
import { styles } from "#admin/applications/wizard/ApplicationWizardFormStepStyles.styles";
|
||||
|
||||
import { ApplicationRequest, ValidationError } from "@goauthentik/api";
|
||||
import { ValidationError } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
export class ApplicationWizardStep<T = Partial<ApplicationRequest>> extends WizardStep {
|
||||
export class ApplicationWizardStep<T = Record<string, unknown>> extends WizardStep {
|
||||
static styles = [...WizardStep.styles, ...styles];
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
@@ -28,15 +28,15 @@ export class ApplicationWizardStep<T = Partial<ApplicationRequest>> extends Wiza
|
||||
|
||||
// As recommended in [WizardStep](../../../components/ak-wizard/WizardStep.ts), we override
|
||||
// these fields and provide them to all the child classes.
|
||||
protected wizardTitle = msg("New application");
|
||||
protected wizardDescription = msg("Create a new application and configure a provider for it.");
|
||||
public canCancel = true;
|
||||
wizardTitle = msg("New application");
|
||||
wizardDescription = msg("Create a new application and configure a provider for it.");
|
||||
canCancel = true;
|
||||
|
||||
// This should be overridden in the children for more precise targeting.
|
||||
@query("form")
|
||||
protected form!: HTMLFormElement;
|
||||
form!: HTMLFormElement;
|
||||
|
||||
protected get formValues(): T {
|
||||
get formValues(): T {
|
||||
return serializeForm<T>([
|
||||
...this.form.querySelectorAll("ak-form-element-horizontal"),
|
||||
...this.form.querySelectorAll("[data-ak-control]"),
|
||||
|
||||
@@ -50,16 +50,7 @@ function renderRadiusOverview(rawProvider: OneOfProvider) {
|
||||
}
|
||||
|
||||
function renderRACOverview(rawProvider: OneOfProvider) {
|
||||
const provider = rawProvider as RACProvider;
|
||||
return renderSummary("RAC", provider.name, [
|
||||
[msg("Connection expiry"), provider.connectionExpiry ?? "-"],
|
||||
[
|
||||
msg("Property mappings"),
|
||||
Array.isArray(provider.propertyMappings) && provider.propertyMappings.length
|
||||
? provider.propertyMappings.join(", ")
|
||||
: msg("None"),
|
||||
],
|
||||
]);
|
||||
const _provider = rawProvider as RACProvider;
|
||||
}
|
||||
|
||||
function formatRedirectUris(uris: RedirectURI[] = []) {
|
||||
|
||||
@@ -8,6 +8,8 @@ import "#elements/forms/HorizontalFormElement";
|
||||
|
||||
import { ApplicationWizardStateUpdate, ValidationRecord } from "../types.js";
|
||||
|
||||
import { camelToSnake } from "#common/utils";
|
||||
|
||||
import { isSlug } from "#elements/router/utils";
|
||||
|
||||
import { type NavigableButton, type WizardButton } from "#components/ak-wizard/types";
|
||||
@@ -17,31 +19,25 @@ import { policyEngineModes } from "#admin/policies/PolicyEngineModes";
|
||||
|
||||
import { type ApplicationRequest } from "@goauthentik/api";
|
||||
|
||||
import { snakeCase } from "change-case";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
function trimMany<T extends object, K extends keyof T>(target: T, keys: K[]): Pick<T, K> {
|
||||
const output = {} as Record<K, unknown>;
|
||||
const autoTrim = (v: unknown) => (typeof v === "string" ? v.trim() : v);
|
||||
|
||||
for (const key of keys) {
|
||||
const value = target[key];
|
||||
const trimMany = (o: Record<string, unknown>, vs: string[]) =>
|
||||
Object.fromEntries(vs.map((v) => [v, autoTrim(o[v])]));
|
||||
|
||||
output[key] = typeof value === "string" ? value.trim() : value;
|
||||
}
|
||||
|
||||
return output as Pick<T, K>;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const isStr = (v: any): v is string => typeof v === "string";
|
||||
|
||||
@customElement("ak-application-wizard-application-step")
|
||||
export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
label = msg("Application");
|
||||
|
||||
@state()
|
||||
errors = new Map<keyof ApplicationRequest, string>();
|
||||
errors = new Map<string, string>();
|
||||
|
||||
@query("form#applicationform")
|
||||
form!: HTMLFormElement;
|
||||
@@ -52,10 +48,12 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
protected errorMessages(name: keyof ApplicationRequest) {
|
||||
errorMessages(name: string) {
|
||||
return this.errors.has(name)
|
||||
? [this.errors.get(name)]
|
||||
: (this.wizard.errors?.app?.[name] ?? this.wizard.errors?.app?.[snakeCase(name)] ?? []);
|
||||
: (this.wizard.errors?.app?.[name] ??
|
||||
this.wizard.errors?.app?.[camelToSnake(name)] ??
|
||||
[]);
|
||||
}
|
||||
|
||||
get buttons(): WizardButton[] {
|
||||
@@ -64,53 +62,51 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
|
||||
get valid() {
|
||||
this.errors = new Map();
|
||||
const values = trimMany(this.formValues ?? {}, ["metaLaunchUrl", "name", "slug"]);
|
||||
|
||||
const values = trimMany(this.formValues, ["metaLaunchUrl", "name", "slug"]);
|
||||
|
||||
if (!values.name) {
|
||||
if (values.name === "") {
|
||||
this.errors.set("name", msg("An application name is required"));
|
||||
}
|
||||
|
||||
if (!values.metaLaunchUrl || !URL.canParse(values.metaLaunchUrl)) {
|
||||
if (
|
||||
!(
|
||||
isStr(values.metaLaunchUrl) &&
|
||||
(values.metaLaunchUrl === "" || URL.canParse(values.metaLaunchUrl))
|
||||
)
|
||||
) {
|
||||
this.errors.set("metaLaunchUrl", msg("Not a valid URL"));
|
||||
}
|
||||
|
||||
if (!values.slug || !isSlug(values.slug)) {
|
||||
if (!(isStr(values.slug) && values.slug !== "" && isSlug(values.slug))) {
|
||||
this.errors.set("slug", msg("Not a valid slug"));
|
||||
}
|
||||
|
||||
return this.errors.size === 0;
|
||||
}
|
||||
|
||||
override handleButton(button: NavigableButton) {
|
||||
if (button.kind !== "next") {
|
||||
return super.handleButton(button);
|
||||
}
|
||||
if (button.kind === "next") {
|
||||
if (!this.valid) {
|
||||
this.handleEnabling({
|
||||
disabled: ["provider-choice", "provider", "bindings", "submit"],
|
||||
});
|
||||
return;
|
||||
}
|
||||
const app: Partial<ApplicationRequest> = this.formValues as Partial<ApplicationRequest>;
|
||||
|
||||
if (!this.valid) {
|
||||
this.handleEnabling({
|
||||
disabled: ["provider-choice", "provider", "bindings", "submit"],
|
||||
let payload: ApplicationWizardStateUpdate = {
|
||||
app: this.formValues,
|
||||
errors: this.removeErrors("app"),
|
||||
};
|
||||
if (app.name && (this.wizard.provider?.name ?? "").trim() === "") {
|
||||
payload = {
|
||||
...payload,
|
||||
provider: { name: `Provider for ${app.name}` },
|
||||
};
|
||||
}
|
||||
this.handleUpdate(payload, button.destination, {
|
||||
enable: "provider-choice",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const app = { ...this.formValues };
|
||||
|
||||
const payload: ApplicationWizardStateUpdate = {
|
||||
app,
|
||||
errors: this.removeErrors("app"),
|
||||
};
|
||||
|
||||
if (!this.wizard.provider?.name?.trim() && app.name) {
|
||||
payload.provider = {
|
||||
name: `Provider for ${app.name}`,
|
||||
};
|
||||
}
|
||||
|
||||
this.handleUpdate(payload, button.destination, {
|
||||
enable: "provider-choice",
|
||||
});
|
||||
super.handleButton(button);
|
||||
}
|
||||
|
||||
renderForm(app: Partial<ApplicationRequest>, errors: ValidationRecord) {
|
||||
@@ -118,14 +114,12 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
<form id="applicationform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
<ak-text-input
|
||||
name="name"
|
||||
autocomplete="off"
|
||||
placeholder=${msg("Application name")}
|
||||
value=${ifDefined(app.name)}
|
||||
label=${msg("Name")}
|
||||
required
|
||||
?invalid=${this.errors.has("name")}
|
||||
.errorMessages=${errors.name ?? this.errorMessages("name")}
|
||||
help=${msg("The name displayed in the application library.")}
|
||||
help=${msg("Application's display Name.")}
|
||||
></ak-text-input>
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
@@ -141,8 +135,7 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
name="group"
|
||||
value=${ifDefined(app.group)}
|
||||
label=${msg("Group")}
|
||||
placeholder=${msg("e.g. Collaboration, Communication, Internal, etc.")}
|
||||
.errorMessages=${errors.group}
|
||||
.errorMessages=${errors.group ?? []}
|
||||
help=${msg(
|
||||
"Optionally enter a group name. Applications with identical groups are shown grouped together.",
|
||||
)}
|
||||
@@ -154,14 +147,13 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
name="policyEngineMode"
|
||||
.options=${policyEngineModes}
|
||||
.value=${app.policyEngineMode}
|
||||
.errorMessages=${errors.policyEngineMode}
|
||||
.errorMessages=${errors.policyEngineMode ?? []}
|
||||
></ak-radio-input>
|
||||
<ak-form-group label=${msg("UI Settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="metaLaunchUrl"
|
||||
label=${msg("Launch URL")}
|
||||
placeholder="https://..."
|
||||
value=${ifDefined(app.metaLaunchUrl)}
|
||||
?invalid=${this.errors.has("metaLaunchUrl")}
|
||||
.errorMessages=${errors.metaLaunchUrl ??
|
||||
|
||||
@@ -297,15 +297,11 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
||||
|
||||
renderReview(app: Partial<ApplicationRequest>, provider: OneOfProvider) {
|
||||
const renderer = providerRenderers.get(this.wizard.providerModel);
|
||||
|
||||
if (!renderer) {
|
||||
throw new Error(
|
||||
`Provider ${this.wizard.providerModel ?? "-- undefined --"} has no summary renderer.`,
|
||||
);
|
||||
}
|
||||
|
||||
const metaLaunchUrl = app.metaLaunchUrl?.trim();
|
||||
|
||||
return html`
|
||||
<div class="ak-wizard-main-content">
|
||||
<ak-wizard-title>${msg("Review the Application and Provider")}</ak-wizard-title>
|
||||
@@ -325,10 +321,12 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
||||
${app.policyEngineMode?.toUpperCase()}
|
||||
</dt>
|
||||
</div>
|
||||
${metaLaunchUrl
|
||||
${(app.metaLaunchUrl ?? "").trim() !== ""
|
||||
? html` <div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">${msg("Launch URL")}</dt>
|
||||
<dt class="pf-c-description-list__description">${metaLaunchUrl}</dt>
|
||||
<dt class="pf-c-description-list__description">
|
||||
${app.metaLaunchUrl}
|
||||
</dt>
|
||||
</div>`
|
||||
: nothing}
|
||||
</dl>
|
||||
|
||||
@@ -8,11 +8,11 @@ import "#elements/forms/HorizontalFormElement";
|
||||
import { styles as AwadStyles } from "../../ApplicationWizardFormStepStyles.styles.js";
|
||||
import { type ApplicationWizardState, type OneOfProvider } from "../../types.js";
|
||||
|
||||
import { camelToSnake } from "#common/utils";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { serializeForm } from "#elements/forms/Form";
|
||||
|
||||
import { snakeCase } from "change-case";
|
||||
|
||||
import { CSSResult } from "lit";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
@@ -46,7 +46,7 @@ export class ApplicationWizardProviderForm<T extends OneOfProvider> extends AKEl
|
||||
return name in this.errors
|
||||
? [this.errors[name]]
|
||||
: (this.wizard.errors?.provider?.[name] ??
|
||||
this.wizard.errors?.provider?.[snakeCase(name)] ??
|
||||
this.wizard.errors?.provider?.[camelToSnake(name)] ??
|
||||
[]);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,12 @@ import { groupBy } from "#common/utils";
|
||||
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
|
||||
import { policyEngineModes } from "#admin/policies/PolicyEngineModes";
|
||||
|
||||
import {
|
||||
FlowsApi,
|
||||
FlowsInstancesListDesignationEnum,
|
||||
FlowStageBinding,
|
||||
InvalidResponseActionEnum,
|
||||
PolicyEngineMode,
|
||||
Stage,
|
||||
StagesAllListRequest,
|
||||
StagesApi,
|
||||
@@ -203,7 +202,22 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
|
||||
required
|
||||
name="policyEngineMode"
|
||||
>
|
||||
<ak-radio .options=${policyEngineModes} .value=${this.instance?.policyEngineMode}>
|
||||
<ak-radio
|
||||
.options=${[
|
||||
{
|
||||
label: "any",
|
||||
value: PolicyEngineMode.Any,
|
||||
default: true,
|
||||
description: html`${msg("Any policy must match to grant access")}`,
|
||||
},
|
||||
{
|
||||
label: "all",
|
||||
value: PolicyEngineMode.All,
|
||||
description: html`${msg("All policies must match to grant access")}`,
|
||||
},
|
||||
]}
|
||||
.value=${this.instance?.policyEngineMode}
|
||||
>
|
||||
</ak-radio>
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import type { RadioOption } from "#elements/forms/Radio";
|
||||
|
||||
import { PolicyEngineMode } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
|
||||
export const policyEngineModes: RadioOption<PolicyEngineMode>[] = [
|
||||
export const policyEngineModes = [
|
||||
{
|
||||
label: "ANY",
|
||||
className: "pf-m-monospace",
|
||||
label: "any",
|
||||
value: PolicyEngineMode.Any,
|
||||
default: true,
|
||||
description: html`${msg("Any policy must match to grant access")}`,
|
||||
},
|
||||
{
|
||||
label: "ALL",
|
||||
className: "pf-m-monospace",
|
||||
label: "all",
|
||||
value: PolicyEngineMode.All,
|
||||
description: html`${msg("All policies must match to grant access")}`,
|
||||
},
|
||||
|
||||
@@ -51,7 +51,7 @@ export function renderForm(
|
||||
placeholder=${msg("Provider name")}
|
||||
value=${ifDefined(provider?.name)}
|
||||
label=${msg("Name")}
|
||||
.errorMessages=${errors?.name}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
required
|
||||
help=${msg("Method's display Name.")}
|
||||
></ak-text-input>
|
||||
@@ -87,7 +87,7 @@ export function renderForm(
|
||||
label=${msg("Bind flow")}
|
||||
required
|
||||
name="authorizationFlow"
|
||||
.errorMessages=${errors?.authorizationFlow}
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
>
|
||||
<ak-branded-flow-search
|
||||
label=${msg("Bind flow")}
|
||||
@@ -111,7 +111,7 @@ export function renderForm(
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
.brandFlow=${brand?.flowInvalidation}
|
||||
defaultFlowSlug="default-invalidation-flow"
|
||||
.errorMessages=${errors?.invalidationFlow}
|
||||
.errorMessages=${errors?.invalidationFlow ?? []}
|
||||
required
|
||||
></ak-branded-flow-search>
|
||||
<p class="pf-c-form__helper-text">${msg("Flow used for unbinding users.")}</p>
|
||||
@@ -127,7 +127,7 @@ export function renderForm(
|
||||
required
|
||||
value="${provider?.baseDn ?? "DC=ldap,DC=goauthentik,DC=io"}"
|
||||
input-hint="code"
|
||||
.errorMessages=${errors?.baseDn}
|
||||
.errorMessages=${errors?.baseDn ?? []}
|
||||
help=${msg(
|
||||
"LDAP DN under which bind requests and search requests can be made.",
|
||||
)}
|
||||
@@ -137,7 +137,7 @@ export function renderForm(
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Certificate")}
|
||||
name="certificate"
|
||||
.errorMessages=${errors?.certificate}
|
||||
.errorMessages=${errors?.certificate ?? []}
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
certificate=${ifDefined(provider?.certificate ?? nothing)}
|
||||
@@ -151,7 +151,7 @@ export function renderForm(
|
||||
label=${msg("TLS Server name")}
|
||||
name="tlsServerName"
|
||||
value="${provider?.tlsServerName ?? ""}"
|
||||
.errorMessages=${errors?.tlsServerName}
|
||||
.errorMessages=${errors?.tlsServerName ?? []}
|
||||
help=${tlsServerNameHelp}
|
||||
input-hint="code"
|
||||
></ak-text-input>
|
||||
@@ -161,7 +161,7 @@ export function renderForm(
|
||||
required
|
||||
name="uidStartNumber"
|
||||
value="${provider?.uidStartNumber ?? 2000}"
|
||||
.errorMessages=${errors?.uidStartNumber}
|
||||
.errorMessages=${errors?.uidStartNumber ?? []}
|
||||
help=${uidStartNumberHelp}
|
||||
></ak-number-input>
|
||||
|
||||
@@ -170,7 +170,7 @@ export function renderForm(
|
||||
required
|
||||
name="gidStartNumber"
|
||||
value="${provider?.gidStartNumber ?? 4000}"
|
||||
.errorMessages=${errors?.gidStartNumber}
|
||||
.errorMessages=${errors?.gidStartNumber ?? []}
|
||||
help=${gidStartNumberHelp}
|
||||
></ak-number-input>
|
||||
</div>
|
||||
|
||||
@@ -20,8 +20,6 @@ import { oauth2SourcesProvider, oauth2SourcesSelector } from "./OAuth2Sources.js
|
||||
|
||||
import { ascii_letters, digits, randomString } from "#common/utils";
|
||||
|
||||
import { RadioOption } from "#elements/forms/Radio";
|
||||
|
||||
import {
|
||||
ClientTypeEnum,
|
||||
FlowsInstancesListDesignationEnum,
|
||||
@@ -37,7 +35,7 @@ import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
export const clientTypeOptions: RadioOption<ClientTypeEnum>[] = [
|
||||
export const clientTypeOptions = [
|
||||
{
|
||||
label: msg("Confidential"),
|
||||
value: ClientTypeEnum.Confidential,
|
||||
@@ -55,7 +53,7 @@ export const clientTypeOptions: RadioOption<ClientTypeEnum>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const subjectModeOptions: RadioOption<SubModeEnum>[] = [
|
||||
export const subjectModeOptions = [
|
||||
{
|
||||
label: msg("Based on the User's hashed ID"),
|
||||
value: SubModeEnum.HashedUserId,
|
||||
@@ -87,7 +85,7 @@ export const subjectModeOptions: RadioOption<SubModeEnum>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const issuerModeOptions: RadioOption<IssuerModeEnum>[] = [
|
||||
export const issuerModeOptions = [
|
||||
{
|
||||
label: msg("Each provider has a different issuer, based on the application slug"),
|
||||
value: IssuerModeEnum.PerProvider,
|
||||
@@ -99,7 +97,7 @@ export const issuerModeOptions: RadioOption<IssuerModeEnum>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const redirectUriHelpMessages: string[] = [
|
||||
const redirectUriHelpMessages = [
|
||||
msg(
|
||||
"Valid redirect URIs after a successful authorization flow. Also specify any origins here for Implicit flows.",
|
||||
),
|
||||
@@ -111,14 +109,9 @@ const redirectUriHelpMessages: string[] = [
|
||||
),
|
||||
];
|
||||
|
||||
const backchannelLogoutUriHelpMessages: string[] = [
|
||||
msg(
|
||||
"URIs to send back-channel logout notifications to when users log out. Required for OpenID Connect Back-Channel Logout functionality.",
|
||||
),
|
||||
msg(
|
||||
"These URIs are called server-to-server when a user logs out to notify OAuth2/OpenID clients about the logout event.",
|
||||
),
|
||||
];
|
||||
export const redirectUriHelp = html`${redirectUriHelpMessages.map(
|
||||
(m) => html`<p class="pf-c-form__helper-text">${m}</p>`,
|
||||
)}`;
|
||||
|
||||
type ShowClientSecret = (show: boolean) => void;
|
||||
const defaultShowClientSecret: ShowClientSecret = (_show) => undefined;
|
||||
@@ -133,7 +126,6 @@ export function renderForm(
|
||||
name="name"
|
||||
label=${msg("Name")}
|
||||
value=${ifDefined(provider?.name)}
|
||||
.errorMessages=${errors?.name}
|
||||
required
|
||||
></ak-text-input>
|
||||
|
||||
@@ -145,7 +137,6 @@ export function renderForm(
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
.errorMessages=${errors?.authorizationFlow}
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
@@ -171,7 +162,6 @@ export function renderForm(
|
||||
value="${provider?.clientId ?? randomString(40, ascii_letters + digits)}"
|
||||
required
|
||||
input-hint="code"
|
||||
.errorMessages=${errors?.clientId}
|
||||
>
|
||||
</ak-text-input>
|
||||
<ak-hidden-text-input
|
||||
@@ -184,6 +174,7 @@ export function renderForm(
|
||||
>
|
||||
</ak-hidden-text-input>
|
||||
<ak-form-element-horizontal
|
||||
flow-direction="row"
|
||||
label=${msg("Redirect URIs/Origins (RegEx)")}
|
||||
name="redirectUris"
|
||||
>
|
||||
@@ -200,22 +191,9 @@ export function renderForm(
|
||||
}}
|
||||
>
|
||||
</ak-array-input>
|
||||
${redirectUriHelpMessages.map(
|
||||
(m) => html`<p class="pf-c-form__helper-text">${m}</p>`,
|
||||
)}
|
||||
${redirectUriHelp}
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-text-input
|
||||
label=${msg("Back-Channel Logout URI")}
|
||||
name="backchannelLogoutUri"
|
||||
value="${provider?.backchannelLogoutUri ?? ""}"
|
||||
input-hint="code"
|
||||
placeholder="https://..."
|
||||
.help=${backchannelLogoutUriHelpMessages.map(
|
||||
(m) => html`<p class="pf-c-form__helper-text">${m}</p>`,
|
||||
)}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
|
||||
<!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
|
||||
<ak-crypto-certificate-search
|
||||
|
||||
@@ -4,7 +4,6 @@ import "#components/events/ObjectChangelog";
|
||||
import "#elements/CodeMirror";
|
||||
import "#elements/EmptyState";
|
||||
import "#elements/Tabs";
|
||||
import "#elements/tasks/TaskList";
|
||||
import "#elements/ak-mdx/index";
|
||||
import "#elements/buttons/ModalButton";
|
||||
import "#elements/buttons/SpinnerButton/index";
|
||||
@@ -20,7 +19,6 @@ import {
|
||||
ClientTypeEnum,
|
||||
CoreApi,
|
||||
CoreUsersListRequest,
|
||||
ModelEnum,
|
||||
OAuth2Provider,
|
||||
OAuth2ProviderSetupURLs,
|
||||
PropertyMappingPreview,
|
||||
@@ -172,7 +170,6 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
if (!this.provider) {
|
||||
return html``;
|
||||
}
|
||||
const [appLabel, modelName] = ModelEnum.AuthentikProvidersOauth2Oauth2provider.split(".");
|
||||
return html` ${this.provider?.assignedApplicationName
|
||||
? html``
|
||||
: html`<div slot="header" class="pf-c-banner pf-m-warning">
|
||||
@@ -249,18 +246,6 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${msg("Back-Channel Logout URI")}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text pf-m-monospace">
|
||||
${this.provider.backchannelLogoutUri}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
@@ -370,18 +355,6 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-card pf-l-grid__item pf-m-12-col pf-m-12-col-on-xl pf-m-12-col-on-2xl"
|
||||
>
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col-on-2xl">
|
||||
<div class="pf-c-card__title">${msg("Tasks")}</div>
|
||||
<ak-task-list
|
||||
.relObjAppLabel=${appLabel}
|
||||
.relObjModel=${modelName}
|
||||
.relObjId="${this.provider.pk}"
|
||||
></ak-task-list>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-card pf-l-grid__item pf-m-12-col pf-m-12-col-on-xl pf-m-12-col-on-2xl"
|
||||
>
|
||||
|
||||
@@ -88,7 +88,7 @@ function renderProxySettings(provider: Partial<ProxyProvider>, errors?: Validati
|
||||
label=${msg("External host")}
|
||||
value="${ifDefined(provider?.externalHost)}"
|
||||
required
|
||||
.errorMessages=${errors?.externalHost}
|
||||
.errorMessages=${errors?.externalHost ?? []}
|
||||
help=${msg(
|
||||
"The external URL you'll access the application at. Include any non-standard port.",
|
||||
)}
|
||||
@@ -99,7 +99,7 @@ function renderProxySettings(provider: Partial<ProxyProvider>, errors?: Validati
|
||||
label=${msg("Internal host")}
|
||||
value="${ifDefined(provider?.internalHost)}"
|
||||
required
|
||||
.errorMessages=${errors?.internalHost}
|
||||
.errorMessages=${errors?.internalHost ?? []}
|
||||
help=${msg("Upstream host that the requests are forwarded to.")}
|
||||
input-hint="code"
|
||||
></ak-text-input>
|
||||
@@ -124,7 +124,7 @@ function renderForwardSingleSettings(provider: Partial<ProxyProvider>, errors?:
|
||||
label=${msg("External host")}
|
||||
value="${ifDefined(provider?.externalHost)}"
|
||||
required
|
||||
.errorMessages=${errors?.externalHost}
|
||||
.errorMessages=${errors?.externalHost ?? []}
|
||||
help=${msg(
|
||||
"The external URL you'll access the application at. Include any non-standard port.",
|
||||
)}
|
||||
@@ -154,7 +154,7 @@ function renderForwardDomainSettings(provider: Partial<ProxyProvider>, errors?:
|
||||
label=${msg("Authentication URL")}
|
||||
value="${provider?.externalHost ?? window.location.origin}"
|
||||
required
|
||||
.errorMessages=${errors?.externalHost}
|
||||
.errorMessages=${errors?.externalHost ?? []}
|
||||
help=${msg(
|
||||
"The external URL you'll authenticate at. The authentik core server should be reachable under this URL.",
|
||||
)}
|
||||
@@ -165,7 +165,7 @@ function renderForwardDomainSettings(provider: Partial<ProxyProvider>, errors?:
|
||||
name="cookieDomain"
|
||||
value="${ifDefined(provider?.cookieDomain)}"
|
||||
required
|
||||
.errorMessages=${errors?.cookieDomain}
|
||||
.errorMessages=${errors?.cookieDomain ?? []}
|
||||
help=${msg(
|
||||
"Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.",
|
||||
)}
|
||||
@@ -196,7 +196,7 @@ export function renderForm(
|
||||
name="name"
|
||||
value=${ifDefined(provider?.name)}
|
||||
label=${msg("Name")}
|
||||
.errorMessages=${errors?.name}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
required
|
||||
></ak-text-input>
|
||||
|
||||
@@ -224,7 +224,7 @@ export function renderForm(
|
||||
label=${msg("Token validity")}
|
||||
name="accessTokenValidity"
|
||||
value="${provider?.accessTokenValidity ?? "hours=24"}"
|
||||
.errorMessages=${errors?.accessTokenValidity}
|
||||
.errorMessages=${errors?.accessTokenValidity ?? []}
|
||||
required
|
||||
.help=${msg("Configure how long tokens are valid for.")}
|
||||
input-hint="code"
|
||||
|
||||
@@ -46,7 +46,7 @@ export function renderForm(
|
||||
name="name"
|
||||
label=${msg("Name")}
|
||||
value=${ifDefined(provider?.name)}
|
||||
.errorMessages=${errors?.name}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
required
|
||||
>
|
||||
</ak-text-input>
|
||||
@@ -55,7 +55,7 @@ export function renderForm(
|
||||
label=${msg("Authentication flow")}
|
||||
required
|
||||
name="authorizationFlow"
|
||||
.errorMessages=${errors?.authorizationFlow}
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
>
|
||||
<ak-branded-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
@@ -79,7 +79,7 @@ export function renderForm(
|
||||
<ak-hidden-text-input
|
||||
name="sharedSecret"
|
||||
label=${msg("Shared secret")}
|
||||
.errorMessages=${errors?.sharedSecret}
|
||||
.errorMessages=${errors?.sharedSecret ?? []}
|
||||
value=${provider?.sharedSecret ?? randomString(128, ascii_letters + digits)}
|
||||
required
|
||||
input-hint="code"
|
||||
@@ -88,7 +88,7 @@ export function renderForm(
|
||||
name="clientNetworks"
|
||||
label=${msg("Client Networks")}
|
||||
value=${provider?.clientNetworks ?? "0.0.0.0/0, ::/0"}
|
||||
.errorMessages=${errors?.clientNetworks}
|
||||
.errorMessages=${errors?.clientNetworks ?? []}
|
||||
required
|
||||
help=${clientNetworksHelp}
|
||||
input-hint="code"
|
||||
@@ -118,7 +118,7 @@ export function renderForm(
|
||||
placeholder=${msg("Select an invalidation flow...")}
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
.errorMessages=${errors?.invalidationFlow}
|
||||
.errorMessages=${errors?.invalidationFlow ?? []}
|
||||
defaultFlowSlug="default-invalidation-flow"
|
||||
required
|
||||
></ak-flow-search>
|
||||
|
||||
@@ -12,8 +12,6 @@ import { digestAlgorithmOptions, signatureAlgorithmOptions } from "./SAMLProvide
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { RadioOption } from "#elements/forms/Radio";
|
||||
|
||||
import {
|
||||
FlowsInstancesListDesignationEnum,
|
||||
PropertymappingsApi,
|
||||
@@ -29,7 +27,7 @@ import { msg } from "@lit/localize";
|
||||
import { html, nothing } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
const serviceProviderBindingOptions: RadioOption<SpBindingEnum>[] = [
|
||||
const serviceProviderBindingOptions = [
|
||||
{
|
||||
label: msg("Redirect"),
|
||||
value: SpBindingEnum.Redirect,
|
||||
@@ -41,11 +39,11 @@ const serviceProviderBindingOptions: RadioOption<SpBindingEnum>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
function renderHasSigningKp(provider: Partial<SAMLProvider>) {
|
||||
function renderHasSigningKp(provider?: Partial<SAMLProvider>) {
|
||||
return html` <ak-switch-input
|
||||
name="signAssertion"
|
||||
label=${msg("Sign assertions")}
|
||||
?checked=${provider.signAssertion ?? true}
|
||||
?checked=${provider?.signAssertion ?? true}
|
||||
help=${msg("When enabled, the assertion element of the SAML response will be signed.")}
|
||||
>
|
||||
</ak-switch-input>
|
||||
@@ -53,7 +51,7 @@ function renderHasSigningKp(provider: Partial<SAMLProvider>) {
|
||||
<ak-switch-input
|
||||
name="signResponse"
|
||||
label=${msg("Sign responses")}
|
||||
?checked=${provider.signResponse ?? false}
|
||||
?checked=${provider?.signResponse ?? false}
|
||||
help=${msg("When enabled, the SAML response will be signed.")}
|
||||
>
|
||||
</ak-switch-input>`;
|
||||
@@ -67,10 +65,10 @@ export function renderForm(
|
||||
) {
|
||||
return html` <ak-text-input
|
||||
name="name"
|
||||
value=${ifDefined(provider.name)}
|
||||
value=${ifDefined(provider?.name)}
|
||||
label=${msg("Name")}
|
||||
required
|
||||
.errorMessages=${errors?.name}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
></ak-text-input>
|
||||
<ak-form-element-horizontal
|
||||
name="authorizationFlow"
|
||||
@@ -79,9 +77,9 @@ export function renderForm(
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||
.currentFlow=${provider.authorizationFlow}
|
||||
.errorMessages=${errors?.authorizationFlow}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
required
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when authorizing this provider.")}
|
||||
@@ -93,16 +91,16 @@ export function renderForm(
|
||||
<ak-text-input
|
||||
name="acsUrl"
|
||||
label=${msg("ACS URL")}
|
||||
value="${ifDefined(provider.acsUrl)}"
|
||||
value="${ifDefined(provider?.acsUrl)}"
|
||||
required
|
||||
.errorMessages=${errors?.acsUrl}
|
||||
.errorMessages=${errors?.acsUrl ?? []}
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
label=${msg("Issuer")}
|
||||
name="issuer"
|
||||
value="${provider.issuer || "authentik"}"
|
||||
value="${provider?.issuer || "authentik"}"
|
||||
required
|
||||
.errorMessages=${errors?.issuer}
|
||||
.errorMessages=${errors?.issuer ?? []}
|
||||
help=${msg("Also known as EntityID.")}
|
||||
></ak-text-input>
|
||||
<ak-radio-input
|
||||
@@ -110,7 +108,7 @@ export function renderForm(
|
||||
name="spBinding"
|
||||
required
|
||||
.options=${serviceProviderBindingOptions}
|
||||
.value=${provider.spBinding}
|
||||
.value=${provider?.spBinding}
|
||||
help=${msg(
|
||||
"Determines how authentik sends the response back to the Service Provider.",
|
||||
)}
|
||||
@@ -119,8 +117,8 @@ export function renderForm(
|
||||
<ak-text-input
|
||||
name="audience"
|
||||
label=${msg("Audience")}
|
||||
value="${ifDefined(provider.audience)}"
|
||||
.errorMessages=${errors?.audience}
|
||||
value="${ifDefined(provider?.audience)}"
|
||||
.errorMessages=${errors?.audience ?? []}
|
||||
></ak-text-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
@@ -133,7 +131,7 @@ export function renderForm(
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider.authenticationFlow}
|
||||
.currentFlow=${provider?.authenticationFlow}
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
@@ -148,7 +146,7 @@ export function renderForm(
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider.invalidationFlow}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
defaultFlowSlug="default-provider-invalidation-flow"
|
||||
required
|
||||
></ak-flow-search>
|
||||
@@ -163,7 +161,7 @@ export function renderForm(
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Signing Certificate")} name="signingKp">
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${provider.signingKp}
|
||||
.certificate=${provider?.signingKp}
|
||||
@input=${setHasSigningKp}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
@@ -179,7 +177,7 @@ export function renderForm(
|
||||
name="verificationKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${provider.verificationKp}
|
||||
.certificate=${provider?.verificationKp}
|
||||
nokey
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
@@ -193,7 +191,7 @@ export function renderForm(
|
||||
name="encryptionKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${provider.encryptionKp}
|
||||
.certificate=${provider?.encryptionKp}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("When selected, assertions will be encrypted using this keypair.")}
|
||||
@@ -205,7 +203,7 @@ export function renderForm(
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${propertyMappingsProvider}
|
||||
.selector=${propertyMappingsSelector(provider.propertyMappings)}
|
||||
.selector=${propertyMappingsSelector(provider?.propertyMappings)}
|
||||
available-label=${msg("Available User Property Mappings")}
|
||||
selected-label=${msg("Selected User Property Mappings")}
|
||||
></ak-dual-select-dynamic-selected>
|
||||
@@ -215,7 +213,6 @@ export function renderForm(
|
||||
name="nameIdMapping"
|
||||
>
|
||||
<ak-search-select
|
||||
required
|
||||
.fetchObjects=${async (query?: string): Promise<SAMLPropertyMapping[]> => {
|
||||
const args: PropertymappingsProviderSamlListRequest = {
|
||||
ordering: "saml_name",
|
||||
@@ -235,7 +232,7 @@ export function renderForm(
|
||||
return item?.pk;
|
||||
}}
|
||||
.selected=${(item: SAMLPropertyMapping): boolean => {
|
||||
return provider.nameIdMapping === item.pk;
|
||||
return provider?.nameIdMapping === item.pk;
|
||||
}}
|
||||
blankable
|
||||
>
|
||||
@@ -251,7 +248,6 @@ export function renderForm(
|
||||
name="authnContextClassRefMapping"
|
||||
>
|
||||
<ak-search-select
|
||||
required
|
||||
.fetchObjects=${async (query?: string): Promise<SAMLPropertyMapping[]> => {
|
||||
const args: PropertymappingsProviderSamlListRequest = {
|
||||
ordering: "saml_name",
|
||||
@@ -271,7 +267,7 @@ export function renderForm(
|
||||
return item?.pk;
|
||||
}}
|
||||
.selected=${(item: SAMLPropertyMapping): boolean => {
|
||||
return provider.authnContextClassRefMapping === item.pk;
|
||||
return provider?.authnContextClassRefMapping === item.pk;
|
||||
}}
|
||||
blankable
|
||||
>
|
||||
@@ -286,35 +282,35 @@ export function renderForm(
|
||||
<ak-text-input
|
||||
name="assertionValidNotBefore"
|
||||
label=${msg("Assertion valid not before")}
|
||||
value="${provider.assertionValidNotBefore || "minutes=-5"}"
|
||||
value="${provider?.assertionValidNotBefore || "minutes=-5"}"
|
||||
required
|
||||
.errorMessages=${errors?.assertionValidNotBefore}
|
||||
.errorMessages=${errors?.assertionValidNotBefore ?? []}
|
||||
help=${msg("Configure the maximum allowed time drift for an assertion.")}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="assertionValidNotOnOrAfter"
|
||||
label=${msg("Assertion valid not on or after")}
|
||||
value="${provider.assertionValidNotOnOrAfter || "minutes=5"}"
|
||||
value="${provider?.assertionValidNotOnOrAfter || "minutes=5"}"
|
||||
required
|
||||
.errorMessages=${errors?.assertionValidNotBefore}
|
||||
.errorMessages=${errors?.assertionValidNotBefore ?? []}
|
||||
help=${msg("Assertion not valid on or after current time + this value.")}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="sessionValidNotOnOrAfter"
|
||||
label=${msg("Session valid not on or after")}
|
||||
value="${provider.sessionValidNotOnOrAfter || "minutes=86400"}"
|
||||
value="${provider?.sessionValidNotOnOrAfter || "minutes=86400"}"
|
||||
required
|
||||
.errorMessages=${errors?.sessionValidNotOnOrAfter}
|
||||
.errorMessages=${errors?.sessionValidNotOnOrAfter ?? []}
|
||||
help=${msg("Session not valid on or after current time + this value.")}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="defaultRelayState"
|
||||
label=${msg("Default relay state")}
|
||||
value="${provider.defaultRelayState || ""}"
|
||||
.errorMessages=${errors?.sessionValidNotOnOrAfter}
|
||||
value="${provider?.defaultRelayState || ""}"
|
||||
.errorMessages=${errors?.sessionValidNotOnOrAfter ?? []}
|
||||
help=${msg(
|
||||
"When using IDP-initiated logins, the relay state will be set to this value.",
|
||||
)}
|
||||
@@ -372,7 +368,7 @@ export function renderForm(
|
||||
name="digestAlgorithm"
|
||||
label=${msg("Digest algorithm")}
|
||||
.options=${digestAlgorithmOptions}
|
||||
.value=${provider.digestAlgorithm}
|
||||
.value=${provider?.digestAlgorithm}
|
||||
required
|
||||
>
|
||||
</ak-radio-input>
|
||||
@@ -381,7 +377,7 @@ export function renderForm(
|
||||
name="signatureAlgorithm"
|
||||
label=${msg("Signature algorithm")}
|
||||
.options=${signatureAlgorithmOptions}
|
||||
.value=${provider.signatureAlgorithm}
|
||||
.value=${provider?.signatureAlgorithm}
|
||||
required
|
||||
>
|
||||
</ak-radio-input>
|
||||
|
||||
@@ -28,7 +28,7 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
|
||||
name="name"
|
||||
value=${ifDefined(provider?.name)}
|
||||
label=${msg("Name")}
|
||||
.errorMessages=${errors?.name}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
required
|
||||
help=${msg("Method's display Name.")}
|
||||
></ak-text-input>
|
||||
@@ -38,7 +38,7 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
|
||||
name="url"
|
||||
label=${msg("URL")}
|
||||
value="${provider?.url ?? ""}"
|
||||
.errorMessages=${errors?.url}
|
||||
.errorMessages=${errors?.url ?? []}
|
||||
required
|
||||
help=${msg("SCIM base url, usually ends in /v2.")}
|
||||
input-hint="code"
|
||||
@@ -55,7 +55,7 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
|
||||
name="token"
|
||||
label=${msg("Token")}
|
||||
value="${provider?.token ?? ""}"
|
||||
.errorMessages=${errors?.token}
|
||||
.errorMessages=${errors?.token ?? []}
|
||||
required
|
||||
help=${msg(
|
||||
"Token to authenticate with. Currently only bearer authentication is supported.",
|
||||
|
||||
@@ -232,36 +232,6 @@ export class EmailStageForm extends BaseStageForm<EmailStage> {
|
||||
})}
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Account Recovery Max Attempts")}
|
||||
required
|
||||
name="recoveryMaxAttempts"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${this.instance?.recoveryMaxAttempts ?? 5}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Account Recovery Cache Timeout")}
|
||||
required
|
||||
name="recoveryCacheTimeout"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.recoveryCacheTimeout || "minutes=5")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"The time window used to count recent account recovery attempts.",
|
||||
)}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
${this.renderConnectionSettings()}`;
|
||||
|
||||
@@ -144,14 +144,7 @@ export function composeResponseErrorDescriptor(descriptor: ResponseErrorDescript
|
||||
return `${descriptor.headline}: ${descriptor.reason}`;
|
||||
}
|
||||
|
||||
export const ErrorFieldFallbackKeys = [
|
||||
// ---
|
||||
"detail", // OpenAPI
|
||||
"non_field_errors", // ValidationError.non_field_errors
|
||||
"message", // Error.prototype.message
|
||||
"string", // OpenAPI
|
||||
] as const;
|
||||
|
||||
export const ErrorFieldFallbackKeys = ["detail", "message", "non_field_errors"] as const;
|
||||
export type FallbackError = Record<(typeof ErrorFieldFallbackKeys)[number], string | undefined>;
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user