Compare commits
43 Commits
openapi-ge
...
version/20
Author | SHA1 | Date | |
---|---|---|---|
bda30c5ad5 | |||
588a7ff2e1 | |||
599d0f701f | |||
967e4cce9d | |||
f1c5f43419 | |||
b5b68fc829 | |||
1d7be5e770 | |||
489ef7a0a1 | |||
668f35cd5b | |||
42f0528a1d | |||
ae47624761 | |||
14a6430e21 | |||
ed0a9d6a0a | |||
53143e0c40 | |||
178e010ed4 | |||
49b666fbde | |||
c343e3a7f4 | |||
5febf3ce5b | |||
b8c5bd678b | |||
4dd5eccbaa | |||
2410884006 | |||
3cb921b0f9 | |||
535f92981f | |||
955d69d5b7 | |||
fb01d8e96a | |||
6d39efd3e3 | |||
3020c31bcd | |||
22412729e2 | |||
a02868a27d | |||
bfbb4a8ebc | |||
6c0e827677 | |||
29884cbf81 | |||
0f02985b0c | |||
2244e026c2 | |||
429c03021c | |||
f47e8d9d72 | |||
3e7d2587c4 | |||
55a38d4a36 | |||
6021bb932d | |||
54a5d95717 | |||
a0a1275452 | |||
919aa5df59 | |||
cedf7cf683 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2025.4.0
|
||||
current_version = 2025.4.2
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||
|
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@ -36,7 +36,7 @@ runs:
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- name: Setup docker cache
|
||||
uses: ScribeMD/docker-cache@0.5.0
|
||||
uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7
|
||||
with:
|
||||
key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/docker-compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
|
||||
- name: Setup dependencies
|
||||
|
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@ -118,15 +118,3 @@ updates:
|
||||
prefix: "core:"
|
||||
labels:
|
||||
- dependencies
|
||||
- package-ecosystem: docker-compose
|
||||
directories:
|
||||
# - /scripts # Maybe
|
||||
- /tests/e2e
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "04:00"
|
||||
open-pull-requests-limit: 10
|
||||
commit-message:
|
||||
prefix: "core:"
|
||||
labels:
|
||||
- dependencies
|
||||
|
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -16,7 +16,7 @@
|
||||
],
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.preferences.importModuleSpecifierEnding": "index",
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"typescript.tsdk": "./web/node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"yaml.schemas": {
|
||||
"./blueprints/schema.json": "blueprints/**/*.yaml"
|
||||
@ -30,5 +30,7 @@
|
||||
}
|
||||
],
|
||||
"go.testFlags": ["-count=1"],
|
||||
"github-actions.workflows.pinned.workflows": [".github/workflows/ci-main.yml"]
|
||||
"github-actions.workflows.pinned.workflows": [
|
||||
".github/workflows/ci-main.yml"
|
||||
]
|
||||
}
|
||||
|
@ -40,7 +40,8 @@ COPY ./web /work/web/
|
||||
COPY ./website /work/website/
|
||||
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
|
||||
|
||||
RUN npm run build
|
||||
RUN npm run build && \
|
||||
npm run build:sfe
|
||||
|
||||
# Stage 3: Build go proxy
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
|
||||
@ -94,7 +95,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 5: Download uv
|
||||
FROM ghcr.io/astral-sh/uv:0.7.2 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.6.16 AS uv
|
||||
# Stage 6: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base
|
||||
|
||||
|
94
Makefile
94
Makefile
@ -1,7 +1,6 @@
|
||||
.PHONY: gen dev-reset all clean test web website
|
||||
|
||||
SHELL := /usr/bin/env bash
|
||||
.SHELLFLAGS += ${SHELLFLAGS} -e -o pipefail
|
||||
.SHELLFLAGS += ${SHELLFLAGS} -e
|
||||
PWD = $(shell pwd)
|
||||
UID = $(shell id -u)
|
||||
GID = $(shell id -g)
|
||||
@ -9,9 +8,9 @@ NPM_VERSION = $(shell python -m scripts.generate_semver)
|
||||
PY_SOURCES = authentik tests scripts lifecycle .github
|
||||
DOCKER_IMAGE ?= "authentik:test"
|
||||
|
||||
GEN_API_TS = gen-ts-api
|
||||
GEN_API_PY = gen-py-api
|
||||
GEN_API_GO = gen-go-api
|
||||
GEN_API_TS = "gen-ts-api"
|
||||
GEN_API_PY = "gen-py-api"
|
||||
GEN_API_GO = "gen-go-api"
|
||||
|
||||
pg_user := $(shell uv run python -m authentik.lib.config postgresql.user 2>/dev/null)
|
||||
pg_host := $(shell uv run python -m authentik.lib.config postgresql.host 2>/dev/null)
|
||||
@ -118,45 +117,63 @@ gen-diff: ## (Release) generate the changelog diff between the current schema a
|
||||
npx prettier --write diff.md
|
||||
|
||||
gen-clean-ts: ## Remove generated API client for Typescript
|
||||
rm -rf ${PWD}/${GEN_API_TS}/
|
||||
rm -rf ${PWD}/web/node_modules/@goauthentik/api/
|
||||
rm -rf ./${GEN_API_TS}/
|
||||
rm -rf ./web/node_modules/@goauthentik/api/
|
||||
|
||||
gen-clean-go: ## Remove generated API client for Go
|
||||
mkdir -p ${PWD}/${GEN_API_GO}
|
||||
ifneq ($(wildcard ${PWD}/${GEN_API_GO}/.*),)
|
||||
make -C ${PWD}/${GEN_API_GO} clean
|
||||
else
|
||||
rm -rf ${PWD}/${GEN_API_GO}
|
||||
endif
|
||||
gen-clean-go: ## Remove generated API client for Go
|
||||
rm -rf ./${GEN_API_GO}/
|
||||
|
||||
gen-clean-py: ## Remove generated API client for Python
|
||||
rm -rf ${PWD}/${GEN_API_PY}/
|
||||
gen-clean-py: ## Remove generated API client for Python
|
||||
rm -rf ./${GEN_API_PY}/
|
||||
|
||||
gen-clean: gen-clean-ts gen-clean-go gen-clean-py ## Remove generated API clients
|
||||
|
||||
gen-client-ts: gen-clean-ts ## Build and install the authentik API for Typescript into the authentik UI Application
|
||||
./scripts/gen-client-ts.mjs
|
||||
|
||||
npm i --prefix ${GEN_API_TS}
|
||||
|
||||
cd ./${GEN_API_TS} && npm link
|
||||
cd ./web && npm link @goauthentik/api
|
||||
docker run \
|
||||
--rm -v ${PWD}:/local \
|
||||
--user ${UID}:${GID} \
|
||||
docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \
|
||||
-i /local/schema.yml \
|
||||
-g typescript-fetch \
|
||||
-o /local/${GEN_API_TS} \
|
||||
-c /local/scripts/api-ts-config.yaml \
|
||||
--additional-properties=npmVersion=${NPM_VERSION} \
|
||||
--git-repo-id authentik \
|
||||
--git-user-id goauthentik
|
||||
mkdir -p web/node_modules/@goauthentik/api
|
||||
cd ./${GEN_API_TS} && npm i
|
||||
\cp -rf ./${GEN_API_TS}/* web/node_modules/@goauthentik/api
|
||||
|
||||
gen-client-py: gen-clean-py ## Build and install the authentik API for Python
|
||||
./scripts/gen-client-py.mjs
|
||||
|
||||
docker run \
|
||||
--rm -v ${PWD}:/local \
|
||||
--user ${UID}:${GID} \
|
||||
docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \
|
||||
-i /local/schema.yml \
|
||||
-g python \
|
||||
-o /local/${GEN_API_PY} \
|
||||
-c /local/scripts/api-py-config.yaml \
|
||||
--additional-properties=packageVersion=${NPM_VERSION} \
|
||||
--git-repo-id authentik \
|
||||
--git-user-id goauthentik
|
||||
pip install ./${GEN_API_PY}
|
||||
|
||||
gen-client-go: gen-clean-go ## Build and install the authentik API for Golang
|
||||
mkdir -p ${PWD}/${GEN_API_GO}
|
||||
ifeq ($(wildcard ${PWD}/${GEN_API_GO}/.*),)
|
||||
git clone --depth 1 https://github.com/goauthentik/client-go.git ${PWD}/${GEN_API_GO}
|
||||
else
|
||||
cd ${PWD}/${GEN_API_GO} && git pull
|
||||
endif
|
||||
cp ${PWD}/schema.yml ${PWD}/${GEN_API_GO}
|
||||
make -C ${PWD}/${GEN_API_GO} build
|
||||
mkdir -p ./${GEN_API_GO} ./${GEN_API_GO}/templates
|
||||
wget https://raw.githubusercontent.com/goauthentik/client-go/main/config.yaml -O ./${GEN_API_GO}/config.yaml
|
||||
wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/README.mustache -O ./${GEN_API_GO}/templates/README.mustache
|
||||
wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/go.mod.mustache -O ./${GEN_API_GO}/templates/go.mod.mustache
|
||||
cp schema.yml ./${GEN_API_GO}/
|
||||
docker run \
|
||||
--rm -v ${PWD}/${GEN_API_GO}:/local \
|
||||
--user ${UID}:${GID} \
|
||||
docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \
|
||||
-i /local/schema.yml \
|
||||
-g go \
|
||||
-o /local/ \
|
||||
-c /local/config.yaml
|
||||
go mod edit -replace goauthentik.io/api/v3=./${GEN_API_GO}
|
||||
rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/
|
||||
|
||||
gen-dev-config: ## Generate a local development config file
|
||||
uv run scripts/generate_config.py
|
||||
@ -227,7 +244,7 @@ docker: ## Build a docker image of the current source tree
|
||||
DOCKER_BUILDKIT=1 docker build . --progress plain --tag ${DOCKER_IMAGE}
|
||||
|
||||
test-docker:
|
||||
BUILD=true ${PWD}/scripts/test_docker.sh
|
||||
BUILD=true ./scripts/test_docker.sh
|
||||
|
||||
#########################
|
||||
## CI
|
||||
@ -247,3 +264,14 @@ ci-ruff: ci--meta-debug
|
||||
|
||||
ci-codespell: ci--meta-debug
|
||||
uv run codespell -s
|
||||
|
||||
ci-bandit: ci--meta-debug
|
||||
uv run bandit -r $(PY_SOURCES)
|
||||
|
||||
ci-pending-migrations: ci--meta-debug
|
||||
uv run ak makemigrations --check
|
||||
|
||||
ci-test: ci--meta-debug
|
||||
uv run coverage run manage.py test --keepdb --randomly-seed ${CI_TEST_SEED} authentik
|
||||
uv run coverage report
|
||||
uv run coverage xml
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from os import environ
|
||||
|
||||
__version__ = "2025.4.0"
|
||||
__version__ = "2025.4.2"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -99,18 +99,17 @@ class GroupSerializer(ModelSerializer):
|
||||
if superuser
|
||||
else "authentik_core.disable_group_superuser"
|
||||
)
|
||||
has_perm = user.has_perm(perm)
|
||||
if self.instance and not has_perm:
|
||||
has_perm = user.has_perm(perm, self.instance)
|
||||
if not has_perm:
|
||||
raise ValidationError(
|
||||
_(
|
||||
(
|
||||
"User does not have permission to set "
|
||||
"superuser status to {superuser_status}."
|
||||
).format_map({"superuser_status": superuser})
|
||||
if self.instance or superuser:
|
||||
has_perm = user.has_perm(perm) or user.has_perm(perm, self.instance)
|
||||
if not has_perm:
|
||||
raise ValidationError(
|
||||
_(
|
||||
(
|
||||
"User does not have permission to set "
|
||||
"superuser status to {superuser_status}."
|
||||
).format_map({"superuser_status": superuser})
|
||||
)
|
||||
)
|
||||
)
|
||||
return superuser
|
||||
|
||||
class Meta:
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from django.apps import apps
|
||||
from django.contrib.auth.management import create_permissions
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand, no_translations
|
||||
from guardian.management import create_anonymous_user
|
||||
|
||||
@ -16,6 +17,10 @@ class Command(BaseCommand):
|
||||
"""Check permissions for all apps"""
|
||||
for tenant in Tenant.objects.filter(ready=True):
|
||||
with tenant:
|
||||
# See https://code.djangoproject.com/ticket/28417
|
||||
# Remove potential lingering old permissions
|
||||
call_command("remove_stale_contenttypes", "--no-input")
|
||||
|
||||
for app in apps.get_app_configs():
|
||||
self.stdout.write(f"Checking app {app.name} ({app.label})\n")
|
||||
create_permissions(app, verbosity=0)
|
||||
|
@ -31,7 +31,10 @@ class PickleSerializer:
|
||||
|
||||
def loads(self, data):
|
||||
"""Unpickle data to be loaded from redis"""
|
||||
return pickle.loads(data) # nosec
|
||||
try:
|
||||
return pickle.loads(data) # nosec
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _migrate_session(
|
||||
@ -76,6 +79,7 @@ def _migrate_session(
|
||||
AuthenticatedSession.objects.using(db_alias).create(
|
||||
session=session,
|
||||
user=old_auth_session.user,
|
||||
uuid=old_auth_session.uuid,
|
||||
)
|
||||
|
||||
|
||||
|
@ -0,0 +1,103 @@
|
||||
# Generated by Django 5.1.9 on 2025-05-14 11:15
|
||||
|
||||
from django.apps.registry import Apps, apps as global_apps
|
||||
from django.db import migrations
|
||||
from django.contrib.contenttypes.management import create_contenttypes
|
||||
from django.contrib.auth.management import create_permissions
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def migrate_authenticated_session_permissions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
"""Migrate permissions from OldAuthenticatedSession to AuthenticatedSession"""
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
# `apps` here is just an instance of `django.db.migrations.state.AppConfigStub`, we need the
|
||||
# real config for creating permissions and content types
|
||||
authentik_core_config = global_apps.get_app_config("authentik_core")
|
||||
# These are only ran by django after all migrations, but we need them right now.
|
||||
# `global_apps` is needed,
|
||||
create_permissions(authentik_core_config, using=db_alias, verbosity=1)
|
||||
create_contenttypes(authentik_core_config, using=db_alias, verbosity=1)
|
||||
|
||||
# But from now on, this is just a regular migration, so use `apps`
|
||||
Permission = apps.get_model("auth", "Permission")
|
||||
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||
|
||||
try:
|
||||
old_ct = ContentType.objects.using(db_alias).get(
|
||||
app_label="authentik_core", model="oldauthenticatedsession"
|
||||
)
|
||||
new_ct = ContentType.objects.using(db_alias).get(
|
||||
app_label="authentik_core", model="authenticatedsession"
|
||||
)
|
||||
except ContentType.DoesNotExist:
|
||||
# This should exist at this point, but if not, let's cut our losses
|
||||
return
|
||||
|
||||
# Get all permissions for the old content type
|
||||
old_perms = Permission.objects.using(db_alias).filter(content_type=old_ct)
|
||||
|
||||
# Create equivalent permissions for the new content type
|
||||
for old_perm in old_perms:
|
||||
new_perm = (
|
||||
Permission.objects.using(db_alias)
|
||||
.filter(
|
||||
content_type=new_ct,
|
||||
codename=old_perm.codename,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not new_perm:
|
||||
# This should exist at this point, but if not, let's cut our losses
|
||||
continue
|
||||
|
||||
# Global user permissions
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
User.user_permissions.through.objects.using(db_alias).filter(
|
||||
permission=old_perm
|
||||
).all().update(permission=new_perm)
|
||||
|
||||
# Global role permissions
|
||||
DjangoGroup = apps.get_model("auth", "Group")
|
||||
DjangoGroup.permissions.through.objects.using(db_alias).filter(
|
||||
permission=old_perm
|
||||
).all().update(permission=new_perm)
|
||||
|
||||
# Object user permissions
|
||||
UserObjectPermission = apps.get_model("guardian", "UserObjectPermission")
|
||||
UserObjectPermission.objects.using(db_alias).filter(permission=old_perm).all().update(
|
||||
permission=new_perm, content_type=new_ct
|
||||
)
|
||||
|
||||
# Object role permissions
|
||||
GroupObjectPermission = apps.get_model("guardian", "GroupObjectPermission")
|
||||
GroupObjectPermission.objects.using(db_alias).filter(permission=old_perm).all().update(
|
||||
permission=new_perm, content_type=new_ct
|
||||
)
|
||||
|
||||
|
||||
def remove_old_authenticated_session_content_type(
|
||||
apps: Apps, schema_editor: BaseDatabaseSchemaEditor
|
||||
):
|
||||
db_alias = schema_editor.connection.alias
|
||||
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||
|
||||
ContentType.objects.using(db_alias).filter(model="oldauthenticatedsession").delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0047_delete_oldauthenticatedsession"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
code=migrate_authenticated_session_permissions,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=remove_old_authenticated_session_content_type,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
),
|
||||
]
|
@ -124,6 +124,16 @@ class TestGroupsAPI(APITestCase):
|
||||
{"is_superuser": ["User does not have permission to set superuser status to True."]},
|
||||
)
|
||||
|
||||
def test_superuser_no_perm_no_superuser(self):
|
||||
"""Test creating a group without permission and without superuser flag"""
|
||||
assign_perm("authentik_core.add_group", self.login_user)
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.post(
|
||||
reverse("authentik_api:group-list"),
|
||||
data={"name": generate_id(), "is_superuser": False},
|
||||
)
|
||||
self.assertEqual(res.status_code, 201)
|
||||
|
||||
def test_superuser_update_no_perm(self):
|
||||
"""Test updating a superuser group without permission"""
|
||||
group = Group.objects.create(name=generate_id(), is_superuser=True)
|
||||
|
@ -132,13 +132,14 @@ class LicenseKey:
|
||||
"""Get a summarized version of all (not expired) licenses"""
|
||||
total = LicenseKey(get_license_aud(), 0, "Summarized license", 0, 0)
|
||||
for lic in License.objects.all():
|
||||
total.internal_users += lic.internal_users
|
||||
total.external_users += lic.external_users
|
||||
if lic.is_valid:
|
||||
total.internal_users += lic.internal_users
|
||||
total.external_users += lic.external_users
|
||||
total.license_flags.extend(lic.status.license_flags)
|
||||
exp_ts = int(mktime(lic.expiry.timetuple()))
|
||||
if total.exp == 0:
|
||||
total.exp = exp_ts
|
||||
total.exp = max(total.exp, exp_ts)
|
||||
total.license_flags.extend(lic.status.license_flags)
|
||||
return total
|
||||
|
||||
@staticmethod
|
||||
|
@ -39,6 +39,10 @@ class License(SerializerModel):
|
||||
internal_users = models.BigIntegerField()
|
||||
external_users = models.BigIntegerField()
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
return self.expiry >= now()
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[BaseSerializer]:
|
||||
from authentik.enterprise.api import LicenseSerializer
|
||||
|
@ -8,6 +8,7 @@ from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.enterprise.license import LicenseKey
|
||||
from authentik.enterprise.models import (
|
||||
THRESHOLD_READ_ONLY_WEEKS,
|
||||
@ -71,9 +72,9 @@ class TestEnterpriseLicense(TestCase):
|
||||
)
|
||||
def test_valid_multiple(self):
|
||||
"""Check license verification"""
|
||||
lic = License.objects.create(key=generate_id())
|
||||
lic = License.objects.create(key=generate_id(), expiry=expiry_valid)
|
||||
self.assertTrue(lic.status.status().is_valid)
|
||||
lic2 = License.objects.create(key=generate_id())
|
||||
lic2 = License.objects.create(key=generate_id(), expiry=expiry_valid)
|
||||
self.assertTrue(lic2.status.status().is_valid)
|
||||
total = LicenseKey.get_total()
|
||||
self.assertEqual(total.internal_users, 200)
|
||||
@ -232,7 +233,9 @@ class TestEnterpriseLicense(TestCase):
|
||||
)
|
||||
def test_expiry_expired(self):
|
||||
"""Check license verification"""
|
||||
License.objects.create(key=generate_id())
|
||||
User.objects.all().delete()
|
||||
License.objects.all().delete()
|
||||
License.objects.create(key=generate_id(), expiry=expiry_expired)
|
||||
self.assertEqual(LicenseKey.get_total().summary().status, LicenseUsageStatus.EXPIRED)
|
||||
|
||||
@patch(
|
||||
|
@ -15,6 +15,7 @@
|
||||
{% endblock %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
|
||||
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
||||
<link rel="prefetch" href="{{ flow_background_url }}" />
|
||||
{% include "base/header_js.html" %}
|
||||
<style>
|
||||
html,
|
||||
@ -22,7 +23,7 @@
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-image: url("{{ flow.background_url }}");
|
||||
background-image: url("{{ flow_background_url }}");
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
{% block head_before %}
|
||||
{{ block.super }}
|
||||
<link rel="prefetch" href="{{ flow.background_url }}" />
|
||||
<link rel="prefetch" href="{{ flow_background_url }}" />
|
||||
{% if flow.compatibility_mode and not inspector %}
|
||||
<script>ShadyDOM = { force: !navigator.webdriver };</script>
|
||||
{% endif %}
|
||||
@ -21,7 +21,7 @@ window.authentik.flow = {
|
||||
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
|
||||
<style>
|
||||
:root {
|
||||
--ak-flow-background: url("{{ flow.background_url }}");
|
||||
--ak-flow-background: url("{{ flow_background_url }}");
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
@ -13,7 +13,9 @@ class FlowInterfaceView(InterfaceView):
|
||||
"""Flow interface"""
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
||||
flow = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
||||
kwargs["flow"] = flow
|
||||
kwargs["flow_background_url"] = flow.background_url(self.request)
|
||||
kwargs["inspector"] = "inspector" in self.request.GET
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
@ -363,6 +363,9 @@ def django_db_config(config: ConfigLoader | None = None) -> dict:
|
||||
pool_options = config.get_dict_from_b64_json("postgresql.pool_options", True)
|
||||
if not pool_options:
|
||||
pool_options = True
|
||||
# FIXME: Temporarily force pool to be deactivated.
|
||||
# See https://github.com/goauthentik/authentik/issues/14320
|
||||
pool_options = False
|
||||
|
||||
db = {
|
||||
"default": {
|
||||
|
@ -494,86 +494,88 @@ class TestConfig(TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_db_pool(self):
|
||||
"""Test DB Config with pool"""
|
||||
config = ConfigLoader()
|
||||
config.set("postgresql.host", "foo")
|
||||
config.set("postgresql.name", "foo")
|
||||
config.set("postgresql.user", "foo")
|
||||
config.set("postgresql.password", "foo")
|
||||
config.set("postgresql.port", "foo")
|
||||
config.set("postgresql.test.name", "foo")
|
||||
config.set("postgresql.use_pool", True)
|
||||
conf = django_db_config(config)
|
||||
self.assertEqual(
|
||||
conf,
|
||||
{
|
||||
"default": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "foo",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"pool": True,
|
||||
"sslcert": None,
|
||||
"sslkey": None,
|
||||
"sslmode": None,
|
||||
"sslrootcert": None,
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
"CONN_MAX_AGE": 0,
|
||||
"CONN_HEALTH_CHECKS": False,
|
||||
"DISABLE_SERVER_SIDE_CURSORS": False,
|
||||
}
|
||||
},
|
||||
)
|
||||
# FIXME: Temporarily force pool to be deactivated.
|
||||
# See https://github.com/goauthentik/authentik/issues/14320
|
||||
# def test_db_pool(self):
|
||||
# """Test DB Config with pool"""
|
||||
# config = ConfigLoader()
|
||||
# config.set("postgresql.host", "foo")
|
||||
# config.set("postgresql.name", "foo")
|
||||
# config.set("postgresql.user", "foo")
|
||||
# config.set("postgresql.password", "foo")
|
||||
# config.set("postgresql.port", "foo")
|
||||
# config.set("postgresql.test.name", "foo")
|
||||
# config.set("postgresql.use_pool", True)
|
||||
# conf = django_db_config(config)
|
||||
# self.assertEqual(
|
||||
# conf,
|
||||
# {
|
||||
# "default": {
|
||||
# "ENGINE": "authentik.root.db",
|
||||
# "HOST": "foo",
|
||||
# "NAME": "foo",
|
||||
# "OPTIONS": {
|
||||
# "pool": True,
|
||||
# "sslcert": None,
|
||||
# "sslkey": None,
|
||||
# "sslmode": None,
|
||||
# "sslrootcert": None,
|
||||
# },
|
||||
# "PASSWORD": "foo",
|
||||
# "PORT": "foo",
|
||||
# "TEST": {"NAME": "foo"},
|
||||
# "USER": "foo",
|
||||
# "CONN_MAX_AGE": 0,
|
||||
# "CONN_HEALTH_CHECKS": False,
|
||||
# "DISABLE_SERVER_SIDE_CURSORS": False,
|
||||
# }
|
||||
# },
|
||||
# )
|
||||
|
||||
def test_db_pool_options(self):
|
||||
"""Test DB Config with pool"""
|
||||
config = ConfigLoader()
|
||||
config.set("postgresql.host", "foo")
|
||||
config.set("postgresql.name", "foo")
|
||||
config.set("postgresql.user", "foo")
|
||||
config.set("postgresql.password", "foo")
|
||||
config.set("postgresql.port", "foo")
|
||||
config.set("postgresql.test.name", "foo")
|
||||
config.set("postgresql.use_pool", True)
|
||||
config.set(
|
||||
"postgresql.pool_options",
|
||||
base64.b64encode(
|
||||
dumps(
|
||||
{
|
||||
"max_size": 15,
|
||||
}
|
||||
).encode()
|
||||
).decode(),
|
||||
)
|
||||
conf = django_db_config(config)
|
||||
self.assertEqual(
|
||||
conf,
|
||||
{
|
||||
"default": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "foo",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"pool": {
|
||||
"max_size": 15,
|
||||
},
|
||||
"sslcert": None,
|
||||
"sslkey": None,
|
||||
"sslmode": None,
|
||||
"sslrootcert": None,
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
"CONN_MAX_AGE": 0,
|
||||
"CONN_HEALTH_CHECKS": False,
|
||||
"DISABLE_SERVER_SIDE_CURSORS": False,
|
||||
}
|
||||
},
|
||||
)
|
||||
# def test_db_pool_options(self):
|
||||
# """Test DB Config with pool"""
|
||||
# config = ConfigLoader()
|
||||
# config.set("postgresql.host", "foo")
|
||||
# config.set("postgresql.name", "foo")
|
||||
# config.set("postgresql.user", "foo")
|
||||
# config.set("postgresql.password", "foo")
|
||||
# config.set("postgresql.port", "foo")
|
||||
# config.set("postgresql.test.name", "foo")
|
||||
# config.set("postgresql.use_pool", True)
|
||||
# config.set(
|
||||
# "postgresql.pool_options",
|
||||
# base64.b64encode(
|
||||
# dumps(
|
||||
# {
|
||||
# "max_size": 15,
|
||||
# }
|
||||
# ).encode()
|
||||
# ).decode(),
|
||||
# )
|
||||
# conf = django_db_config(config)
|
||||
# self.assertEqual(
|
||||
# conf,
|
||||
# {
|
||||
# "default": {
|
||||
# "ENGINE": "authentik.root.db",
|
||||
# "HOST": "foo",
|
||||
# "NAME": "foo",
|
||||
# "OPTIONS": {
|
||||
# "pool": {
|
||||
# "max_size": 15,
|
||||
# },
|
||||
# "sslcert": None,
|
||||
# "sslkey": None,
|
||||
# "sslmode": None,
|
||||
# "sslrootcert": None,
|
||||
# },
|
||||
# "PASSWORD": "foo",
|
||||
# "PORT": "foo",
|
||||
# "TEST": {"NAME": "foo"},
|
||||
# "USER": "foo",
|
||||
# "CONN_MAX_AGE": 0,
|
||||
# "CONN_HEALTH_CHECKS": False,
|
||||
# "DISABLE_SERVER_SIDE_CURSORS": False,
|
||||
# }
|
||||
# },
|
||||
# )
|
||||
|
@ -97,7 +97,8 @@ class GroupsView(SCIMObjectView):
|
||||
self.logger.warning("Invalid group member", exc=exc)
|
||||
continue
|
||||
query |= Q(uuid=member.value)
|
||||
group.users.set(User.objects.filter(query))
|
||||
if query:
|
||||
group.users.set(User.objects.filter(query))
|
||||
if not connection:
|
||||
connection, _ = SCIMSourceGroup.objects.get_or_create(
|
||||
source=self.source,
|
||||
|
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik 2025.4.0 Blueprint schema",
|
||||
"title": "authentik 2025.4.2 Blueprint schema",
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
|
@ -31,7 +31,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.0}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.2}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -55,7 +55,7 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.0}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.2}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
6
go.mod
6
go.mod
@ -21,13 +21,13 @@ require (
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
github.com/pires/go-proxyproto v0.8.0
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/redis/go-redis/v9 v9.8.0
|
||||
github.com/sethvargo/go-envconfig v1.3.0
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
github.com/sethvargo/go-envconfig v1.2.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
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.2025040.1
|
||||
goauthentik.io/api/v3 v3.2025024.9
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.29.0
|
||||
golang.org/x/sync v0.13.0
|
||||
|
12
go.sum
12
go.sum
@ -245,14 +245,14 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
|
||||
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sethvargo/go-envconfig v1.3.0 h1:gJs+Fuv8+f05omTpwWIu6KmuseFAXKrIaOZSh8RMt0U=
|
||||
github.com/sethvargo/go-envconfig v1.3.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
|
||||
github.com/sethvargo/go-envconfig v1.2.0 h1:q3XkOZWkC+G1sMLCrw9oPGTjYexygLOXDmGUit1ti8Q=
|
||||
github.com/sethvargo/go-envconfig v1.2.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
@ -290,8 +290,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.2025040.1 h1:rQEcMNpz84/LPX8LVFteOJuserrd4PnU4k1Iu/wWqhs=
|
||||
goauthentik.io/api/v3 v3.2025040.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2025024.9 h1:i3tbkyotE32ZpJ729BsPWTuLQUdtZ54Li4aP1amZzsM=
|
||||
goauthentik.io/api/v3 v3.2025024.9/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2025.4.0"
|
||||
const VERSION = "2025.4.2"
|
||||
|
@ -56,6 +56,7 @@ EXPOSE 3389 6636 9300
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV GOFIPS=1
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/ldap"]
|
||||
|
@ -83,7 +83,8 @@ if [[ "$1" == "server" ]]; then
|
||||
run_authentik
|
||||
elif [[ "$1" == "worker" ]]; then
|
||||
set_mode "worker"
|
||||
check_if_root "python -m manage worker"
|
||||
shift
|
||||
check_if_root "python -m manage worker $@"
|
||||
elif [[ "$1" == "worker-status" ]]; then
|
||||
wait_for_db
|
||||
celery -A authentik.root.celery flower \
|
||||
@ -97,6 +98,7 @@ elif [[ "$1" == "test-all" ]]; then
|
||||
elif [[ "$1" == "healthcheck" ]]; then
|
||||
run_authentik healthcheck $(cat $MODE_FILE)
|
||||
elif [[ "$1" == "dump_config" ]]; then
|
||||
shift
|
||||
exec python -m authentik.lib.config $@
|
||||
elif [[ "$1" == "debug" ]]; then
|
||||
exec sleep infinity
|
||||
|
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.1013.0",
|
||||
"aws-cdk": "^2.1012.0",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -17,9 +17,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.1013.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1013.0.tgz",
|
||||
"integrity": "sha512-cbq4cOoEIZueMWenGgfI4RujS+AQ9GaMCTlW/3CnvEIhMD8j/tgZx7PTtgMuvwYrRoEeb/wTxgLPgUd5FhsoHA==",
|
||||
"version": "2.1012.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1012.0.tgz",
|
||||
"integrity": "sha512-C6jSWkqP0hkY2Cs300VJHjspmTXDTMfB813kwZvRbd/OsKBfTBJBbYU16VoLAp1LVEOnQMf8otSlaSgzVF0X9A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -10,7 +10,7 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1013.0",
|
||||
"aws-cdk": "^2.1012.0",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ Parameters:
|
||||
Description: authentik Docker image
|
||||
AuthentikVersion:
|
||||
Type: String
|
||||
Default: 2025.4.0
|
||||
Default: 2025.4.2
|
||||
Description: authentik Docker image tag
|
||||
AuthentikServerCPU:
|
||||
Type: Number
|
||||
|
Binary file not shown.
@ -12,8 +12,8 @@
|
||||
# tmassimi, 2024
|
||||
# Marc Schmitt, 2024
|
||||
# albanobattistella <albanobattistella@gmail.com>, 2024
|
||||
# Matteo Piccina <altermatte@gmail.com>, 2025
|
||||
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
|
||||
# Matteo Piccina <altermatte@gmail.com>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
@ -22,7 +22,7 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
|
||||
"Last-Translator: Matteo Piccina <altermatte@gmail.com>, 2025\n"
|
||||
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -383,7 +383,7 @@ msgstr "Mappatura delle proprietà"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr "dati sessione"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
@ -509,7 +509,7 @@ msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr "Numero di password da verificare."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
@ -519,19 +519,18 @@ msgstr "Password non impostata nel contesto"
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
"Questa password è già stata utilizzata in precedenza. Scegline una diversa."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr "Politica di unicità della password"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr "Criteri di unicità delle password"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr "Cronologia password utente"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
@ -2204,7 +2203,7 @@ msgstr "Ruoli"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr "Permessi Iniziali"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
@ -2459,9 +2458,6 @@ msgid ""
|
||||
"attribute. This allows nested group resolution on systems like FreeIPA and "
|
||||
"Active Directory"
|
||||
msgstr ""
|
||||
"Cerca l'appartenenza al gruppo in base a un attributo utente anziché a un "
|
||||
"attributo di gruppo. Questo consente la risoluzione di gruppi nidificati su "
|
||||
"sistemi come FreeIPA e Active Directory."
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "LDAP Source"
|
||||
@ -2481,19 +2477,19 @@ msgstr "Mappature delle proprietà della sorgente LDAP"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr "Connessione Sorgente LDAP Utente"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr "Connessioni Sorgente LDAP Utente"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr "Connessione Sorgente LDAP Gruppo"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr "Connessioni Sorgente LDAP Gruppo"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
@ -2505,11 +2501,11 @@ msgstr "Nessun token ricevuto."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr "HTTP Basic Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr "Includi il client ID e il segreto come parametri di richiesta"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
@ -2556,8 +2552,6 @@ msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
"Come eseguire l'autenticazione durante un flusso di richiesta del token "
|
||||
"authorization_code"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
@ -3490,9 +3484,6 @@ msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
"Mostra all'utente il pulsante \"Ricordami su questo dispositivo\", "
|
||||
"consentendo agli utenti abituali di passare direttamente all'inserimento "
|
||||
"della password."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
@ -3882,11 +3873,11 @@ msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr "La reputazione non può scendere sotto questo valore. Zero o negativo."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr "La reputazione non può superare questo valore. Zero o positivo."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
538
package-lock.json
generated
538
package-lock.json
generated
@ -1,546 +1,12 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.4.0",
|
||||
"version": "2025.2.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.4.0",
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-packagejson": "^2.5.10",
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.25.9",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
|
||||
"integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.27.0",
|
||||
"@babel/types": "^7.27.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
||||
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
|
||||
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
|
||||
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.27.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
|
||||
"integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/parser": "^7.27.0",
|
||||
"@babel/types": "^7.27.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
|
||||
"integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/generator": "^7.27.0",
|
||||
"@babel/parser": "^7.27.0",
|
||||
"@babel/template": "^7.27.0",
|
||||
"@babel/types": "^7.27.0",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
|
||||
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
"@babel/helper-validator-identifier": "^7.25.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.2.tgz",
|
||||
"integrity": "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/@trivago/prettier-plugin-sort-imports": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz",
|
||||
"integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.26.5",
|
||||
"@babel/parser": "^7.26.7",
|
||||
"@babel/traverse": "^7.26.7",
|
||||
"@babel/types": "^7.26.7",
|
||||
"javascript-natural-sort": "^0.7.1",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">18.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/compiler-sfc": "3.x",
|
||||
"prettier": "2.x - 3.x",
|
||||
"prettier-plugin-svelte": "3.x",
|
||||
"svelte": "4.x || 5.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/compiler-sfc": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-svelte": {
|
||||
"optional": true
|
||||
},
|
||||
"svelte": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/detect-indent": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.1.tgz",
|
||||
"integrity": "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-newline": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-4.0.1.tgz",
|
||||
"integrity": "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.4.4",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
|
||||
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/get-stdin": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
|
||||
"integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/git-hooks-list": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-3.2.0.tgz",
|
||||
"integrity": "sha512-ZHG9a1gEhUMX1TvGrLdyWb9kDopCBbTnI8z4JgRMYxsijWipgjSEYoPWqBuIB0DnRnvqlQSEeVmzpeuPm7NdFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/fisker/git-hooks-list?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-obj": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
|
||||
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/javascript-natural-sort": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-organize-imports": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
|
||||
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"prettier": ">=2.0",
|
||||
"typescript": ">=2.9",
|
||||
"vue-tsc": "^2.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"vue-tsc": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-packagejson": {
|
||||
"version": "2.5.10",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.10.tgz",
|
||||
"integrity": "sha512-LUxATI5YsImIVSaaLJlJ3aE6wTD+nvots18U3GuQMJpUyClChaZlQrqx3dBnbhF20OnKWZyx8EgyZypQtBDtgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sort-package-json": "2.15.1",
|
||||
"synckit": "0.9.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prettier": ">= 1.16.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"prettier": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sort-object-keys": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz",
|
||||
"integrity": "sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sort-package-json": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.15.1.tgz",
|
||||
"integrity": "sha512-9x9+o8krTT2saA9liI4BljNjwAbvUnWf11Wq+i/iZt8nl2UGYnf3TH5uBydE7VALmP7AGwlfszuEeL8BDyb0YA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-indent": "^7.0.1",
|
||||
"detect-newline": "^4.0.0",
|
||||
"get-stdin": "^9.0.0",
|
||||
"git-hooks-list": "^3.0.0",
|
||||
"is-plain-obj": "^4.1.0",
|
||||
"semver": "^7.6.0",
|
||||
"sort-object-keys": "^1.1.3",
|
||||
"tinyglobby": "^0.2.9"
|
||||
},
|
||||
"bin": {
|
||||
"sort-package-json": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
|
||||
"integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pkgr/core": "^0.1.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
|
||||
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
"version": "2025.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
14
package.json
14
package.json
@ -1,15 +1,5 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.4.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-packagejson": "^2.5.10",
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"workspaces": [],
|
||||
"prettier": "./packages/prettier-config/index.js"
|
||||
"version": "2025.4.2",
|
||||
"private": true
|
||||
}
|
||||
|
@ -2,3 +2,4 @@
|
||||
|
||||
This package contains utility scripts common to all TypeScript and JavaScript packages in the
|
||||
`@goauthentik` monorepo.
|
||||
|
@ -1,9 +1,8 @@
|
||||
/**
|
||||
* @file Constants for JavaScript and TypeScript files.
|
||||
*
|
||||
*/
|
||||
|
||||
/// <reference types="../../types/global.js" />
|
||||
|
||||
/**
|
||||
* The current Node.js environment, defaulting to "development" when not set.
|
||||
*
|
||||
@ -13,4 +12,6 @@
|
||||
* ensure that module tree-shaking works correctly.
|
||||
*
|
||||
*/
|
||||
export const NodeEnvironment = process.env.NODE_ENV || "development";
|
||||
export const NodeEnvironment = /** @type {'development' | 'production'} */ (
|
||||
process.env.NODE_ENV || "development"
|
||||
);
|
@ -1,7 +1,4 @@
|
||||
/// <reference types="./types/global.js" />
|
||||
|
||||
export * from "./paths.js";
|
||||
export * from "./constants.js";
|
||||
export * from "./build.js";
|
||||
export * from "./version.js";
|
||||
export * from "./scripting.js";
|
19
packages/monorepo/package.json
Normal file
19
packages/monorepo/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@goauthentik/monorepo",
|
||||
"version": "1.0.0",
|
||||
"description": "Utilities for the authentik monorepo.",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./out/index.d.ts"
|
||||
}
|
||||
},
|
||||
"types": "./out/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
}
|
||||
}
|
30
packages/monorepo/paths.js
Normal file
30
packages/monorepo/paths.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* @typedef {'~authentik'} MonoRepoRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* The root of the authentik monorepo.
|
||||
*/
|
||||
export const MonoRepoRoot = /** @type {MonoRepoRoot} */ (resolve(__dirname, "..", ".."));
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
/**
|
||||
* Resolve a package name to its location in the monorepo to the single node_modules directory.
|
||||
* @param {string} packageName
|
||||
* @returns {string} The resolved path to the package.
|
||||
* @throws {Error} If the package cannot be resolved.
|
||||
*/
|
||||
export function resolvePackage(packageName) {
|
||||
const packageJSONPath = require.resolve(join(packageName, "package.json"), {
|
||||
paths: [MonoRepoRoot],
|
||||
});
|
||||
|
||||
return dirname(packageJSONPath);
|
||||
}
|
0
packages/monorepo/scripts.js
Normal file
0
packages/monorepo/scripts.js
Normal file
@ -1,6 +1,6 @@
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
import PackageJSON from "../../../package.json" with { type: "json" };
|
||||
import PackageJSON from "../../package.json" with { type: "json" };
|
||||
import { MonoRepoRoot } from "./paths.js";
|
||||
|
||||
/**
|
@ -76,6 +76,7 @@ EXPOSE 9000 9300 9443
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV GOFIPS=1
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/proxy"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "authentik"
|
||||
version = "2025.4.0"
|
||||
version = "2025.4.2"
|
||||
description = ""
|
||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||
requires-python = "==3.12.*"
|
||||
|
@ -56,6 +56,7 @@ HEALTHCHECK --interval=5s --retries=20 --start-period=3s CMD [ "/rac", "healthch
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV GOFIPS=1
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/rac"]
|
||||
|
@ -56,6 +56,7 @@ EXPOSE 1812/udp 9300
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV GOFIPS=1
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
GOFIPS=1
|
||||
|
||||
ENTRYPOINT ["/radius"]
|
||||
|
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2025.4.0
|
||||
version: 2025.4.2
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
|
@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @file Generates the authentik API client for Python.
|
||||
*/
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { generateOpenAPIClient } from "./openapi-generator.mjs";
|
||||
|
||||
const scriptDirectory = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const repoRoot = resolve(scriptDirectory, "..");
|
||||
|
||||
generateOpenAPIClient({
|
||||
cwd: repoRoot,
|
||||
outputDirectory: resolve(repoRoot, "gen-py-api"),
|
||||
generatorName: "python",
|
||||
config: resolve(scriptDirectory, "api-py-config.yaml"),
|
||||
});
|
@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @file Generates the authentik API client for TypeScript.
|
||||
*/
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import PackageJSON from "../package.json" with { type: "json" };
|
||||
import { generateOpenAPIClient } from "./openapi-generator.mjs";
|
||||
|
||||
const scriptDirectory = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const repoRoot = resolve(scriptDirectory, "..");
|
||||
const npmVersion = [PackageJSON.version, Date.now()].join("-");
|
||||
|
||||
generateOpenAPIClient({
|
||||
cwd: repoRoot,
|
||||
outputDirectory: resolve(repoRoot, "gen-ts-api"),
|
||||
generatorName: "typescript-fetch",
|
||||
config: resolve(scriptDirectory, "api-ts-config.yaml"),
|
||||
commandArgs: [`--additional-properties=npmVersion=${npmVersion}`],
|
||||
});
|
15
scripts/generate_semver.py
Executable file
15
scripts/generate_semver.py
Executable file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generates a Semantic Versioning identifier, suffixed with a timestamp.
|
||||
"""
|
||||
|
||||
from time import time
|
||||
|
||||
from authentik import __version__ as package_version
|
||||
|
||||
"""
|
||||
See: https://semver.org/#spec-item-9 (Pre-release spec)
|
||||
"""
|
||||
pre_release_timestamp = int(time())
|
||||
|
||||
print(f"{package_version}-{pre_release_timestamp}")
|
@ -1,100 +0,0 @@
|
||||
/**
|
||||
* @file OpenAPI generator utilities.
|
||||
*/
|
||||
import { execFileSync, execSync } from "node:child_process";
|
||||
import { existsSync, rmSync } from "node:fs";
|
||||
import { userInfo } from "node:os";
|
||||
import { join, relative, resolve } from "node:path";
|
||||
|
||||
const OPENAPI_CONTAINER_IMAGE = "docker.io/openapitools/openapi-generator-cli:v7.11.0";
|
||||
|
||||
/**
|
||||
* Checks if a command exists in the PATH.
|
||||
*
|
||||
* @template {string} T
|
||||
* @param {T} command
|
||||
* @returns {T | null}
|
||||
*/
|
||||
function commandExists(command) {
|
||||
if (execSync(`command -v ${command} || echo ''`).toString().trim()) {
|
||||
return command;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path relative to the current working directory,
|
||||
* resolves it to a path relative to the local volume.
|
||||
*
|
||||
* @param {string} cwd
|
||||
* @param {...string} pathSegments
|
||||
*/
|
||||
function resolveLocalPath(cwd, ...pathSegments) {
|
||||
return resolve("/local", relative(cwd, join(...pathSegments)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} GenerateOpenAPIClientOptions
|
||||
* @property {string} cwd The working directory to run the generator in.
|
||||
* @property {string} outputDirectory The path to the output directory.
|
||||
* @property {string} generatorName The name of the generator.
|
||||
* @property {string} config The path to the generator configuration.
|
||||
* @property {string} [inputSpec] The path to the OpenAPI specification.
|
||||
* @property {Array<string | string[]>} [commandArgs] Additional arguments to pass to the generator.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generates an OpenAPI client using the `openapi-generator-cli` Docker image.
|
||||
*
|
||||
* @param {GenerateOpenAPIClientOptions} options
|
||||
* @see {@link https://openapi-generator.tech/docs/usage}
|
||||
*/
|
||||
export function generateOpenAPIClient({
|
||||
cwd,
|
||||
outputDirectory,
|
||||
generatorName,
|
||||
config,
|
||||
inputSpec = resolve(cwd, "schema.yml"),
|
||||
commandArgs = [],
|
||||
}) {
|
||||
if (existsSync(outputDirectory)) {
|
||||
console.log(`Removing existing generated API client from ${outputDirectory}`);
|
||||
|
||||
rmSync(outputDirectory, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
const containerEngine = commandExists("docker") || commandExists("podman");
|
||||
|
||||
if (!containerEngine) {
|
||||
throw new Error("Container engine not found. Is Docker or Podman available in the PATH?");
|
||||
}
|
||||
|
||||
const { gid, uid } = userInfo();
|
||||
|
||||
const args = [
|
||||
"run",
|
||||
[`--user`, `${uid}:${gid}`],
|
||||
`--rm`,
|
||||
[`-v`, `${cwd}:/local`],
|
||||
OPENAPI_CONTAINER_IMAGE,
|
||||
"generate",
|
||||
["--input-spec", resolveLocalPath(cwd, inputSpec)],
|
||||
[`--generator-name`, generatorName],
|
||||
["--config", resolveLocalPath(cwd, config)],
|
||||
["--git-repo-id", `authentik`],
|
||||
["--git-user-id", `goauthentik`],
|
||||
["--output", resolveLocalPath(cwd, outputDirectory)],
|
||||
|
||||
...commandArgs,
|
||||
];
|
||||
|
||||
console.debug(`Running command: ${containerEngine}`, args);
|
||||
|
||||
execFileSync(containerEngine, args.flat(), {
|
||||
cwd,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
console.log(`Generated API client to ${outputDirectory}`);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
// TypeScript Project Configuration
|
||||
{
|
||||
"extends": "./packages/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
},
|
||||
"watchOptions": {
|
||||
"excludeDirectories": [
|
||||
"**/.git", // Git
|
||||
"**/.yarn", // Yarn
|
||||
"**/.vscode", // VS Code
|
||||
"**/.vscode-test-web", // VS Code Web Test
|
||||
"**/dist", // Distributed build files
|
||||
"**/out", // Output build files
|
||||
"**/.drafts", // Drafts
|
||||
"**/.github", // GitHub
|
||||
"**/node_modules" // Node modules
|
||||
]
|
||||
},
|
||||
|
||||
// The root project has no sources of its own. By setting `files` to an empty
|
||||
// list, TS won't automatically include all sources below root (the default).
|
||||
"files": [],
|
||||
"references": [
|
||||
// Note that references are in the order we want them to be built.
|
||||
// TODO: Left blank until TypeScript workspaces are complete.
|
||||
]
|
||||
}
|
168
uv.lock
generated
168
uv.lock
generated
@ -13,7 +13,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.11.18"
|
||||
version = "3.11.16"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohappyeyeballs" },
|
||||
@ -24,24 +24,24 @@ dependencies = [
|
||||
{ name = "propcache" },
|
||||
{ name = "yarl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/e7/fa1a8c00e2c54b05dc8cb5d1439f627f7c267874e3f7bb047146116020f9/aiohttp-3.11.18.tar.gz", hash = "sha256:ae856e1138612b7e412db63b7708735cff4d38d0399f6a5435d3dac2669f558a", size = 7678653 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/d9/1c4721d143e14af753f2bf5e3b681883e1f24b592c0482df6fa6e33597fa/aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8", size = 7676826 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/d2/5bc436f42bf4745c55f33e1e6a2d69e77075d3e768e3d1a34f96ee5298aa/aiohttp-3.11.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63d71eceb9cad35d47d71f78edac41fcd01ff10cacaa64e473d1aec13fa02df2", size = 706671 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/d0/2dbabecc4e078c0474abb40536bbde717fb2e39962f41c5fc7a216b18ea7/aiohttp-3.11.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1929da615840969929e8878d7951b31afe0bac883d84418f92e5755d7b49508", size = 466169 },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/84/19edcf0b22933932faa6e0be0d933a27bd173da02dc125b7354dff4d8da4/aiohttp-3.11.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d0aebeb2392f19b184e3fdd9e651b0e39cd0f195cdb93328bd124a1d455cd0e", size = 457554 },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/d0/e8d1f034ae5624a0f21e4fb3feff79342ce631f3a4d26bd3e58b31ef033b/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3849ead845e8444f7331c284132ab314b4dac43bfae1e3cf350906d4fff4620f", size = 1690154 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/de/2f9dbe2ac6f38f8495562077131888e0d2897e3798a0ff3adda766b04a34/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e8452ad6b2863709f8b3d615955aa0807bc093c34b8e25b3b52097fe421cb7f", size = 1733402 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/04/bd2870e1e9aef990d14b6df2a695f17807baf5c85a4c187a492bda569571/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b8d2b42073611c860a37f718b3d61ae8b4c2b124b2e776e2c10619d920350ec", size = 1783958 },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/06/4203ffa2beb5bedb07f0da0f79b7d9039d1c33f522e0d1a2d5b6218e6f2e/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fbf91f6a0ac317c0a07eb328a1384941872f6761f2e6f7208b63c4cc0a7ff6", size = 1695288 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/b2/e2285dda065d9f29ab4b23d8bcc81eb881db512afb38a3f5247b191be36c/aiohttp-3.11.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ff5625413fec55216da5eaa011cf6b0a2ed67a565914a212a51aa3755b0009", size = 1618871 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/e0/88f2987885d4b646de2036f7296ebea9268fdbf27476da551c1a7c158bc0/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f33a92a2fde08e8c6b0c61815521324fc1612f397abf96eed86b8e31618fdb4", size = 1646262 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/19/4d2da508b4c587e7472a032290b2981f7caeca82b4354e19ab3df2f51d56/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:11d5391946605f445ddafda5eab11caf310f90cdda1fd99865564e3164f5cff9", size = 1677431 },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/ae/047473ea50150a41440f3265f53db1738870b5a1e5406ece561ca61a3bf4/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3cc314245deb311364884e44242e00c18b5896e4fe6d5f942e7ad7e4cb640adb", size = 1637430 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/32/c6d1e3748077ce7ee13745fae33e5cb1dac3e3b8f8787bf738a93c94a7d2/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f421843b0f70740772228b9e8093289924359d306530bcd3926f39acbe1adda", size = 1703342 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/1d/a3b57bfdbe285f0d45572d6d8f534fd58761da3e9cbc3098372565005606/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e220e7562467dc8d589e31c1acd13438d82c03d7f385c9cd41a3f6d1d15807c1", size = 1740600 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/71/f9cd2fed33fa2b7ce4d412fb7876547abb821d5b5520787d159d0748321d/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab2ef72f8605046115bc9aa8e9d14fd49086d405855f40b79ed9e5c1f9f4faea", size = 1695131 },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/97/d1248cd6d02b9de6aa514793d0dcb20099f0ec47ae71a933290116c070c5/aiohttp-3.11.18-cp312-cp312-win32.whl", hash = "sha256:12a62691eb5aac58d65200c7ae94d73e8a65c331c3a86a2e9670927e94339ee8", size = 412442 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/9a/e34e65506e06427b111e19218a99abf627638a9703f4b8bcc3e3021277ed/aiohttp-3.11.18-cp312-cp312-win_amd64.whl", hash = "sha256:364329f319c499128fd5cd2d1c31c44f234c58f9b96cc57f743d16ec4f3238c8", size = 439444 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/38/100d01cbc60553743baf0fba658cb125f8ad674a8a771f765cdc155a890d/aiohttp-3.11.16-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:911a6e91d08bb2c72938bc17f0a2d97864c531536b7832abee6429d5296e5b27", size = 704881 },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/ed/b4102bb6245e36591209e29f03fe87e7956e54cb604ee12e20f7eb47f994/aiohttp-3.11.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac13b71761e49d5f9e4d05d33683bbafef753e876e8e5a7ef26e937dd766713", size = 464564 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/e1/a9ab6c47b62ecee080eeb33acd5352b40ecad08fb2d0779bcc6739271745/aiohttp-3.11.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd36c119c5d6551bce374fcb5c19269638f8d09862445f85a5a48596fd59f4bb", size = 456548 },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/ad/216c6f71bdff2becce6c8776f0aa32cb0fa5d83008d13b49c3208d2e4016/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d489d9778522fbd0f8d6a5c6e48e3514f11be81cb0a5954bdda06f7e1594b321", size = 1691749 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/ea/7df7bcd3f4e734301605f686ffc87993f2d51b7acb6bcc9b980af223f297/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69a2cbd61788d26f8f1e626e188044834f37f6ae3f937bd9f08b65fc9d7e514e", size = 1736874 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/41/c7724b9c87a29b7cfd1202ec6446bae8524a751473d25e2ff438bc9a02bf/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd464ba806e27ee24a91362ba3621bfc39dbbb8b79f2e1340201615197370f7c", size = 1786885 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/b3/f61f8492fa6569fa87927ad35a40c159408862f7e8e70deaaead349e2fba/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce63ae04719513dd2651202352a2beb9f67f55cb8490c40f056cea3c5c355ce", size = 1698059 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/be/7097cf860a9ce8bbb0e8960704e12869e111abcd3fbd245153373079ccec/aiohttp-3.11.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b00dd520d88eac9d1768439a59ab3d145065c91a8fab97f900d1b5f802895e", size = 1626527 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/1d/aaa841c340e8c143a8d53a1f644c2a2961c58cfa26e7b398d6bf75cf5d23/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f6428fee52d2bcf96a8aa7b62095b190ee341ab0e6b1bcf50c615d7966fd45b", size = 1644036 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/88/59d870f76e9345e2b149f158074e78db457985c2b4da713038d9da3020a8/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13ceac2c5cdcc3f64b9015710221ddf81c900c5febc505dbd8f810e770011540", size = 1685270 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b1/c6686948d4c79c3745595efc469a9f8a43cab3c7efc0b5991be65d9e8cb8/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fadbb8f1d4140825069db3fedbbb843290fd5f5bc0a5dbd7eaf81d91bf1b003b", size = 1650852 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/94/3e42a6916fd3441721941e0f1b8438e1ce2a4c49af0e28e0d3c950c9b3c9/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6a792ce34b999fbe04a7a71a90c74f10c57ae4c51f65461a411faa70e154154e", size = 1704481 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/6d/6ab5854ff59b27075c7a8c610597d2b6c38945f9a1284ee8758bc3720ff6/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f4065145bf69de124accdd17ea5f4dc770da0a6a6e440c53f6e0a8c27b3e635c", size = 1735370 },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/2a/08a68eec3c99a6659067d271d7553e4d490a0828d588e1daa3970dc2b771/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa73e8c2656a3653ae6c307b3f4e878a21f87859a9afab228280ddccd7369d71", size = 1697619 },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/d5/fea8dbbfb0cd68fbb56f0ae913270a79422d9a41da442a624febf72d2aaf/aiohttp-3.11.16-cp312-cp312-win32.whl", hash = "sha256:f244b8e541f414664889e2c87cac11a07b918cb4b540c36f7ada7bfa76571ea2", size = 411710 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/fb/41cde15fbe51365024550bf77b95a4fc84ef41365705c946da0421f0e1e0/aiohttp-3.11.16-cp312-cp312-win_amd64.whl", hash = "sha256:23a15727fbfccab973343b6d1b7181bfb0b4aa7ae280f36fd2f90f5476805682", size = 438012 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -165,7 +165,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "authentik"
|
||||
version = "2025.4.0"
|
||||
version = "2025.4.2"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "argon2-cffi" },
|
||||
@ -558,30 +558,30 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.37.38"
|
||||
version = "1.37.35"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "botocore" },
|
||||
{ name = "jmespath" },
|
||||
{ name = "s3transfer" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0d/b5/d1c2e8c484cea43891629bbab6ca90ce9ca932586750bc0e786c8f096ccf/boto3-1.37.38.tar.gz", hash = "sha256:88c02910933ab7777597d1ca7c62375f52822e0aa1a8e0c51b2598a547af42b2", size = 111623 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/48/5f/e356ecd2f236e6ddc7711eaf3f075c15b13e2d044cfdb47719d49c4ae7dd/boto3-1.37.35.tar.gz", hash = "sha256:751ed599c8fd9ca24896edcd6620e8a32b3db1b68efea3a90126312240e668a2", size = 111640 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/87/8189f22ee798177bc7b40afd13f046442c5f91b699e70a950b42ff447e80/boto3-1.37.38-py3-none-any.whl", hash = "sha256:b6d42803607148804dff82389757827a24ce9271f0583748853934c86310999f", size = 139922 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/e4/00958f65ac74ab0a76af33f16c8fdf5726a5c6f0d3c0d0c058ff0dd00fd7/boto3-1.37.35-py3-none-any.whl", hash = "sha256:5a90d674830adbaf86456d6b27a18f5f11378277da5286511fa860d2e7b14261", size = 139922 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.37.38"
|
||||
version = "1.37.35"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jmespath" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/34/79/4e072e614339727f79afef704e5993b5b4d2667c1671c757cc4deb954744/botocore-1.37.38.tar.gz", hash = "sha256:c3ea386177171f2259b284db6afc971c959ec103fa2115911c4368bea7cbbc5d", size = 13832365 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/64/0b/d281d74d53f7d4733402aed7a536275084fa344a2672f7ea4dbc8ebe1f1b/botocore-1.37.35.tar.gz", hash = "sha256:197a9bf8251c45b9d882c405ec0d0ab40c10e2d2a55ee66960185daec4beb6ec", size = 13821053 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/55/1b/93f3504afc7c523dcaa8a8147cfc75421983e30b08d9f93a533929589630/botocore-1.37.38-py3-none-any.whl", hash = "sha256:23b4097780e156a4dcaadfc1ed156ce25cb95b6087d010c4bb7f7f5d9bc9d219", size = 13499391 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/00/bf9c894f5af8e35b06ecf757d4a95883408e71c48642dc7f8760580584fd/botocore-1.37.35-py3-none-any.whl", hash = "sha256:50839212e90650d0b0fa6b8f7514876bf802f6164f2775f3abcd4d53c98bb73c", size = 13485892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1279,28 +1279,26 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "frozenlist"
|
||||
version = "1.6.0"
|
||||
version = "1.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/f4/d744cba2da59b5c1d88823cf9e8a6c74e4659e2b27604ed973be2a0bf5ab/frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68", size = 42831 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/8a/289b7d0de2fbac832ea80944d809759976f661557a38bb8e77db5d9f79b7/frozenlist-1.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5b9e42ace7d95bf41e19b87cec8f262c41d3510d8ad7514ab3862ea2197bfb1", size = 160193 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/80/2fd17d322aec7f430549f0669f599997174f93ee17929ea5b92781ec902c/frozenlist-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca9973735ce9f770d24d5484dcb42f68f135351c2fc81a7a9369e48cf2998a29", size = 123831 },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/06/f5812da431273f78c6543e0b2f7de67dfd65eb0a433978b2c9c63d2205e4/frozenlist-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ac40ec76041c67b928ca8aaffba15c2b2ee3f5ae8d0cb0617b5e63ec119ca25", size = 121862 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/31/9e61c6b5fc493cf24d54881731204d27105234d09878be1a5983182cc4a5/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b7a8a3180dfb280eb044fdec562f9b461614c0ef21669aea6f1d3dac6ee576", size = 316361 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/55/22ca9362d4f0222324981470fd50192be200154d51509ee6eb9baa148e96/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c444d824e22da6c9291886d80c7d00c444981a72686e2b59d38b285617cb52c8", size = 307115 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/39/4fff42920a57794881e7bb3898dc7f5f539261711ea411b43bba3cde8b79/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb52c8166499a8150bfd38478248572c924c003cbb45fe3bcd348e5ac7c000f9", size = 322505 },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/f2/88c41f374c1e4cf0092a5459e5f3d6a1e17ed274c98087a76487783df90c/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b35298b2db9c2468106278537ee529719228950a5fdda686582f68f247d1dc6e", size = 322666 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/51/034eeb75afdf3fd03997856195b500722c0b1a50716664cde64e28299c4b/frozenlist-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d108e2d070034f9d57210f22fefd22ea0d04609fc97c5f7f5a686b3471028590", size = 302119 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/a6/564ecde55ee633270a793999ef4fd1d2c2b32b5a7eec903b1012cb7c5143/frozenlist-1.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1be9111cb6756868ac242b3c2bd1f09d9aea09846e4f5c23715e7afb647103", size = 316226 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/c8/6c0682c32377f402b8a6174fb16378b683cf6379ab4d2827c580892ab3c7/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:94bb451c664415f02f07eef4ece976a2c65dcbab9c2f1705b7031a3a75349d8c", size = 312788 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/b8/10fbec38f82c5d163ca1750bfff4ede69713badf236a016781cf1f10a0f0/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d1a686d0b0949182b8faddea596f3fc11f44768d1f74d4cad70213b2e139d821", size = 325914 },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/ca/2bf4f3a1bd40cdedd301e6ecfdbb291080d5afc5f9ce350c0739f773d6b9/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea8e59105d802c5a38bdbe7362822c522230b3faba2aa35c0fa1765239b7dd70", size = 305283 },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/64/20cc13ccf94abc2a1f482f74ad210703dc78a590d0b805af1c9aa67f76f9/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:abc4e880a9b920bc5020bf6a431a6bb40589d9bca3975c980495f63632e8382f", size = 319264 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/ff/86c6a2bbe98cfc231519f5e6d712a0898488ceac804a917ce014f32e68f6/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a79713adfe28830f27a3c62f6b5406c37376c892b05ae070906f07ae4487046", size = 326482 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/da/8e381f66367d79adca245d1d71527aac774e30e291d41ef161ce2d80c38e/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0318c2068e217a8f5e3b85e35899f5a19e97141a45bb925bb357cfe1daf770", size = 318248 },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/24/1a1976563fb476ab6f0fa9fefaac7616a4361dbe0461324f9fd7bf425dbe/frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc", size = 115161 },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/2e/fb4ed62a65f8cd66044706b1013f0010930d8cbb0729a2219561ea075434/frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878", size = 120548 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/3e/b04a0adda73bd52b390d730071c0d577073d3d26740ee1bad25c3ad0f37b/frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191", size = 12404 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1438,11 +1436,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1469,15 +1467,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/8d/f052b1e336bb2c1fc7ed1aaed898aa570c0b61a09707b108979d9fc6e308/httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be", size = 78732 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2160,11 +2158,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
version = "24.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2204,16 +2202,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pdoc"
|
||||
version = "15.0.3"
|
||||
version = "15.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jinja2" },
|
||||
{ name = "markupsafe" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/e9/66ab0fc39276a1818dea6302858ec9558964d8d9f1c90dd1facfe395d216/pdoc-15.0.3.tar.gz", hash = "sha256:6482d8ebbd40185fea5e6aec2f1592f4be92e93cf6bf70b9e2a00378bbaf3252", size = 155384 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/16/1b542af6f18a27de059f722c487a596681127897b6d31f78e46d6e5bf2fe/pdoc-15.0.1.tar.gz", hash = "sha256:3b08382c9d312243ee6c2a1813d0ff517a6ab84d596fa2c6c6b5255b17c3d666", size = 154174 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/37/bc3189471c63c84e15f7dc42d4b712747e9662ffbcfacfc4b6a93e6c3bc6/pdoc-15.0.3-py3-none-any.whl", hash = "sha256:686c921ef2622f166de5f73b7241935a4ddac79c8d10dbfa43def8c1fca86550", size = 145950 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/4d/60d856a1b12fbf6ac1539efccfa138e57c6b88675c9867d84bbb46455cc1/pdoc-15.0.1-py3-none-any.whl", hash = "sha256:fd437ab8eb55f9b942226af7865a3801e2fb731665199b74fd9a44737dbe20f9", size = 144186 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2773,39 +2771,39 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.6"
|
||||
version = "0.11.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/11/bcef6784c7e5d200b8a1f5c2ddf53e5da0efec37e6e5a44d163fb97e04ba/ruff-0.11.6.tar.gz", hash = "sha256:bec8bcc3ac228a45ccc811e45f7eb61b950dbf4cf31a67fa89352574b01c7d79", size = 4010053 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/45/71/5759b2a6b2279bb77fe15b1435b89473631c2cd6374d45ccdb6b785810be/ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef", size = 3976488 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/1f/8848b625100ebcc8740c8bac5b5dd8ba97dd4ee210970e98832092c1635b/ruff-0.11.6-py3-none-linux_armv6l.whl", hash = "sha256:d84dcbe74cf9356d1bdb4a78cf74fd47c740bf7bdeb7529068f69b08272239a1", size = 10248105 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/47/c44036e70c6cc11e6ee24399c2a1e1f1e99be5152bd7dff0190e4b325b76/ruff-0.11.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9bc583628e1096148011a5d51ff3c836f51899e61112e03e5f2b1573a9b726de", size = 11001494 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/5b/170444061650202d84d316e8f112de02d092bff71fafe060d3542f5bc5df/ruff-0.11.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2959049faeb5ba5e3b378709e9d1bf0cab06528b306b9dd6ebd2a312127964a", size = 10352151 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/91/f02839fb3787c678e112c8865f2c3e87cfe1744dcc96ff9fc56cfb97dda2/ruff-0.11.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63c5d4e30d9d0de7fedbfb3e9e20d134b73a30c1e74b596f40f0629d5c28a193", size = 10541951 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/f3/c09933306096ff7a08abede3cc2534d6fcf5529ccd26504c16bf363989b5/ruff-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4b9a4e1439f7d0a091c6763a100cef8fbdc10d68593df6f3cfa5abdd9246e", size = 10079195 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/0d/a87f8933fccbc0d8c653cfbf44bedda69c9582ba09210a309c066794e2ee/ruff-0.11.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5edf270223dd622218256569636dc3e708c2cb989242262fe378609eccf1308", size = 11698918 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/7d/8eac0bd083ea8a0b55b7e4628428203441ca68cd55e0b67c135a4bc6e309/ruff-0.11.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f55844e818206a9dd31ff27f91385afb538067e2dc0beb05f82c293ab84f7d55", size = 12319426 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/dc/d0c17d875662d0c86fadcf4ca014ab2001f867621b793d5d7eef01b9dcce/ruff-0.11.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d8f782286c5ff562e4e00344f954b9320026d8e3fae2ba9e6948443fafd9ffc", size = 11791012 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/f3/81a1aea17f1065449a72509fc7ccc3659cf93148b136ff2a8291c4bc3ef1/ruff-0.11.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01c63ba219514271cee955cd0adc26a4083df1956d57847978383b0e50ffd7d2", size = 13949947 },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/9f/a3e34de425a668284e7024ee6fd41f452f6fa9d817f1f3495b46e5e3a407/ruff-0.11.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15adac20ef2ca296dd3d8e2bedc6202ea6de81c091a74661c3666e5c4c223ff6", size = 11471753 },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/c5/4a57a86d12542c0f6e2744f262257b2aa5a3783098ec14e40f3e4b3a354a/ruff-0.11.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4dd6b09e98144ad7aec026f5588e493c65057d1b387dd937d7787baa531d9bc2", size = 10417121 },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/3f/a3b4346dff07ef5b862e2ba06d98fcbf71f66f04cf01d375e871382b5e4b/ruff-0.11.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:45b2e1d6c0eed89c248d024ea95074d0e09988d8e7b1dad8d3ab9a67017a5b03", size = 10073829 },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/cc/7ed02e0b86a649216b845b3ac66ed55d8aa86f5898c5f1691797f408fcb9/ruff-0.11.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd40de4115b2ec4850302f1a1d8067f42e70b4990b68838ccb9ccd9f110c5e8b", size = 11076108 },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/5e/5b09840fef0eff1a6fa1dea6296c07d09c17cb6fb94ed5593aa591b50460/ruff-0.11.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:77cda2dfbac1ab73aef5e514c4cbfc4ec1fbef4b84a44c736cc26f61b3814cd9", size = 11512366 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/4c/1cd5a84a412d3626335ae69f5f9de2bb554eea0faf46deb1f0cb48534042/ruff-0.11.6-py3-none-win32.whl", hash = "sha256:5151a871554be3036cd6e51d0ec6eef56334d74dfe1702de717a995ee3d5b287", size = 10485900 },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/46/8997872bc44d43df986491c18d4418f1caff03bc47b7f381261d62c23442/ruff-0.11.6-py3-none-win_amd64.whl", hash = "sha256:cce85721d09c51f3b782c331b0abd07e9d7d5f775840379c640606d3159cae0e", size = 11558592 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/6a/65fecd51a9ca19e1477c3879a7fda24f8904174d1275b419422ac00f6eee/ruff-0.11.6-py3-none-win_arm64.whl", hash = "sha256:3567ba0d07fb170b1b48d944715e3294b77f5b7679e8ba258199a250383ccb79", size = 10682766 },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/db/6efda6381778eec7f35875b5cbefd194904832a1153d68d36d6b269d81a8/ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b", size = 10103150 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/f2/06cd9006077a8db61956768bc200a8e52515bf33a8f9b671ee527bb10d77/ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077", size = 10898637 },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/f5/af390a013c56022fe6f72b95c86eb7b2585c89cc25d63882d3bfe411ecf1/ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779", size = 10236012 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/ca/b9bf954cfed165e1a0c24b86305d5c8ea75def256707f2448439ac5e0d8b/ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794", size = 10415338 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/4d/2522dde4e790f1b59885283f8786ab0046958dfd39959c81acc75d347467/ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038", size = 9965277 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/7a/749f56f150eef71ce2f626a2f6988446c620af2f9ba2a7804295ca450397/ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f", size = 11541614 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/b2/7d9b8435222485b6aac627d9c29793ba89be40b5de11584ca604b829e960/ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82", size = 12198873 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/e0/a1a69ef5ffb5c5f9c31554b27e030a9c468fc6f57055886d27d316dfbabd/ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304", size = 11670190 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/61/c1c16df6e92975072c07f8b20dad35cd858e8462b8865bc856fe5d6ccb63/ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470", size = 13902301 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/89/0af10c8af4363304fd8cb833bd407a2850c760b71edf742c18d5a87bb3ad/ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a", size = 11350132 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/e1/ecb4c687cbf15164dd00e38cf62cbab238cad05dd8b6b0fc68b0c2785e15/ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b", size = 10312937 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/4f/0e53fe5e500b65934500949361e3cd290c5ba60f0324ed59d15f46479c06/ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a", size = 9936683 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/a8/8183c4da6d35794ae7f76f96261ef5960853cd3f899c2671961f97a27d8e/ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159", size = 10950217 },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/88/9b85a5a8af21e46a0639b107fcf9bfc31da4f1d263f2fc7fbe7199b47f0a/ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783", size = 11404521 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/52/047f35d3b20fd1ae9ccfe28791ef0f3ca0ef0b3e6c1a58badd97d450131b/ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe", size = 10320697 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/fe/00c78010e3332a6e92762424cf4c1919065707e962232797d0b57fd8267e/ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800", size = 11378665 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/7c/c83fe5cbb70ff017612ff36654edfebec4b1ef79b558b8e5fd933bab836b/ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e", size = 10460287 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "s3transfer"
|
||||
version = "0.11.5"
|
||||
version = "0.11.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "botocore" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/2b/5c9562795c2eb2b5f63536961754760c25bf0f34af93d36aa28dea2fb303/s3transfer-0.11.5.tar.gz", hash = "sha256:8c8aad92784779ab8688a61aefff3e28e9ebdce43142808eaa3f0b0f402f68b7", size = 149107 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/ec/aa1a215e5c126fe5decbee2e107468f51d9ce190b9763cb649f76bb45938/s3transfer-0.11.4.tar.gz", hash = "sha256:559f161658e1cf0a911f45940552c696735f5c74e64362e515f333ebed87d679", size = 148419 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/39/13402e323666d17850eca87e4cd6ecfcf9fd7809cac9efdcce10272fc29d/s3transfer-0.11.5-py3-none-any.whl", hash = "sha256:757af0f2ac150d3c75bc4177a32355c3862a98d20447b69a0161812992fe0bd4", size = 84782 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/62/8d3fc3ec6640161a5649b2cddbbf2b9fa39c92541225b33f117c37c5a2eb/s3transfer-0.11.4-py3-none-any.whl", hash = "sha256:ac265fa68318763a03bf2dc4f39d5cbd6a9e178d81cc9483ad27da33637e320d", size = 84412 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2887,11 +2885,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "79.0.0"
|
||||
version = "78.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/19/fecb7e2825616270f34512b3394cdcf6f45a79b5b6d94fdbd86a509e67b5/setuptools-79.0.0.tar.gz", hash = "sha256:9828422e7541213b0aacb6e10bbf9dd8febeaa45a48570e09b6d100e063fc9f9", size = 1367685 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/ea/d53f2f8897c46a36df085964d07761ea4c2d1f2cf92019693b6742b7aabb/setuptools-79.0.0-py3-none-any.whl", hash = "sha256:b9ab3a104bedb292323f53797b00864e10e434a3ab3906813a7169e4745b912a", size = 1256065 },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3016,7 +3014,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "trio"
|
||||
version = "0.30.0"
|
||||
version = "0.29.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
@ -3026,9 +3024,9 @@ dependencies = [
|
||||
{ name = "sniffio" },
|
||||
{ name = "sortedcontainers" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/c1/68d582b4d3a1c1f8118e18042464bb12a7c1b75d64d75111b297687041e3/trio-0.30.0.tar.gz", hash = "sha256:0781c857c0c81f8f51e0089929a26b5bb63d57f927728a5586f7e36171f064df", size = 593776 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/47/f62e62a1a6f37909aed0bf8f5d5411e06fa03846cfcb64540cd1180ccc9f/trio-0.29.0.tar.gz", hash = "sha256:ea0d3967159fc130acb6939a0be0e558e364fee26b5deeecc893a6b08c361bdf", size = 588952 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/8e/3f6dfda475ecd940e786defe6df6c500734e686c9cd0a0f8ef6821e9b2f2/trio-0.30.0-py3-none-any.whl", hash = "sha256:3bf4f06b8decf8d3cf00af85f40a89824669e2d033bb32469d34840edcfc22a5", size = 499194 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/55/c4d9bea8b3d7937901958f65124123512419ab0eb73695e5f382521abbfb/trio-0.29.0-py3-none-any.whl", hash = "sha256:d8c463f1a9cc776ff63e331aba44c125f423a5a13c684307e828d930e625ba66", size = 492920 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2,11 +2,15 @@
|
||||
node_modules
|
||||
# don't lint build output (make sure it's set to your correct build folder name)
|
||||
dist
|
||||
out
|
||||
# don't lint nyc coverage output
|
||||
coverage
|
||||
# Import order matters
|
||||
poly.ts
|
||||
src/locale-codes.ts
|
||||
src/locales/
|
||||
storybook-static/
|
||||
# Prettier breaks the tsconfig file
|
||||
tsconfig.json
|
||||
.storybook/css-import-maps*
|
||||
package.json
|
||||
packages/**/package.json
|
||||
|
846
web/package-lock.json
generated
846
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
115
web/package.json
115
web/package.json
@ -1,44 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/web",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "wireit",
|
||||
"build-locales": "wireit",
|
||||
"build-locales:build": "wireit",
|
||||
"build-proxy": "wireit",
|
||||
"build:sfe": "wireit",
|
||||
"esbuild:watch": "node scripts/build-web.mjs --watch",
|
||||
"extract-locales": "wireit",
|
||||
"format": "wireit",
|
||||
"lint": "wireit",
|
||||
"lint:imports": "wireit",
|
||||
"lint:lockfile": "wireit",
|
||||
"lint:nightmare": "wireit",
|
||||
"lint:precommit": "wireit",
|
||||
"lint:types": "wireit",
|
||||
"lit-analyse": "wireit",
|
||||
"postinstall": "bash scripts/patch-spotlight.sh",
|
||||
"precommit": "wireit",
|
||||
"prettier": "wireit",
|
||||
"prettier-check": "wireit",
|
||||
"pseudolocalize": "wireit",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:build": "wireit",
|
||||
"test": "wireit",
|
||||
"test:e2e": "wireit",
|
||||
"test:e2e:watch": "wireit",
|
||||
"test:watch": "wireit",
|
||||
"tsc": "wireit",
|
||||
"watch": "run-s build-locales esbuild:watch"
|
||||
},
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
"./paths": "./paths.js",
|
||||
"./scripts/*": "./scripts/*.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
@ -50,7 +12,8 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.5.7",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@goauthentik/api": "^2025.4.0-1746018955",
|
||||
"@goauthentik/api": "^2025.2.4-1745519715",
|
||||
"@lit-labs/ssr": "3.2.2",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
@ -91,7 +54,6 @@
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-mdx-frontmatter": "^5.0.0",
|
||||
"style-mod": "^4.1.2",
|
||||
"trusted-types": "^2.0.0",
|
||||
"ts-pattern": "^5.4.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"webcomponent-qr-code": "^1.2.0",
|
||||
@ -100,7 +62,6 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@goauthentik/esbuild-plugin-live-reload": "^1.0.4",
|
||||
"@goauthentik/monorepo": "^1.0.0",
|
||||
"@goauthentik/prettier-config": "^1.0.4",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@hcaptcha/types": "^1.0.4",
|
||||
@ -132,13 +93,13 @@
|
||||
"@wdio/spec-reporter": "^9.1.2",
|
||||
"chromedriver": "^131.0.1",
|
||||
"esbuild": "^0.25.0",
|
||||
"esbuild-plugin-copy": "^2.1.1",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"esbuild-plugins-node-modules-polyfill": "^1.7.0",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-plugin-lit": "^1.15.0",
|
||||
"eslint-plugin-wc": "^2.1.1",
|
||||
"github-slugger": "^2.0.0",
|
||||
"glob": "^11.0.0",
|
||||
"globals": "^15.10.0",
|
||||
"knip": "^5.30.6",
|
||||
"lit-analyzer": "^2.0.3",
|
||||
@ -149,6 +110,7 @@
|
||||
"rollup-plugin-postcss-lit": "^2.1.0",
|
||||
"storybook": "^8.3.4",
|
||||
"storybook-addon-mock": "^5.0.0",
|
||||
"syncpack": "^13.0.0",
|
||||
"turnstile-types": "^1.2.3",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript-eslint": "^8.8.0",
|
||||
@ -156,6 +118,10 @@
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wireit": "^0.14.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"@esbuild/darwin-arm64": "^0.24.0",
|
||||
"@esbuild/linux-amd64": "^0.18.11",
|
||||
@ -164,6 +130,48 @@
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.23.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.23.0"
|
||||
},
|
||||
"overrides": {
|
||||
"rapidoc": {
|
||||
"@apitools/openapi-parser@": "0.0.37"
|
||||
},
|
||||
"chromedriver": {
|
||||
"axios": "^1.8.4"
|
||||
}
|
||||
},
|
||||
"prettier": "@goauthentik/prettier-config",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "wireit",
|
||||
"build-locales": "wireit",
|
||||
"build-locales:build": "wireit",
|
||||
"build-proxy": "wireit",
|
||||
"build:sfe": "wireit",
|
||||
"esbuild:watch": "node scripts/build-web.mjs --watch",
|
||||
"extract-locales": "wireit",
|
||||
"format": "wireit",
|
||||
"lint": "wireit",
|
||||
"lint:imports": "wireit",
|
||||
"lint:lockfile": "wireit",
|
||||
"lint:nightmare": "wireit",
|
||||
"lint:package": "wireit",
|
||||
"lint:precommit": "wireit",
|
||||
"lint:types": "wireit",
|
||||
"lit-analyse": "wireit",
|
||||
"postinstall": "bash scripts/patch-spotlight.sh",
|
||||
"precommit": "wireit",
|
||||
"prettier": "wireit",
|
||||
"prettier-check": "wireit",
|
||||
"pseudolocalize": "wireit",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:build": "wireit",
|
||||
"test": "wireit",
|
||||
"test:e2e": "wireit",
|
||||
"test:e2e:watch": "wireit",
|
||||
"test:watch": "wireit",
|
||||
"tsc": "wireit",
|
||||
"watch": "run-s build-locales esbuild:watch"
|
||||
},
|
||||
"type": "module",
|
||||
"wireit": {
|
||||
"build": {
|
||||
"#comment": [
|
||||
@ -240,7 +248,10 @@
|
||||
"command": "lit-localize extract"
|
||||
},
|
||||
"format": {
|
||||
"command": "prettier --write ."
|
||||
"command": "prettier --write .",
|
||||
"dependencies": [
|
||||
"lint:package"
|
||||
]
|
||||
},
|
||||
"format:packages": {
|
||||
"dependencies": [
|
||||
@ -279,6 +290,9 @@
|
||||
"./packages/sfe:lint:lockfile"
|
||||
]
|
||||
},
|
||||
"lint:package": {
|
||||
"command": "syncpack format -i ' '"
|
||||
},
|
||||
"lint:nightmare": {
|
||||
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --nightmare",
|
||||
"env": {
|
||||
@ -309,6 +323,7 @@
|
||||
"lint:types",
|
||||
"lint:components",
|
||||
"lint:spelling",
|
||||
"lint:package",
|
||||
"lint:lockfile",
|
||||
"lint:lockfiles",
|
||||
"lint:precommit",
|
||||
@ -373,20 +388,8 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"workspaces": [
|
||||
".",
|
||||
"./packages/*"
|
||||
],
|
||||
"prettier": "@goauthentik/prettier-config",
|
||||
"overrides": {
|
||||
"rapidoc": {
|
||||
"@apitools/openapi-parser@": "0.0.37"
|
||||
},
|
||||
"chromedriver": {
|
||||
"axios": "^1.8.4"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,11 +1,22 @@
|
||||
{
|
||||
"name": "@goauthentik/esbuild-plugin-live-reload",
|
||||
"version": "1.0.4",
|
||||
"description": "ESBuild plugin to watch for file changes and trigger client-side reloads.",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"version": "1.0.4",
|
||||
"dependencies": {
|
||||
"find-free-ports": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@goauthentik/prettier-config": "^1.0.4",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@types/node": "^22.14.1",
|
||||
"esbuild": "^0.25.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
},
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
@ -21,33 +32,22 @@
|
||||
"import": "./plugin/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"find-free-ports": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@goauthentik/prettier-config": "^1.0.4",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@types/node": "^22.14.1",
|
||||
"esbuild": "^0.25.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"esbuild": "^0.25.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
},
|
||||
"types": "./out/index.d.ts",
|
||||
"files": [
|
||||
"./index.js",
|
||||
"client/**/*",
|
||||
"plugin/**/*",
|
||||
"out/**/*"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"peerDependencies": {
|
||||
"esbuild": "^0.25.0"
|
||||
},
|
||||
"prettier": "@goauthentik/prettier-config",
|
||||
"private": true,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"types": "./out/index.d.ts"
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @file Utility functions for building and copying files.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A source environment variable, which can be a string, number, boolean, null, or undefined.
|
||||
* @typedef {string | number | boolean | null | undefined} EnvironmentVariable
|
||||
*/
|
||||
|
||||
/**
|
||||
* A type helper for serializing environment variables.
|
||||
*
|
||||
* @template {EnvironmentVariable} T
|
||||
* @typedef {T extends string ? `"${T}"` : T} JSONify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Given an object of environment variables, returns a new object with the same keys and values, but
|
||||
* with the values serialized as strings.
|
||||
*
|
||||
* @template {Record<string, EnvironmentVariable>} EnvRecord
|
||||
* @template {string} [Prefix='process.env.']
|
||||
*
|
||||
* @param {EnvRecord} input
|
||||
* @param {Prefix} [prefix='process.env.']
|
||||
*
|
||||
* @returns {{[K in keyof EnvRecord as `${Prefix}${K}`]: JSONify<EnvRecord[K]>}}
|
||||
*/
|
||||
export function serializeEnvironmentVars(input, prefix = /** @type {Prefix} */ ("process.env.")) {
|
||||
/**
|
||||
* @type {Record<string, string>}
|
||||
*/
|
||||
const env = {};
|
||||
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
const namespaceKey = prefix + key;
|
||||
|
||||
env[namespaceKey] = JSON.stringify(value || "");
|
||||
}
|
||||
|
||||
return /** @type {any} */ (env);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
{
|
||||
"name": "@goauthentik/monorepo",
|
||||
"version": "1.0.0",
|
||||
"description": "Utilities for the authentik monorepo.",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"types": "./out/index.d.ts",
|
||||
"import": "./index.js"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@goauthentik/prettier-config": "^1.0.4",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@types/node": "^22.14.1",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
},
|
||||
"types": "./out/index.d.ts",
|
||||
"prettier": "@goauthentik/prettier-config"
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const relativeDirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* @typedef {'~authentik'} MonoRepoRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* The root of the authentik monorepo.
|
||||
*/
|
||||
// TODO: Revise when this package is moved to the monorepo's `packages/monorepo` directory.
|
||||
export const MonoRepoRoot = /** @type {MonoRepoRoot} */ (
|
||||
resolve(relativeDirname, "..", "..", "..")
|
||||
);
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
/**
|
||||
* Resolve a package name to its location in the monorepo to the single node_modules directory.
|
||||
* @param {string} packageName
|
||||
*
|
||||
* @returns {string} The resolved path to the package.
|
||||
* @throws {Error} If the package cannot be resolved.
|
||||
*/
|
||||
export function resolvePackage(packageName) {
|
||||
const relativePackageJSONPath = join(packageName, "package.json");
|
||||
|
||||
/** @type {string} */
|
||||
let absolutePackageJSONPath;
|
||||
|
||||
try {
|
||||
absolutePackageJSONPath = require.resolve(relativePackageJSONPath);
|
||||
} catch (cause) {
|
||||
const error = new Error(`Failed to resolve package "${packageName}"`);
|
||||
|
||||
error.cause = cause;
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return dirname(absolutePackageJSONPath);
|
||||
}
|
15
web/packages/monorepo/types/global.d.ts
vendored
15
web/packages/monorepo/types/global.d.ts
vendored
@ -1,15 +0,0 @@
|
||||
declare module "process" {
|
||||
global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
/**
|
||||
* An environment variable used to determine
|
||||
* whether Node.js is running in production mode.
|
||||
*
|
||||
* @see {@link https://nodejs.org/en/learn/getting-started/nodejs-the-difference-between-development-and-production | The difference between development and production}
|
||||
*/
|
||||
NODE_ENV?: "production" | "development";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -47,7 +47,16 @@ class SimpleFlowExecutor {
|
||||
return `${ak().api.base}api/v3/flows/executor/${this.flowSlug}/?query=${encodeURIComponent(window.location.search.substring(1))}`;
|
||||
}
|
||||
|
||||
loading() {
|
||||
this.container.innerHTML = `<div class="d-flex justify-content-center">
|
||||
<div class="spinner-border spinner-border-md" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.loading();
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: this.apiURL,
|
||||
|
78
web/paths.js
78
web/paths.js
@ -1,78 +0,0 @@
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const relativeDirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
//#region Base paths
|
||||
|
||||
/**
|
||||
* @typedef {'@goauthentik/web'} WebPackageIdentifier
|
||||
*/
|
||||
|
||||
/**
|
||||
* The root of the web package.
|
||||
*/
|
||||
export const PackageRoot = /** @type {WebPackageIdentifier} */ (resolve(relativeDirname));
|
||||
|
||||
/**
|
||||
* The name of the distribution directory.
|
||||
*/
|
||||
export const DistDirectoryName = "dist";
|
||||
|
||||
/**
|
||||
* Path to the web package's distribution directory.
|
||||
*
|
||||
* This is where the built files are located after running the build process.
|
||||
*/
|
||||
export const DistDirectory = /** @type {`${WebPackageIdentifier}/${DistDirectoryName}`} */ (
|
||||
resolve(relativeDirname, DistDirectoryName)
|
||||
);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Entry points
|
||||
|
||||
/**
|
||||
* @typedef {{ in: string, out: string }} EntryPointTarget
|
||||
*
|
||||
* ESBuild entrypoint target.
|
||||
* Matches the type defined in the ESBuild context.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Entry points available for building.
|
||||
*
|
||||
* @satisfies {Record<string, EntryPointTarget>}
|
||||
*/
|
||||
export const EntryPoint = /** @type {const} */ ({
|
||||
Admin: {
|
||||
in: resolve(PackageRoot, "src", "admin", "AdminInterface", "index.entrypoint.ts"),
|
||||
out: resolve(DistDirectory, "admin", "AdminInterface"),
|
||||
},
|
||||
User: {
|
||||
in: resolve(PackageRoot, "src", "user", "index.entrypoint.ts"),
|
||||
out: resolve(DistDirectory, "user", "UserInterface"),
|
||||
},
|
||||
Flow: {
|
||||
in: resolve(PackageRoot, "src", "flow", "index.entrypoint.ts"),
|
||||
out: resolve(DistDirectory, "flow", "FlowInterface"),
|
||||
},
|
||||
Standalone: {
|
||||
in: resolve(PackageRoot, "src", "standalone", "api-browser/index.entrypoint.ts"),
|
||||
out: resolve(DistDirectory, "standalone", "api-browser", "index"),
|
||||
},
|
||||
StandaloneLoading: {
|
||||
in: resolve(PackageRoot, "src", "standalone", "loading/index.entrypoint.ts"),
|
||||
out: resolve(DistDirectory, "standalone", "loading", "index"),
|
||||
},
|
||||
RAC: {
|
||||
in: resolve(PackageRoot, "src", "rac", "index.entrypoint.ts"),
|
||||
out: resolve(DistDirectory, "rac", "index"),
|
||||
},
|
||||
Polyfill: {
|
||||
in: resolve(PackageRoot, "src", "polyfill", "index.entrypoint.ts"),
|
||||
out: resolve(DistDirectory, "poly"),
|
||||
},
|
||||
});
|
||||
|
||||
//#endregion
|
@ -4,86 +4,138 @@
|
||||
* @import { BuildOptions } from "esbuild";
|
||||
*/
|
||||
import { liveReloadPlugin } from "@goauthentik/esbuild-plugin-live-reload/plugin";
|
||||
import {
|
||||
MonoRepoRoot,
|
||||
NodeEnvironment,
|
||||
readBuildIdentifier,
|
||||
resolvePackage,
|
||||
serializeEnvironmentVars,
|
||||
} from "@goauthentik/monorepo";
|
||||
import { DistDirectory, DistDirectoryName, EntryPoint, PackageRoot } from "@goauthentik/web/paths";
|
||||
import { execFileSync } from "child_process";
|
||||
import { deepmerge } from "deepmerge-ts";
|
||||
import esbuild from "esbuild";
|
||||
import copy from "esbuild-plugin-copy";
|
||||
import { polyfillNode } from "esbuild-plugin-polyfill-node";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { copyFileSync, mkdirSync, readFileSync, statSync } from "fs";
|
||||
import { globSync } from "glob";
|
||||
import * as path from "path";
|
||||
import { cwd } from "process";
|
||||
import process from "process";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs";
|
||||
|
||||
const logPrefix = "[Build]";
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
let authentikProjectRoot = path.join(__dirname, "..", "..");
|
||||
|
||||
const definitions = serializeEnvironmentVars({
|
||||
NODE_ENV: NodeEnvironment,
|
||||
CWD: process.cwd(),
|
||||
AK_API_BASE_PATH: process.env.AK_API_BASE_PATH,
|
||||
});
|
||||
try {
|
||||
// Use the package.json file in the root folder, as it has the current version information.
|
||||
authentikProjectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
||||
encoding: "utf8",
|
||||
}).replace("\n", "");
|
||||
} catch (_error) {
|
||||
// We probably don't have a .git folder, which could happen in container builds.
|
||||
}
|
||||
|
||||
const patternflyPath = resolvePackage("@patternfly/patternfly");
|
||||
const packageJSONPath = path.join(authentikProjectRoot, "./package.json");
|
||||
const rootPackage = JSON.parse(readFileSync(packageJSONPath, "utf8"));
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV || "development";
|
||||
const AK_API_BASE_PATH = process.env.AK_API_BASE_PATH || "";
|
||||
|
||||
const environmentVars = new Map([
|
||||
["NODE_ENV", NODE_ENV],
|
||||
["CWD", cwd()],
|
||||
["AK_API_BASE_PATH", AK_API_BASE_PATH],
|
||||
]);
|
||||
|
||||
const definitions = Object.fromEntries(
|
||||
Array.from(environmentVars).map(([key, value]) => {
|
||||
return [`process.env.${key}`, JSON.stringify(value)];
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @type {Readonly<BuildOptions>}
|
||||
* All is magic is just to make sure the assets are copied into the right places. This is a very
|
||||
* stripped down version of what the rollup-copy-plugin does, without any of the features we don't
|
||||
* use, and using globSync instead of globby since we already had globSync lying around thanks to
|
||||
* Typescript. If there's a third argument in an array entry, it's used to replace the internal path
|
||||
* before concatenating it all together as the destination target.
|
||||
* @type {Array<[string, string, string?]>}
|
||||
*/
|
||||
const assetsFileMappings = [
|
||||
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
|
||||
["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"],
|
||||
["src/common/styles/**", "."],
|
||||
["src/assets/images/**", "./assets/images"],
|
||||
["./icons/*", "./assets/icons"],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {string} filePath
|
||||
*/
|
||||
const isFile = (filePath) => statSync(filePath).isFile();
|
||||
|
||||
/**
|
||||
* @param {string} src Source file
|
||||
* @param {string} dest Destination folder
|
||||
* @param {string} [strip] Path to strip from the source file
|
||||
*/
|
||||
function nameCopyTarget(src, dest, strip) {
|
||||
const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base);
|
||||
return [src, target];
|
||||
}
|
||||
|
||||
for (const [source, rawdest, strip] of assetsFileMappings) {
|
||||
const matchedPaths = globSync(source);
|
||||
const dest = path.join("dist", rawdest);
|
||||
|
||||
const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip));
|
||||
|
||||
for (const [src, dest] of copyTargets) {
|
||||
if (isFile(src)) {
|
||||
mkdirSync(path.dirname(dest), { recursive: true });
|
||||
copyFileSync(src, dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {[source: string, destination: string]} EntryPoint
|
||||
*/
|
||||
|
||||
/**
|
||||
* This starts the definitions used for esbuild: Our targets, our arguments, the function for
|
||||
* running a build, and three options for building: watching, building, and building the proxy.
|
||||
* Ordered by largest to smallest interface to build even faster
|
||||
*
|
||||
* @type {EntryPoint[]}
|
||||
*/
|
||||
const entryPoints = [
|
||||
["admin/AdminInterface/AdminInterface.ts", "admin"],
|
||||
["user/UserInterface.ts", "user"],
|
||||
["flow/FlowInterface.ts", "flow"],
|
||||
["standalone/api-browser/index.ts", "standalone/api-browser"],
|
||||
["rac/index.ts", "rac"],
|
||||
["standalone/loading/index.ts", "standalone/loading"],
|
||||
["polyfill/poly.ts", "."],
|
||||
];
|
||||
|
||||
/**
|
||||
* @type {import("esbuild").BuildOptions}
|
||||
*/
|
||||
const BASE_ESBUILD_OPTIONS = {
|
||||
entryNames: `[dir]/[name]-${readBuildIdentifier()}`,
|
||||
chunkNames: "[dir]/chunks/[name]-[hash]",
|
||||
assetNames: "assets/[dir]/[name]-[hash]",
|
||||
publicPath: path.join("/static", DistDirectoryName),
|
||||
outdir: DistDirectory,
|
||||
bundle: true,
|
||||
write: true,
|
||||
sourcemap: true,
|
||||
minify: NodeEnvironment === "production",
|
||||
legalComments: "external",
|
||||
minify: NODE_ENV === "production",
|
||||
splitting: true,
|
||||
treeShaking: true,
|
||||
external: ["*.woff", "*.woff2"],
|
||||
tsconfig: path.resolve(PackageRoot, "tsconfig.build.json"),
|
||||
tsconfig: path.resolve(__dirname, "..", "tsconfig.build.json"),
|
||||
loader: {
|
||||
".css": "text",
|
||||
},
|
||||
plugins: [
|
||||
copy({
|
||||
assets: [
|
||||
{
|
||||
from: path.join(patternflyPath, "patternfly.min.css"),
|
||||
to: ".",
|
||||
},
|
||||
{
|
||||
from: path.join(patternflyPath, "assets", "**"),
|
||||
to: "./assets",
|
||||
},
|
||||
{
|
||||
from: path.resolve(PackageRoot, "src", "common", "styles", "**"),
|
||||
to: ".",
|
||||
},
|
||||
{
|
||||
from: path.resolve(PackageRoot, "src", "assets", "images", "**"),
|
||||
to: "./assets/images",
|
||||
},
|
||||
{
|
||||
from: path.resolve(PackageRoot, "icons", "*"),
|
||||
to: "./assets/icons",
|
||||
},
|
||||
],
|
||||
}),
|
||||
polyfillNode({
|
||||
polyfills: {
|
||||
path: true,
|
||||
},
|
||||
}),
|
||||
mdxPlugin({
|
||||
root: MonoRepoRoot,
|
||||
root: authentikProjectRoot,
|
||||
}),
|
||||
],
|
||||
define: definitions,
|
||||
@ -99,43 +151,69 @@ const BASE_ESBUILD_OPTIONS = {
|
||||
},
|
||||
};
|
||||
|
||||
async function cleanDistDirectory() {
|
||||
const timerLabel = `${logPrefix} ♻️ Cleaning previous builds...`;
|
||||
/**
|
||||
* Creates a version ID for the build.
|
||||
* @returns {string}
|
||||
*/
|
||||
function composeVersionID() {
|
||||
const { version } = rootPackage;
|
||||
const buildHash = process.env.GIT_BUILD_HASH;
|
||||
|
||||
console.time(timerLabel);
|
||||
if (buildHash) {
|
||||
return `${version}+${buildHash}`;
|
||||
}
|
||||
|
||||
await fs.rm(DistDirectory, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
await fs.mkdir(DistDirectory, {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
console.timeEnd(timerLabel);
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ESBuild options, extending the base options with the given overrides.
|
||||
* Build a single entry point.
|
||||
*
|
||||
* @param {BuildOptions} overrides
|
||||
* @returns {BuildOptions}
|
||||
* @param {EntryPoint} buildTarget
|
||||
* @param {Partial<esbuild.BuildOptions>} [overrides]
|
||||
* @throws {Error} on build failure
|
||||
*/
|
||||
export function createESBuildOptions(overrides) {
|
||||
/**
|
||||
* @type {BuildOptions}
|
||||
*/
|
||||
const mergedOptions = deepmerge(BASE_ESBUILD_OPTIONS, overrides);
|
||||
function createEntryPointOptions([source, dest], overrides = {}) {
|
||||
const outdir = path.join(__dirname, "..", "dist", dest);
|
||||
|
||||
return mergedOptions;
|
||||
/**
|
||||
* @type {esbuild.BuildOptions}
|
||||
*/
|
||||
|
||||
const entryPointConfig = {
|
||||
entryPoints: [`./src/${source}`],
|
||||
entryNames: `[dir]/[name]-${composeVersionID()}`,
|
||||
publicPath: path.join("/static", "dist", dest),
|
||||
outdir,
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {esbuild.BuildOptions}
|
||||
*/
|
||||
const mergedConfig = deepmerge(BASE_ESBUILD_OPTIONS, entryPointConfig, overrides);
|
||||
|
||||
return mergedConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all entry points in parallel.
|
||||
*
|
||||
* @param {EntryPoint[]} entryPoints
|
||||
* @returns {Promise<esbuild.BuildResult[]>}
|
||||
*/
|
||||
async function buildParallel(entryPoints) {
|
||||
return Promise.all(
|
||||
entryPoints.map((entryPoint) => {
|
||||
return esbuild.build(createEntryPointOptions(entryPoint));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function doHelp() {
|
||||
console.log(`Build the authentik UI
|
||||
|
||||
options:
|
||||
-w, --watch: Build all interfaces
|
||||
-w, --watch: Build all ${entryPoints.length} interfaces
|
||||
-p, --proxy: Build only the polyfills and the loading application
|
||||
-h, --help: This help message
|
||||
`);
|
||||
@ -144,29 +222,27 @@ function doHelp() {
|
||||
}
|
||||
|
||||
async function doWatch() {
|
||||
console.group(`${logPrefix} 🤖 Watching entry points`);
|
||||
console.log("Watching all entry points...");
|
||||
|
||||
const entryPoints = Object.entries(EntryPoint).map(([entrypointID, target]) => {
|
||||
console.log(entrypointID);
|
||||
const buildContexts = await Promise.all(
|
||||
entryPoints.map((entryPoint) => {
|
||||
return esbuild.context(
|
||||
createEntryPointOptions(entryPoint, {
|
||||
define: definitions,
|
||||
plugins: [
|
||||
liveReloadPlugin({
|
||||
logPrefix: `Build Observer (${entryPoint[1]})`,
|
||||
relativeRoot: path.join(__dirname, ".."),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
return target;
|
||||
});
|
||||
await Promise.all(buildContexts.map((context) => context.rebuild()));
|
||||
|
||||
console.groupEnd();
|
||||
|
||||
const buildOptions = createESBuildOptions({
|
||||
entryPoints,
|
||||
plugins: [
|
||||
liveReloadPlugin({
|
||||
relativeRoot: PackageRoot,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const buildContext = await esbuild.context(buildOptions);
|
||||
|
||||
await buildContext.rebuild();
|
||||
await buildContext.watch();
|
||||
await Promise.allSettled(buildContexts.map((context) => context.watch()));
|
||||
|
||||
return /** @type {Promise<void>} */ (
|
||||
new Promise((resolve) => {
|
||||
@ -178,34 +254,15 @@ async function doWatch() {
|
||||
}
|
||||
|
||||
async function doBuild() {
|
||||
console.group(`${logPrefix} 🚀 Building entry points:`);
|
||||
console.log("Building all entry points");
|
||||
|
||||
const entryPoints = Object.entries(EntryPoint).map(([entrypointID, target]) => {
|
||||
console.log(entrypointID);
|
||||
|
||||
return target;
|
||||
});
|
||||
|
||||
console.groupEnd();
|
||||
|
||||
const buildOptions = createESBuildOptions({
|
||||
entryPoints,
|
||||
});
|
||||
|
||||
await esbuild.build(buildOptions);
|
||||
|
||||
console.log("Build complete");
|
||||
return buildParallel(entryPoints);
|
||||
}
|
||||
|
||||
async function doProxy() {
|
||||
const entryPoints = [EntryPoint.StandaloneLoading];
|
||||
|
||||
const buildOptions = createESBuildOptions({
|
||||
entryPoints,
|
||||
});
|
||||
|
||||
await esbuild.build(buildOptions);
|
||||
console.log("Proxy build complete");
|
||||
return buildParallel(
|
||||
entryPoints.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)),
|
||||
);
|
||||
}
|
||||
|
||||
async function delegateCommand() {
|
||||
@ -227,16 +284,12 @@ async function delegateCommand() {
|
||||
}
|
||||
}
|
||||
|
||||
await cleanDistDirectory()
|
||||
// ---
|
||||
.then(() =>
|
||||
delegateCommand()
|
||||
.then(() => {
|
||||
console.log("Build complete");
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}),
|
||||
);
|
||||
await delegateCommand()
|
||||
.then(() => {
|
||||
console.log("Build complete");
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
|
||||
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, css, html } from "lit";
|
||||
|
@ -4,17 +4,13 @@ import { ROUTES } from "@goauthentik/admin/Routes";
|
||||
import {
|
||||
EVENT_API_DRAWER_TOGGLE,
|
||||
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
||||
EVENT_SIDEBAR_TOGGLE,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { configureSentry } from "@goauthentik/common/sentry";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
|
||||
import "@goauthentik/elements/ak-locale-context";
|
||||
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
|
||||
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
|
||||
import "@goauthentik/elements/banner/VersionBanner";
|
||||
import "@goauthentik/elements/banner/VersionBanner";
|
||||
import "@goauthentik/elements/messages/MessageContainer";
|
||||
import "@goauthentik/elements/messages/MessageContainer";
|
||||
@ -25,32 +21,25 @@ import "@goauthentik/elements/router/RouterOutlet";
|
||||
import "@goauthentik/elements/sidebar/Sidebar";
|
||||
import "@goauthentik/elements/sidebar/SidebarItem";
|
||||
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
|
||||
import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { LicenseSummaryStatusEnum, SessionUser, UiThemeEnum } from "@goauthentik/api";
|
||||
import { SessionUser, UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
import {
|
||||
AdminSidebarEnterpriseEntries,
|
||||
AdminSidebarEntries,
|
||||
renderSidebarItems,
|
||||
} from "./AdminSidebar.js";
|
||||
import "./AdminSidebar";
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
await import("@goauthentik/esbuild-plugin-live-reload/client");
|
||||
}
|
||||
|
||||
@customElement("ak-interface-admin")
|
||||
export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
//#region Properties
|
||||
|
||||
export class AdminInterface extends AuthenticatedInterface {
|
||||
@property({ type: Boolean })
|
||||
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
||||
|
||||
@ -65,29 +54,12 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
@query("ak-about-modal")
|
||||
aboutModal?: AboutModal;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public sidebarOpen: boolean;
|
||||
|
||||
#toggleSidebar = () => {
|
||||
this.sidebarOpen = !this.sidebarOpen;
|
||||
};
|
||||
|
||||
#sidebarMatcher: MediaQueryList;
|
||||
#sidebarListener = (event: MediaQueryListEvent) => {
|
||||
this.sidebarOpen = event.matches;
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Styles
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFPage,
|
||||
PFButton,
|
||||
PFDrawer,
|
||||
PFNav,
|
||||
css`
|
||||
.pf-c-page__main,
|
||||
.pf-c-drawer__content,
|
||||
@ -95,30 +67,23 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
z-index: auto !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pf-c-page {
|
||||
background-color: var(--pf-c-page--BackgroundColor) !important;
|
||||
}
|
||||
|
||||
:host([theme="dark"]) {
|
||||
/* Global page background colour */
|
||||
.pf-c-page {
|
||||
--pf-c-page--BackgroundColor: var(--ak-dark-background);
|
||||
}
|
||||
/* Global page background colour */
|
||||
:host([theme="dark"]) .pf-c-page {
|
||||
--pf-c-page--BackgroundColor: var(--ak-dark-background);
|
||||
}
|
||||
|
||||
ak-page-navbar {
|
||||
ak-enterprise-status,
|
||||
ak-version-banner {
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
.ak-sidebar {
|
||||
ak-admin-sidebar {
|
||||
grid-area: nav;
|
||||
}
|
||||
|
||||
.pf-c-drawer__panel {
|
||||
z-index: var(--pf-global--ZIndex--xl);
|
||||
}
|
||||
@ -126,23 +91,10 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
|
||||
this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
|
||||
this.sidebarOpen = this.#sidebarMatcher.matches;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
window.addEventListener(EVENT_SIDEBAR_TOGGLE, this.#toggleSidebar);
|
||||
|
||||
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
|
||||
this.notificationDrawerOpen = !this.notificationDrawerOpen;
|
||||
updateURLParams({
|
||||
@ -156,14 +108,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
apiDrawerOpen: this.apiDrawerOpen,
|
||||
});
|
||||
});
|
||||
|
||||
this.#sidebarMatcher.addEventListener("change", this.#sidebarListener);
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener(EVENT_SIDEBAR_TOGGLE, this.#toggleSidebar);
|
||||
this.#sidebarMatcher.removeEventListener("change", this.#sidebarListener);
|
||||
}
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
@ -174,7 +118,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
this.user.user.isSuperuser ||
|
||||
// TODO: somehow add `access_admin_interface` to the API schema
|
||||
this.user.user.systemPermissions.includes("access_admin_interface");
|
||||
|
||||
if (!canAccessAdmin && this.user.user.pk > 0) {
|
||||
window.location.assign("/if/user/");
|
||||
}
|
||||
@ -182,14 +125,10 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
|
||||
render(): TemplateResult {
|
||||
const sidebarClasses = {
|
||||
"pf-c-page__sidebar": true,
|
||||
"pf-m-light": this.activeTheme === UiThemeEnum.Light,
|
||||
"pf-m-expanded": this.sidebarOpen,
|
||||
"pf-m-collapsed": !this.sidebarOpen,
|
||||
};
|
||||
|
||||
const drawerOpen = this.notificationDrawerOpen || this.apiDrawerOpen;
|
||||
|
||||
const drawerClasses = {
|
||||
"pf-m-expanded": drawerOpen,
|
||||
"pf-m-collapsed": !drawerOpen,
|
||||
@ -197,18 +136,11 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
||||
|
||||
return html` <ak-locale-context>
|
||||
<div class="pf-c-page">
|
||||
<ak-page-navbar>
|
||||
<ak-version-banner></ak-version-banner>
|
||||
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||
</ak-page-navbar>
|
||||
|
||||
<ak-sidebar class="${classMap(sidebarClasses)}">
|
||||
${renderSidebarItems(AdminSidebarEntries)}
|
||||
${this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed
|
||||
? renderSidebarItems(AdminSidebarEnterpriseEntries)
|
||||
: nothing}
|
||||
</ak-sidebar>
|
||||
|
||||
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||
<ak-version-banner></ak-version-banner>
|
||||
<ak-admin-sidebar
|
||||
class="pf-c-page__sidebar ${classMap(sidebarClasses)}"
|
||||
></ak-admin-sidebar>
|
||||
<div class="pf-c-page__drawer">
|
||||
<div class="pf-c-drawer ${classMap(drawerClasses)}">
|
||||
<div class="pf-c-drawer__main">
|
@ -1,97 +1,186 @@
|
||||
import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import {
|
||||
CapabilitiesEnum,
|
||||
WithCapabilitiesConfig,
|
||||
} from "@goauthentik/elements/Interface/capabilitiesProvider";
|
||||
import { WithVersion } from "@goauthentik/elements/Interface/versionProvider";
|
||||
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
|
||||
import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html, nothing } from "lit";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { map } from "lit/directives/map.js";
|
||||
|
||||
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
|
||||
// commonplace and singular enough to merit its own handler.
|
||||
type SidebarEntry = [
|
||||
path: string | null,
|
||||
label: string,
|
||||
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
|
||||
children?: SidebarEntry[],
|
||||
];
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
import type { SessionUser, UserSelf } from "@goauthentik/api";
|
||||
|
||||
/**
|
||||
* Recursively renders a sidebar entry.
|
||||
*/
|
||||
export function renderSidebarItem([
|
||||
path,
|
||||
label,
|
||||
attributes,
|
||||
children,
|
||||
]: SidebarEntry): TemplateResult {
|
||||
const properties = Array.isArray(attributes)
|
||||
? { ".activeWhen": attributes }
|
||||
: (attributes ?? {});
|
||||
@customElement("ak-admin-sidebar")
|
||||
export class AkAdminSidebar extends WithCapabilitiesConfig(WithVersion(AKElement)) {
|
||||
@property({ type: Boolean, reflect: true })
|
||||
open = true;
|
||||
|
||||
if (path) {
|
||||
properties.path = path;
|
||||
@state()
|
||||
impersonation: UserSelf["username"] | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
me().then((user: SessionUser) => {
|
||||
this.impersonation = user.original ? user.user.username : null;
|
||||
});
|
||||
this.toggleOpen = this.toggleOpen.bind(this);
|
||||
this.checkWidth = this.checkWidth.bind(this);
|
||||
}
|
||||
|
||||
return html`<ak-sidebar-item ${spread(properties)}>
|
||||
${label ? html`<span slot="label">${label}</span>` : nothing}
|
||||
${children ? renderSidebarItems(children) : nothing}
|
||||
</ak-sidebar-item>`;
|
||||
// This has to be a bound method so the event listener can be removed on disconnection as
|
||||
// needed.
|
||||
toggleOpen() {
|
||||
this.open = !this.open;
|
||||
}
|
||||
|
||||
checkWidth() {
|
||||
// This works just fine, but it assumes that the `--ak-sidebar--minimum-auto-width` is in
|
||||
// REMs. If that changes, this code will have to be adjusted as well.
|
||||
const minWidth =
|
||||
parseFloat(getRootStyle("--ak-sidebar--minimum-auto-width")) *
|
||||
parseFloat(getRootStyle("font-size"));
|
||||
this.open = window.innerWidth >= minWidth;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener(EVENT_SIDEBAR_TOGGLE, this.toggleOpen);
|
||||
window.addEventListener("resize", this.checkWidth);
|
||||
// After connecting to the DOM, we can now perform this check to see if the sidebar should
|
||||
// be open by default.
|
||||
this.checkWidth();
|
||||
}
|
||||
|
||||
// The symmetry (☟, ☝) here is critical in that you want to start adding these handlers after
|
||||
// connection, and removing them before disconnection.
|
||||
|
||||
disconnectedCallback() {
|
||||
window.removeEventListener(EVENT_SIDEBAR_TOGGLE, this.toggleOpen);
|
||||
window.removeEventListener("resize", this.checkWidth);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ak-sidebar
|
||||
class="pf-c-page__sidebar ${this.open ? "pf-m-expanded" : "pf-m-collapsed"} ${this
|
||||
.activeTheme === UiThemeEnum.Light
|
||||
? "pf-m-light"
|
||||
: ""}"
|
||||
>
|
||||
${this.renderSidebarItems()}
|
||||
</ak-sidebar>
|
||||
`;
|
||||
}
|
||||
|
||||
updated() {
|
||||
// This is permissible as`:host.classList` is not one of the properties Lit uses as a
|
||||
// scheduling trigger. This sort of shenanigans can trigger an loop, in that it will trigger
|
||||
// a browser reflow, which may trigger some other styling the application is monitoring,
|
||||
// triggering a re-render which triggers a browser reflow, ad infinitum. But we've been
|
||||
// living with that since jQuery, and it's both well-known and fortunately rare.
|
||||
|
||||
// eslint-disable-next-line wc/no-self-class
|
||||
this.classList.remove("pf-m-expanded", "pf-m-collapsed");
|
||||
// eslint-disable-next-line wc/no-self-class
|
||||
this.classList.add(this.open ? "pf-m-expanded" : "pf-m-collapsed");
|
||||
}
|
||||
|
||||
renderSidebarItems(): TemplateResult {
|
||||
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
|
||||
// commonplace and singular enough to merit its own handler.
|
||||
type SidebarEntry = [
|
||||
path: string | null,
|
||||
label: string,
|
||||
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
|
||||
children?: SidebarEntry[],
|
||||
];
|
||||
|
||||
// prettier-ignore
|
||||
const sidebarContent: SidebarEntry[] = [
|
||||
[null, msg("Dashboards"), { "?expanded": true }, [
|
||||
["/administration/overview", msg("Overview")],
|
||||
["/administration/dashboard/users", msg("User Statistics")],
|
||||
["/administration/system-tasks", msg("System Tasks")]]],
|
||||
[null, msg("Applications"), null, [
|
||||
["/core/applications", msg("Applications"), [`^/core/applications/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
|
||||
["/outpost/outposts", msg("Outposts")]]],
|
||||
[null, msg("Events"), null, [
|
||||
["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
|
||||
["/events/rules", msg("Notification Rules")],
|
||||
["/events/transports", msg("Notification Transports")]]],
|
||||
[null, msg("Customization"), null, [
|
||||
["/policy/policies", msg("Policies")],
|
||||
["/core/property-mappings", msg("Property Mappings")],
|
||||
["/blueprints/instances", msg("Blueprints")],
|
||||
["/policy/reputation", msg("Reputation scores")]]],
|
||||
[null, msg("Flows and Stages"), null, [
|
||||
["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/flow/stages", msg("Stages")],
|
||||
["/flow/stages/prompts", msg("Prompts")]]],
|
||||
[null, msg("Directory"), null, [
|
||||
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
|
||||
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
|
||||
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/tokens", msg("Tokens and App passwords")],
|
||||
["/flow/stages/invitations", msg("Invitations")]]],
|
||||
[null, msg("System"), null, [
|
||||
["/core/brands", msg("Brands")],
|
||||
["/crypto/certificates", msg("Certificates")],
|
||||
["/outpost/integrations", msg("Outpost Integrations")],
|
||||
["/admin/settings", msg("Settings")]]],
|
||||
];
|
||||
|
||||
// Typescript requires the type here to correctly type the recursive path
|
||||
type SidebarRenderer = (_: SidebarEntry) => TemplateResult;
|
||||
|
||||
const renderOneSidebarItem: SidebarRenderer = ([path, label, attributes, children]) => {
|
||||
const properties = Array.isArray(attributes)
|
||||
? { ".activeWhen": attributes }
|
||||
: (attributes ?? {});
|
||||
if (path) {
|
||||
properties.path = path;
|
||||
}
|
||||
return html`<ak-sidebar-item ${spread(properties)}>
|
||||
${label ? html`<span slot="label">${label}</span>` : nothing}
|
||||
${map(children, renderOneSidebarItem)}
|
||||
</ak-sidebar-item>`;
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
return html`
|
||||
${map(sidebarContent, renderOneSidebarItem)}
|
||||
${this.renderEnterpriseMenu()}
|
||||
`;
|
||||
}
|
||||
|
||||
renderEnterpriseMenu() {
|
||||
return this.can(CapabilitiesEnum.IsEnterprise)
|
||||
? html`
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${msg("Enterprise")}</span>
|
||||
<ak-sidebar-item path="/enterprise/licenses">
|
||||
<span slot="label">${msg("Licenses")}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively renders a collection of sidebar entries.
|
||||
*/
|
||||
export function renderSidebarItems(entries: readonly SidebarEntry[]) {
|
||||
return repeat(entries, ([path, label]) => path || label, renderSidebarItem);
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-admin-sidebar": AkAdminSidebar;
|
||||
}
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export const AdminSidebarEntries: readonly SidebarEntry[] = [
|
||||
[null, msg("Dashboards"), { "?expanded": true }, [
|
||||
["/administration/overview", msg("Overview")],
|
||||
["/administration/dashboard/users", msg("User Statistics")],
|
||||
["/administration/system-tasks", msg("System Tasks")]]
|
||||
],
|
||||
[null, msg("Applications"), null, [
|
||||
["/core/applications", msg("Applications"), [`^/core/applications/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
|
||||
["/outpost/outposts", msg("Outposts")]]
|
||||
],
|
||||
[null, msg("Events"), null, [
|
||||
["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
|
||||
["/events/rules", msg("Notification Rules")],
|
||||
["/events/transports", msg("Notification Transports")]]
|
||||
],
|
||||
[null, msg("Customization"), null, [
|
||||
["/policy/policies", msg("Policies")],
|
||||
["/core/property-mappings", msg("Property Mappings")],
|
||||
["/blueprints/instances", msg("Blueprints")],
|
||||
["/policy/reputation", msg("Reputation scores")]]
|
||||
],
|
||||
[null, msg("Flows and Stages"), null, [
|
||||
["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/flow/stages", msg("Stages")],
|
||||
["/flow/stages/prompts", msg("Prompts")]]
|
||||
],
|
||||
[null, msg("Directory"), null, [
|
||||
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
|
||||
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
|
||||
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/tokens", msg("Tokens and App passwords")],
|
||||
["/flow/stages/invitations", msg("Invitations")]]
|
||||
],
|
||||
[null, msg("System"), null, [
|
||||
["/core/brands", msg("Brands")],
|
||||
["/crypto/certificates", msg("Certificates")],
|
||||
["/outpost/integrations", msg("Outpost Integrations")],
|
||||
["/admin/settings", msg("Settings")]]
|
||||
],
|
||||
];
|
||||
|
||||
// prettier-ignore
|
||||
export const AdminSidebarEnterpriseEntries: readonly SidebarEntry[] = [
|
||||
[null, msg("Enterprise"), null, [
|
||||
["/enterprise/licenses", msg("Licenses"), null]
|
||||
],
|
||||
]]
|
||||
|
5
web/src/admin/AdminInterface/index.ts
Normal file
5
web/src/admin/AdminInterface/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { AdminInterface } from "./AdminInterface";
|
||||
import "./AdminInterface";
|
||||
|
||||
export { AdminInterface };
|
||||
export default AdminInterface;
|
@ -94,13 +94,10 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
const username = this.user?.user.name || this.user?.user.username;
|
||||
const name = this.user?.user.name ?? this.user?.user.username;
|
||||
|
||||
return html` <ak-page-header
|
||||
header=${msg(str`Welcome, ${username || ""}.`)}
|
||||
description=${msg("General system status")}
|
||||
?hasIcon=${false}
|
||||
>
|
||||
return html`<ak-page-header description=${msg("General system status")} ?hasIcon=${false}>
|
||||
<span slot="header"> ${msg(str`Welcome, ${name || ""}.`)} </span>
|
||||
</ak-page-header>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
|
@ -83,10 +83,13 @@ export class AdminSettingsPage extends AKElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.settings) return nothing;
|
||||
|
||||
if (!this.settings) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ak-page-header icon="fa fa-cog" header="${msg("System settings")}"> </ak-page-header>
|
||||
<ak-page-header icon="fa fa-cog" header="" description="">
|
||||
<span slot="header"> ${msg("System settings")} </span>
|
||||
</ak-page-header>
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
|
@ -1,7 +1,6 @@
|
||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
@ -9,6 +8,7 @@ import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
import "@goauthentik/elements/forms/SearchSelect";
|
||||
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
|
||||
import YAML from "yaml";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { AdminInterface } from "@goauthentik/admin/AdminInterface/index.entrypoint.js";
|
||||
import { AdminInterface } from "@goauthentik/admin/AdminInterface";
|
||||
import "@goauthentik/admin/users/ServiceAccountForm";
|
||||
import "@goauthentik/admin/users/UserActiveForm";
|
||||
import "@goauthentik/admin/users/UserForm";
|
||||
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
||||
export const ERROR_CLASS = "pf-m-danger";
|
||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
export const CURRENT_CLASS = "pf-m-current";
|
||||
export const VERSION = "2025.4.0";
|
||||
export const VERSION = "2025.4.2";
|
||||
export const TITLE_DEFAULT = "authentik";
|
||||
export const ROUTE_SEPARATOR = ";";
|
||||
|
||||
|
@ -1,110 +1,26 @@
|
||||
import type { Config as DOMPurifyConfig } from "dompurify";
|
||||
import DOMPurify from "dompurify";
|
||||
import { trustedTypes } from "trusted-types";
|
||||
|
||||
import { render } from "lit";
|
||||
import { render } from "@lit-labs/ssr";
|
||||
import { collectResult } from "@lit-labs/ssr/lib/render-result.js";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
/**
|
||||
* Trusted types policy that escapes HTML content in place.
|
||||
*
|
||||
* @see {@linkcode SanitizedTrustPolicy} to strip HTML content.
|
||||
*
|
||||
* @returns {TrustedHTML} All HTML content, escaped.
|
||||
*/
|
||||
export const EscapeTrustPolicy = trustedTypes.createPolicy("authentik-escape", {
|
||||
createHTML: (untrustedHTML: string) => {
|
||||
return DOMPurify.sanitize(untrustedHTML, {
|
||||
RETURN_TRUSTED_TYPE: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Trusted types policy, stripping all HTML content.
|
||||
*
|
||||
* @returns {TrustedHTML} Text content only, all HTML tags stripped.
|
||||
*/
|
||||
export const SanitizedTrustPolicy = trustedTypes.createPolicy("authentik-sanitize", {
|
||||
createHTML: (untrustedHTML: string) => {
|
||||
return DOMPurify.sanitize(untrustedHTML, {
|
||||
RETURN_TRUSTED_TYPE: false,
|
||||
ALLOWED_TAGS: ["#text"],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Trusted types policy, allowing a minimal set of _safe_ HTML tags supplied by
|
||||
* a trusted source, such as the brand API.
|
||||
*/
|
||||
export const BrandedHTMLPolicy = trustedTypes.createPolicy("authentik-restrict", {
|
||||
createHTML: (untrustedHTML: string) => {
|
||||
return DOMPurify.sanitize(untrustedHTML, {
|
||||
RETURN_TRUSTED_TYPE: false,
|
||||
FORBID_TAGS: [
|
||||
"script",
|
||||
"style",
|
||||
"iframe",
|
||||
"link",
|
||||
"object",
|
||||
"embed",
|
||||
"applet",
|
||||
"meta",
|
||||
"base",
|
||||
"form",
|
||||
"input",
|
||||
"textarea",
|
||||
"select",
|
||||
"button",
|
||||
],
|
||||
FORBID_ATTR: [
|
||||
"onerror",
|
||||
"onclick",
|
||||
"onload",
|
||||
"onmouseover",
|
||||
"onmouseout",
|
||||
"onmouseup",
|
||||
"onmousedown",
|
||||
"onfocus",
|
||||
"onblur",
|
||||
"onsubmit",
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export type AuthentikTrustPolicy =
|
||||
| typeof EscapeTrustPolicy
|
||||
| typeof SanitizedTrustPolicy
|
||||
| typeof BrandedHTMLPolicy;
|
||||
|
||||
/**
|
||||
* Sanitize an untrusted HTML string using a trusted types policy.
|
||||
*/
|
||||
export function sanitizeHTML(trustPolicy: AuthentikTrustPolicy, untrustedHTML: string) {
|
||||
return unsafeHTML(trustPolicy.createHTML(untrustedHTML).toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* DOMPurify configuration for strict sanitization.
|
||||
*
|
||||
* This configuration only allows text nodes and disallows all HTML tags.
|
||||
*/
|
||||
export const DOM_PURIFY_STRICT = {
|
||||
ALLOWED_TAGS: ["#text"],
|
||||
} as const satisfies DOMPurifyConfig;
|
||||
|
||||
/**
|
||||
* Render untrusted HTML to a string without escaping it.
|
||||
*
|
||||
* @returns {string} The rendered HTML string.
|
||||
*/
|
||||
export function renderStaticHTMLUnsafe(untrustedHTML: unknown): string {
|
||||
const container = document.createElement("html");
|
||||
render(untrustedHTML, container);
|
||||
|
||||
const result = container.innerHTML;
|
||||
|
||||
return result;
|
||||
export async function renderStatic(input: TemplateResult): Promise<string> {
|
||||
return await collectResult(render(input));
|
||||
}
|
||||
|
||||
export function purify(input: TemplateResult): TemplateResult {
|
||||
return html`${until(
|
||||
(async () => {
|
||||
const rendered = await renderStatic(input);
|
||||
const purified = DOMPurify.sanitize(rendered);
|
||||
return html`${unsafeHTML(purified)}`;
|
||||
})(),
|
||||
)}`;
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { config } from "@goauthentik/common/api/config";
|
||||
import { VERSION } from "@goauthentik/common/constants";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
|
||||
import {
|
||||
ErrorEvent,
|
||||
EventHint,
|
||||
@ -69,7 +68,7 @@ export async function configureSentry(canDoPpi = false): Promise<Config> {
|
||||
});
|
||||
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
|
||||
if (window.location.pathname.includes("if/")) {
|
||||
setTag(TAG_SENTRY_COMPONENT, `web/${readInterfaceRouteParam()}`);
|
||||
setTag(TAG_SENTRY_COMPONENT, `web/${currentInterface()}`);
|
||||
}
|
||||
if (cfg.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
const Spotlight = await import("@spotlightjs/spotlight");
|
||||
@ -87,3 +86,13 @@ export async function configureSentry(canDoPpi = false): Promise<Config> {
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// Get the interface name from URL
|
||||
export function currentInterface(): string {
|
||||
const pathMatches = window.location.pathname.match(/.+if\/(\w+)\//);
|
||||
let currentInterface = "unknown";
|
||||
if (pathMatches && pathMatches.length >= 2) {
|
||||
currentInterface = pathMatches[1];
|
||||
}
|
||||
return currentInterface.toLowerCase();
|
||||
}
|
||||
|
@ -17,13 +17,6 @@
|
||||
|
||||
/* Minimum width after which the sidebar becomes automatic */
|
||||
--ak-sidebar--minimum-auto-width: 80rem;
|
||||
|
||||
/**
|
||||
* The height of the navbar and branded sidebar.
|
||||
* @todo This shouldn't be necessary. The sidebar can instead use a grid layout
|
||||
* ensuring they share the same height.
|
||||
*/
|
||||
--ak-navbar--height: 7rem;
|
||||
}
|
||||
|
||||
@supports selector(::-webkit-scrollbar) {
|
||||
|
@ -1,223 +0,0 @@
|
||||
/**
|
||||
* @file Stylesheet utilities.
|
||||
*/
|
||||
import { CSSResult, CSSResultOrNative, ReactiveElement, css } from "lit";
|
||||
|
||||
/**
|
||||
* Elements containing adoptable stylesheets.
|
||||
*/
|
||||
export type StyleSheetParent = Pick<DocumentOrShadowRoot, "adoptedStyleSheets">;
|
||||
|
||||
/**
|
||||
* Type-predicate to determine if a given object has adoptable stylesheets.
|
||||
*/
|
||||
export function isAdoptableStyleSheetParent(input: unknown): input is StyleSheetParent {
|
||||
// Sanity check - Does the input have the right shape?
|
||||
|
||||
if (!input || typeof input !== "object") return false;
|
||||
|
||||
if (!("adoptedStyleSheets" in input) || !input.adoptedStyleSheets) return false;
|
||||
|
||||
if (typeof input.adoptedStyleSheets !== "object") return false;
|
||||
|
||||
// We avoid `Array.isArray` because the adopted stylesheets property
|
||||
// is defined as a proxied array.
|
||||
// All we care about is that it's shaped like an array.
|
||||
if (!("length" in input.adoptedStyleSheets)) return false;
|
||||
|
||||
if (typeof input.adoptedStyleSheets.length !== "number") return false;
|
||||
|
||||
// Finally is the array mutable?
|
||||
return "push" in input.adoptedStyleSheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given input can adopt stylesheets.
|
||||
*/
|
||||
export function assertAdoptableStyleSheetParent<T>(
|
||||
input: T,
|
||||
): asserts input is T & StyleSheetParent {
|
||||
if (isAdoptableStyleSheetParent(input)) return;
|
||||
|
||||
console.debug("Given input missing `adoptedStyleSheets`", input);
|
||||
|
||||
throw new TypeError("Assertion failed: `adoptedStyleSheets` missing in given input");
|
||||
}
|
||||
|
||||
export function resolveStyleSheetParent<T extends HTMLElement | DocumentFragment | Document>(
|
||||
renderRoot: T,
|
||||
) {
|
||||
const styleRoot = "ShadyDOM" in window ? document : renderRoot;
|
||||
|
||||
assertAdoptableStyleSheetParent(styleRoot);
|
||||
|
||||
return styleRoot;
|
||||
}
|
||||
|
||||
export type StyleSheetInit = string | CSSResult | CSSStyleSheet;
|
||||
|
||||
/**
|
||||
* Given a source of CSS, create a `CSSStyleSheet`.
|
||||
*
|
||||
* @throw {@linkcode TypeError} if the input cannot be converted to a `CSSStyleSheet`
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Storybook's `build` does not currently have a coherent way of importing
|
||||
* CSS-as-text into CSSStyleSheet.
|
||||
*
|
||||
* It works well when Storybook is running in `dev`, but in `build` it fails.
|
||||
* Storied components will have to map their textual CSS imports.
|
||||
*/
|
||||
export function createStyleSheet(input: string): CSSResult {
|
||||
const inputTemplate = [input] as unknown as TemplateStringsArray;
|
||||
|
||||
const result = css(inputTemplate, []);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a source of CSS, create a `CSSStyleSheet`.
|
||||
*
|
||||
* @see {@linkcode createStyleSheet}
|
||||
*/
|
||||
export function normalizeCSSSource(css: string): CSSStyleSheet;
|
||||
export function normalizeCSSSource(styleSheet: CSSStyleSheet): CSSStyleSheet;
|
||||
export function normalizeCSSSource(cssResult: CSSResult): CSSResult;
|
||||
export function normalizeCSSSource(input: StyleSheetInit): CSSResultOrNative;
|
||||
export function normalizeCSSSource(input: StyleSheetInit): CSSResultOrNative {
|
||||
if (typeof input === "string") return createStyleSheet(input);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `CSSStyleSheet` from the given input.
|
||||
*/
|
||||
export function createStyleSheetUnsafe(input: StyleSheetInit): CSSStyleSheet {
|
||||
const result = normalizeCSSSource(input);
|
||||
if (result instanceof CSSStyleSheet) return result;
|
||||
|
||||
if (!result.styleSheet) {
|
||||
console.debug(
|
||||
"authentik/common/stylesheets: CSSResult missing styleSheet, returning empty",
|
||||
{ result, input },
|
||||
);
|
||||
|
||||
throw new TypeError("Expected a CSSStyleSheet");
|
||||
}
|
||||
|
||||
return result.styleSheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append stylesheet(s) to the given roots.
|
||||
*
|
||||
* @see {@linkcode removeStyleSheet} to remove a stylesheet from a given roots.
|
||||
*/
|
||||
export function appendStyleSheet(
|
||||
styleParent: StyleSheetParent,
|
||||
...insertions: CSSStyleSheet[]
|
||||
): void {
|
||||
insertions = Array.isArray(insertions) ? insertions : [insertions];
|
||||
|
||||
for (const styleSheetInsertion of insertions) {
|
||||
if (styleParent.adoptedStyleSheets.includes(styleSheetInsertion)) return;
|
||||
|
||||
styleParent.adoptedStyleSheets = [...styleParent.adoptedStyleSheets, styleSheetInsertion];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a stylesheet from the given roots, matching by referential equality.
|
||||
*
|
||||
* @see {@linkcode appendStyleSheet} to append a stylesheet to a given roots.
|
||||
*/
|
||||
export function removeStyleSheet(
|
||||
styleParent: StyleSheetParent,
|
||||
...removals: CSSStyleSheet[]
|
||||
): void {
|
||||
const nextAdoptedStyleSheets = styleParent.adoptedStyleSheets.filter(
|
||||
(styleSheet) => !removals.includes(styleSheet),
|
||||
);
|
||||
|
||||
if (nextAdoptedStyleSheets.length === styleParent.adoptedStyleSheets.length) return;
|
||||
|
||||
styleParent.adoptedStyleSheets = nextAdoptedStyleSheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a stylesheet to a string.
|
||||
*
|
||||
* This is useful for debugging or inspecting the contents of a stylesheet.
|
||||
*/
|
||||
export function serializeStyleSheet(stylesheet: CSSStyleSheet): string {
|
||||
return Array.from(stylesheet.cssRules || [], (rule) => rule.cssText || "").join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect the adopted stylesheets of a given style parent, serializing them to strings.
|
||||
*/
|
||||
export function inspectStyleSheets(styleParent: StyleSheetParent): string[] {
|
||||
return styleParent.adoptedStyleSheets.map((styleSheet) => serializeStyleSheet(styleSheet));
|
||||
}
|
||||
|
||||
interface InspectedStyleSheetEntry {
|
||||
tagName: string;
|
||||
element: ReactiveElement;
|
||||
styles: string[];
|
||||
children?: InspectedStyleSheetEntry[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively inspect the adopted stylesheets of a given style parent, serializing them to strings.
|
||||
*/
|
||||
export function inspectStyleSheetTree(element: ReactiveElement): InspectedStyleSheetEntry {
|
||||
const styleParent = resolveStyleSheetParent(element.renderRoot);
|
||||
const styles = inspectStyleSheets(styleParent);
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
|
||||
const treewalker = document.createTreeWalker(element.renderRoot, NodeFilter.SHOW_ELEMENT, {
|
||||
acceptNode(node) {
|
||||
if (node instanceof ReactiveElement) {
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
}
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
},
|
||||
});
|
||||
const children: InspectedStyleSheetEntry[] = [];
|
||||
let currentNode: Node | null = treewalker.nextNode();
|
||||
while (currentNode) {
|
||||
const childElement = currentNode as ReactiveElement;
|
||||
|
||||
if (!isAdoptableStyleSheetParent(childElement.renderRoot)) {
|
||||
currentNode = treewalker.nextNode();
|
||||
continue;
|
||||
}
|
||||
|
||||
const childStyles = inspectStyleSheets(childElement.renderRoot);
|
||||
|
||||
children.push({
|
||||
tagName: childElement.tagName.toLowerCase(),
|
||||
element: childElement,
|
||||
styles: childStyles,
|
||||
});
|
||||
currentNode = treewalker.nextNode();
|
||||
}
|
||||
|
||||
return {
|
||||
tagName,
|
||||
element,
|
||||
styles,
|
||||
children,
|
||||
};
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
Object.assign(window, {
|
||||
inspectStyleSheetTree,
|
||||
serializeStyleSheet,
|
||||
inspectStyleSheets,
|
||||
});
|
||||
}
|
@ -1,200 +0,0 @@
|
||||
/**
|
||||
* @file Theme utilities.
|
||||
*/
|
||||
import { UIConfig } from "@goauthentik/common/ui/config";
|
||||
|
||||
import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
//#region Scheme Types
|
||||
|
||||
/**
|
||||
* Valid CSS color scheme values.
|
||||
*
|
||||
* @link {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme | MDN}
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export type CSSColorSchemeValue = "dark" | "light" | "auto";
|
||||
|
||||
/**
|
||||
* A CSS color scheme value that can be preferred by the user, i.e. not `"auto"`.
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export type ResolvedCSSColorSchemeValue = Exclude<CSSColorSchemeValue, "auto">;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region UI Theme Types
|
||||
|
||||
/**
|
||||
* A UI color scheme value that can be preferred by the user.
|
||||
*
|
||||
* i.e. not an lack of preference or unknown value.
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export type ResolvedUITheme = typeof UiThemeEnum.Light | typeof UiThemeEnum.Dark;
|
||||
|
||||
/**
|
||||
* A mapping of theme values to their respective inversion.
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export const UIThemeInversion = {
|
||||
dark: "light",
|
||||
light: "dark",
|
||||
} as const satisfies Record<ResolvedUITheme, ResolvedUITheme>;
|
||||
|
||||
/**
|
||||
* Either a valid CSS color scheme value, or a theme preference.
|
||||
*/
|
||||
export type UIThemeHint = CSSColorSchemeValue | UiThemeEnum;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Scheme Functions
|
||||
|
||||
/**
|
||||
* Creates an event target for the given color scheme.
|
||||
*
|
||||
* @param colorScheme The color scheme to target.
|
||||
* @returns A {@linkcode MediaQueryList} that can be used to listen for changes to the color scheme.
|
||||
*
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList | MDN}
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export function createColorSchemeTarget(colorScheme: ResolvedCSSColorSchemeValue): MediaQueryList {
|
||||
return window.matchMedia(`(prefers-color-scheme: ${colorScheme})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given input into a valid CSS color scheme value.
|
||||
*
|
||||
* If the input is not provided, it defaults to "auto".
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export function formatColorScheme(theme: ResolvedUITheme): ResolvedCSSColorSchemeValue;
|
||||
export function formatColorScheme(
|
||||
colorScheme: ResolvedCSSColorSchemeValue,
|
||||
): ResolvedCSSColorSchemeValue;
|
||||
export function formatColorScheme(hint?: UIThemeHint): CSSColorSchemeValue;
|
||||
export function formatColorScheme(hint?: UIThemeHint): CSSColorSchemeValue {
|
||||
if (!hint) return "auto";
|
||||
|
||||
switch (hint) {
|
||||
case "dark":
|
||||
case UiThemeEnum.Dark:
|
||||
return "dark";
|
||||
case "light":
|
||||
case UiThemeEnum.Light:
|
||||
return "light";
|
||||
case "auto":
|
||||
case UiThemeEnum.Automatic:
|
||||
return "auto";
|
||||
default:
|
||||
console.warn(`Unknown color scheme hint: ${hint}. Defaulting to "auto".`);
|
||||
return "auto";
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Theme Functions
|
||||
|
||||
/**
|
||||
* Resolve the current UI theme based on the user's preference or the provided color scheme.
|
||||
*
|
||||
* @param hint The color scheme hint to use.
|
||||
*
|
||||
* @category CSS
|
||||
*/
|
||||
export function resolveUITheme(
|
||||
hint?: UIThemeHint,
|
||||
defaultUITheme: ResolvedUITheme = UiThemeEnum.Light,
|
||||
): ResolvedUITheme {
|
||||
const colorScheme = formatColorScheme(hint);
|
||||
|
||||
if (colorScheme !== "auto") return colorScheme;
|
||||
|
||||
// Given that we don't know the user's preference,
|
||||
// we can determine the theme based on whether the default theme is
|
||||
// currently being overridden.
|
||||
|
||||
const colorSchemeInversion = formatColorScheme(UIThemeInversion[defaultUITheme]);
|
||||
|
||||
const mediaQueryList = createColorSchemeTarget(colorSchemeInversion);
|
||||
|
||||
return mediaQueryList.matches ? colorSchemeInversion : defaultUITheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Effect listener invoked when the color scheme changes.
|
||||
*/
|
||||
export type UIThemeListener = (currentUITheme: ResolvedUITheme) => void;
|
||||
/**
|
||||
* Create an effect that runs
|
||||
*
|
||||
* @returns A cleanup function that removes the effect.
|
||||
*/
|
||||
export function createUIThemeEffect(
|
||||
effect: UIThemeListener,
|
||||
listenerOptions?: AddEventListenerOptions,
|
||||
): () => void {
|
||||
const colorSchemeTarget = resolveUITheme();
|
||||
const invertedColorSchemeTarget = UIThemeInversion[colorSchemeTarget];
|
||||
|
||||
let previousUITheme: ResolvedUITheme | undefined;
|
||||
|
||||
// First, wrap the effect to ensure we can abort it.
|
||||
const changeListener = (event: MediaQueryListEvent) => {
|
||||
if (listenerOptions?.signal?.aborted) return;
|
||||
|
||||
const currentUITheme = event.matches ? colorSchemeTarget : invertedColorSchemeTarget;
|
||||
|
||||
if (previousUITheme === currentUITheme) return;
|
||||
|
||||
previousUITheme = currentUITheme;
|
||||
|
||||
effect(currentUITheme);
|
||||
};
|
||||
|
||||
const mediaQueryList = createColorSchemeTarget(colorSchemeTarget);
|
||||
|
||||
// Trigger the effect immediately.
|
||||
effect(colorSchemeTarget);
|
||||
|
||||
// Listen for changes to the color scheme...
|
||||
mediaQueryList.addEventListener("change", changeListener, listenerOptions);
|
||||
|
||||
// Finally, allow the caller to remove the effect.
|
||||
const cleanup = () => {
|
||||
mediaQueryList.removeEventListener("change", changeListener);
|
||||
};
|
||||
|
||||
return cleanup;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Theme Element
|
||||
|
||||
/**
|
||||
* An element that can be themed.
|
||||
*/
|
||||
export interface ThemedElement extends HTMLElement {
|
||||
brand?: CurrentBrand;
|
||||
uiConfig?: UIConfig;
|
||||
config?: Config;
|
||||
activeTheme: ResolvedUITheme;
|
||||
}
|
||||
|
||||
export function rootInterface<T extends ThemedElement = ThemedElement>(): T | null {
|
||||
const element = document.body.querySelector<T>("[data-ak-interface-root]");
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
//#endregion
|
@ -1,19 +1,7 @@
|
||||
import { currentInterface } from "@goauthentik/common/sentry";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { isUserRoute } from "@goauthentik/elements/router/utils";
|
||||
|
||||
import { UiThemeEnum, UserSelf } from "@goauthentik/api";
|
||||
import { CurrentBrand } from "@goauthentik/api";
|
||||
|
||||
export const DefaultBrand = {
|
||||
brandingLogo: "/static/dist/assets/icons/icon_left_brand.svg",
|
||||
brandingFavicon: "/static/dist/assets/icons/icon.png",
|
||||
brandingTitle: "authentik",
|
||||
brandingCustomCss: "",
|
||||
uiFooterLinks: [],
|
||||
uiTheme: UiThemeEnum.Automatic,
|
||||
matchedDomain: "",
|
||||
defaultLocale: "",
|
||||
} as const satisfies CurrentBrand;
|
||||
|
||||
export enum UserDisplay {
|
||||
username = "username",
|
||||
@ -89,7 +77,9 @@ export class DefaultUIConfig implements UIConfig {
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.enabledFeatures.apiDrawer = !isUserRoute();
|
||||
if (currentInterface() === "user") {
|
||||
this.enabledFeatures.apiDrawer = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ export class NavigationButtons extends AKElement {
|
||||
);
|
||||
};
|
||||
|
||||
return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-xl">
|
||||
return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg">
|
||||
<button class="pf-c-button pf-m-plain" type="button" @click=${onClick}>
|
||||
<pf-tooltip position="top" content=${msg("Open API drawer")}>
|
||||
<i class="fas fa-code" aria-hidden="true"></i>
|
||||
@ -116,7 +116,7 @@ export class NavigationButtons extends AKElement {
|
||||
);
|
||||
};
|
||||
|
||||
return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-xl">
|
||||
return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@ -156,7 +156,9 @@ export class NavigationButtons extends AKElement {
|
||||
}
|
||||
|
||||
renderImpersonation() {
|
||||
if (!this.me?.original) return nothing;
|
||||
if (!this.me?.original) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const onClick = async () => {
|
||||
await new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve();
|
||||
@ -173,14 +175,6 @@ export class NavigationButtons extends AKElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderAvatar() {
|
||||
return html`<img
|
||||
class="pf-c-page__header-tools-item pf-c-avatar pf-m-hidden pf-m-visible-on-xl"
|
||||
src=${ifDefined(this.me?.user.avatar)}
|
||||
alt="${msg("Avatar image")}"
|
||||
/>`;
|
||||
}
|
||||
|
||||
get userDisplayName() {
|
||||
return match<UserDisplay | undefined, string | undefined>(this.uiConfig?.navbar.userDisplay)
|
||||
.with(UserDisplay.username, () => this.me?.user.username)
|
||||
@ -212,13 +206,17 @@ export class NavigationButtons extends AKElement {
|
||||
</div>
|
||||
${this.renderImpersonation()}
|
||||
${this.userDisplayName != ""
|
||||
? html`<div class="pf-c-page__header-tools-group pf-m-hidden">
|
||||
<div class="pf-c-page__header-tools-item pf-m-visible-on-2xl">
|
||||
? html`<div class="pf-c-page__header-tools-group">
|
||||
<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md">
|
||||
${this.userDisplayName}
|
||||
</div>
|
||||
</div>`
|
||||
: nothing}
|
||||
${this.renderAvatar()}
|
||||
<img
|
||||
class="pf-c-avatar"
|
||||
src=${ifDefined(this.me?.user.avatar)}
|
||||
alt="${msg("Avatar image")}"
|
||||
/>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
@ -1,140 +1,165 @@
|
||||
import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import {
|
||||
StyleSheetInit,
|
||||
StyleSheetParent,
|
||||
appendStyleSheet,
|
||||
createStyleSheetUnsafe,
|
||||
removeStyleSheet,
|
||||
resolveStyleSheetParent,
|
||||
} from "@goauthentik/common/stylesheets";
|
||||
import {
|
||||
CSSColorSchemeValue,
|
||||
ResolvedUITheme,
|
||||
UIThemeListener,
|
||||
createUIThemeEffect,
|
||||
formatColorScheme,
|
||||
resolveUITheme,
|
||||
} from "@goauthentik/common/theme";
|
||||
import { type ThemedElement } from "@goauthentik/common/theme";
|
||||
import { UIConfig } from "@goauthentik/common/ui/config";
|
||||
import { adaptCSS } from "@goauthentik/common/utils";
|
||||
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
|
||||
|
||||
import { localized } from "@lit/localize";
|
||||
import { CSSResultGroup, CSSResultOrNative, LitElement } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { LitElement, ReactiveElement } from "lit";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import OneDark from "@goauthentik/common/styles/one-dark.css";
|
||||
import ThemeDark from "@goauthentik/common/styles/theme-dark.css";
|
||||
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
// Re-export the theme helpers
|
||||
export { rootInterface } from "@goauthentik/common/theme";
|
||||
type AkInterface = HTMLElement & {
|
||||
getTheme: () => Promise<UiThemeEnum>;
|
||||
brand?: CurrentBrand;
|
||||
uiConfig?: UIConfig;
|
||||
config?: Config;
|
||||
get activeTheme(): UiThemeEnum | undefined;
|
||||
};
|
||||
|
||||
export const rootInterface = <T extends AkInterface>(): T | undefined =>
|
||||
(document.body.querySelector("[data-ak-interface-root]") as T) ?? undefined;
|
||||
|
||||
export const QUERY_MEDIA_COLOR_LIGHT = "(prefers-color-scheme: light)";
|
||||
|
||||
// Ensure themes are converted to a static instance of CSS Stylesheet, otherwise the
|
||||
// when changing themes we might not remove the correct css stylesheet instance.
|
||||
const _darkTheme = ensureCSSStyleSheet(ThemeDark);
|
||||
|
||||
@localized()
|
||||
export class AKElement extends LitElement implements ThemedElement {
|
||||
//#region Properties
|
||||
export class AKElement extends LitElement {
|
||||
_mediaMatcher?: MediaQueryList;
|
||||
_mediaMatcherHandler?: (ev?: MediaQueryListEvent) => void;
|
||||
_activeTheme?: UiThemeEnum;
|
||||
|
||||
/**
|
||||
* The resolved theme of the current element.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Unlike the browser's current color scheme, this is a value that can be
|
||||
* resolved to a specific theme, i.e. dark or light.
|
||||
*/
|
||||
@property({
|
||||
attribute: "theme",
|
||||
type: String,
|
||||
reflect: true,
|
||||
})
|
||||
public activeTheme: ResolvedUITheme;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Private Properties
|
||||
|
||||
readonly #preferredColorScheme: CSSColorSchemeValue;
|
||||
|
||||
#customCSSStyleSheet: CSSStyleSheet | null;
|
||||
#darkThemeStyleSheet: CSSStyleSheet | null = null;
|
||||
#themeAbortController: AbortController | null = null;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
protected static finalizeStyles(styles?: CSSResultGroup): CSSResultOrNative[] {
|
||||
// Ensure all style sheets being passed are really style sheets.
|
||||
const baseStyles: StyleSheetInit[] = [AKGlobal, OneDark];
|
||||
|
||||
if (!styles) return baseStyles.map(createStyleSheetUnsafe);
|
||||
|
||||
if (Array.isArray(styles)) {
|
||||
return [
|
||||
//---
|
||||
...(styles as unknown as CSSResultOrNative[]),
|
||||
...baseStyles,
|
||||
].flatMap(createStyleSheetUnsafe);
|
||||
}
|
||||
return [styles, ...baseStyles].map(createStyleSheetUnsafe);
|
||||
get activeTheme(): UiThemeEnum | undefined {
|
||||
return this._activeTheme;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const { brand } = globalAK();
|
||||
|
||||
this.#preferredColorScheme = formatColorScheme(brand.uiTheme);
|
||||
this.activeTheme = resolveUITheme(brand?.uiTheme);
|
||||
|
||||
this.#customCSSStyleSheet = brand?.brandingCustomCss
|
||||
? createStyleSheetUnsafe(brand.brandingCustomCss)
|
||||
: null;
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.#themeAbortController?.abort();
|
||||
setInitialStyles(root: DocumentOrShadowRoot) {
|
||||
const styleRoot: DocumentOrShadowRoot = (
|
||||
"ShadyDOM" in window ? document : root
|
||||
) as DocumentOrShadowRoot;
|
||||
styleRoot.adoptedStyleSheets = adaptCSS([
|
||||
...styleRoot.adoptedStyleSheets,
|
||||
ensureCSSStyleSheet(AKGlobal),
|
||||
ensureCSSStyleSheet(OneDark),
|
||||
]);
|
||||
this._initTheme(styleRoot);
|
||||
this._initCustomCSS(styleRoot);
|
||||
}
|
||||
|
||||
#styleRoot?: StyleSheetParent;
|
||||
|
||||
#dispatchTheme: UIThemeListener = (nextUITheme) => {
|
||||
if (!this.#styleRoot) return;
|
||||
|
||||
if (nextUITheme === UiThemeEnum.Dark) {
|
||||
this.#darkThemeStyleSheet ||= createStyleSheetUnsafe(ThemeDark);
|
||||
appendStyleSheet(this.#styleRoot, this.#darkThemeStyleSheet);
|
||||
this.activeTheme = UiThemeEnum.Dark;
|
||||
} else if (this.#darkThemeStyleSheet) {
|
||||
removeStyleSheet(this.#styleRoot, this.#darkThemeStyleSheet);
|
||||
this.#darkThemeStyleSheet = null;
|
||||
this.activeTheme = UiThemeEnum.Light;
|
||||
}
|
||||
};
|
||||
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
const renderRoot = super.createRenderRoot();
|
||||
this.#styleRoot = resolveStyleSheetParent(renderRoot);
|
||||
|
||||
if (this.#customCSSStyleSheet) {
|
||||
console.debug(`authentik/element[${this.tagName.toLowerCase()}]: Adding custom CSS`);
|
||||
|
||||
appendStyleSheet(this.#styleRoot, this.#customCSSStyleSheet);
|
||||
}
|
||||
|
||||
this.#themeAbortController = new AbortController();
|
||||
|
||||
if (this.#preferredColorScheme === "dark") {
|
||||
this.#dispatchTheme(UiThemeEnum.Dark);
|
||||
} else if (this.#preferredColorScheme === "auto") {
|
||||
createUIThemeEffect(this.#dispatchTheme, {
|
||||
signal: this.#themeAbortController.signal,
|
||||
});
|
||||
}
|
||||
|
||||
return renderRoot;
|
||||
protected createRenderRoot() {
|
||||
this.fixElementStyles();
|
||||
const root = super.createRenderRoot();
|
||||
this.setInitialStyles(root as unknown as DocumentOrShadowRoot);
|
||||
return root;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
async getTheme(): Promise<UiThemeEnum> {
|
||||
return rootInterface()?.getTheme() || UiThemeEnum.Automatic;
|
||||
}
|
||||
|
||||
fixElementStyles() {
|
||||
// Ensure all style sheets being passed are really style sheets.
|
||||
(this.constructor as typeof ReactiveElement).elementStyles = (
|
||||
this.constructor as typeof ReactiveElement
|
||||
).elementStyles.map(ensureCSSStyleSheet);
|
||||
}
|
||||
|
||||
async _initTheme(root: DocumentOrShadowRoot): Promise<void> {
|
||||
// Early activate theme based on media query to prevent light flash
|
||||
// when dark is preferred
|
||||
this._applyTheme(root, globalAK().brand.uiTheme);
|
||||
this._applyTheme(root, await this.getTheme());
|
||||
}
|
||||
|
||||
async _initCustomCSS(root: DocumentOrShadowRoot): Promise<void> {
|
||||
const brand = globalAK().brand;
|
||||
if (!brand) {
|
||||
return;
|
||||
}
|
||||
const sheet = await new CSSStyleSheet().replace(brand.brandingCustomCss);
|
||||
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
|
||||
}
|
||||
|
||||
_applyTheme(root: DocumentOrShadowRoot, theme?: UiThemeEnum): void {
|
||||
if (!theme) {
|
||||
theme = UiThemeEnum.Automatic;
|
||||
}
|
||||
if (theme === UiThemeEnum.Automatic) {
|
||||
// Create a media matcher to automatically switch the theme depending on
|
||||
// prefers-color-scheme
|
||||
if (!this._mediaMatcher) {
|
||||
this._mediaMatcher = window.matchMedia(QUERY_MEDIA_COLOR_LIGHT);
|
||||
this._mediaMatcherHandler = (ev?: MediaQueryListEvent) => {
|
||||
const theme =
|
||||
ev?.matches || this._mediaMatcher?.matches
|
||||
? UiThemeEnum.Light
|
||||
: UiThemeEnum.Dark;
|
||||
this._activateTheme(theme, root);
|
||||
};
|
||||
this._mediaMatcherHandler(undefined);
|
||||
this._mediaMatcher.addEventListener("change", this._mediaMatcherHandler);
|
||||
}
|
||||
return;
|
||||
} else if (this._mediaMatcher && this._mediaMatcherHandler) {
|
||||
// Theme isn't automatic and we have a matcher configured, remove the matcher
|
||||
// to prevent changes
|
||||
this._mediaMatcher.removeEventListener("change", this._mediaMatcherHandler);
|
||||
this._mediaMatcher = undefined;
|
||||
}
|
||||
this._activateTheme(theme, root);
|
||||
}
|
||||
|
||||
static themeToStylesheet(theme?: UiThemeEnum): CSSStyleSheet | undefined {
|
||||
if (theme === UiThemeEnum.Dark) {
|
||||
return _darkTheme;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly activate a given theme, accepts multiple document/ShadowDOMs to apply the stylesheet
|
||||
* to. The stylesheets are applied to each DOM in order. Does nothing if the given theme is already active.
|
||||
*/
|
||||
_activateTheme(theme: UiThemeEnum, ...roots: DocumentOrShadowRoot[]) {
|
||||
if (theme === this._activeTheme) {
|
||||
return;
|
||||
}
|
||||
// Make sure we only get to this callback once we've picked a concise theme choice
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_THEME_CHANGE, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: theme,
|
||||
}),
|
||||
);
|
||||
this.setAttribute("theme", theme);
|
||||
const stylesheet = AKElement.themeToStylesheet(theme);
|
||||
const oldStylesheet = AKElement.themeToStylesheet(this._activeTheme);
|
||||
roots.forEach((root) => {
|
||||
if (stylesheet) {
|
||||
root.adoptedStyleSheets = [
|
||||
...root.adoptedStyleSheets,
|
||||
ensureCSSStyleSheet(stylesheet),
|
||||
];
|
||||
}
|
||||
if (oldStylesheet) {
|
||||
root.adoptedStyleSheets = root.adoptedStyleSheets.filter(
|
||||
(v) => v !== oldStylesheet,
|
||||
);
|
||||
}
|
||||
});
|
||||
this._activeTheme = theme;
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { ThemedElement } from "@goauthentik/common/theme";
|
||||
import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
|
||||
@ -10,12 +9,14 @@ import type { ReactiveController } from "lit";
|
||||
import type { CurrentBrand } from "@goauthentik/api";
|
||||
import { CoreApi } from "@goauthentik/api";
|
||||
|
||||
import type { AkInterface } from "./Interface";
|
||||
|
||||
export class BrandContextController implements ReactiveController {
|
||||
host!: ReactiveElementHost<ThemedElement>;
|
||||
host!: ReactiveElementHost<AkInterface>;
|
||||
|
||||
context!: ContextProvider<{ __context__: CurrentBrand | undefined }>;
|
||||
|
||||
constructor(host: ReactiveElementHost<ThemedElement>) {
|
||||
constructor(host: ReactiveElementHost<AkInterface>) {
|
||||
this.host = host;
|
||||
this.context = new ContextProvider(this.host, {
|
||||
context: authentikBrandContext,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { ThemedElement } from "@goauthentik/common/theme";
|
||||
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
|
||||
@ -11,12 +10,14 @@ import type { ReactiveController } from "lit";
|
||||
import type { Config } from "@goauthentik/api";
|
||||
import { RootApi } from "@goauthentik/api";
|
||||
|
||||
import type { AkInterface } from "./Interface";
|
||||
|
||||
export class ConfigContextController implements ReactiveController {
|
||||
host!: ReactiveElementHost<ThemedElement>;
|
||||
host!: ReactiveElementHost<AkInterface>;
|
||||
|
||||
context!: ContextProvider<{ __context__: Config | undefined }>;
|
||||
|
||||
constructor(host: ReactiveElementHost<ThemedElement>) {
|
||||
constructor(host: ReactiveElementHost<AkInterface>) {
|
||||
this.host = host;
|
||||
this.context = new ContextProvider(this.host, {
|
||||
context: authentikConfigContext,
|
||||
|
@ -1,78 +1,107 @@
|
||||
import {
|
||||
appendStyleSheet,
|
||||
createStyleSheetUnsafe,
|
||||
resolveStyleSheetParent,
|
||||
} from "@goauthentik/common/stylesheets";
|
||||
import { ThemedElement } from "@goauthentik/common/theme";
|
||||
import { UIConfig } from "@goauthentik/common/ui/config";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { UIConfig, uiConfig } from "@goauthentik/common/ui/config";
|
||||
import { VersionContextController } from "@goauthentik/elements/Interface/VersionContextController";
|
||||
import { ModalOrchestrationController } from "@goauthentik/elements/controllers/ModalOrchestrationController.js";
|
||||
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
|
||||
|
||||
import { state } from "lit/decorators.js";
|
||||
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import type { Config, CurrentBrand, LicenseSummary, Version } from "@goauthentik/api";
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
import { AKElement, rootInterface } from "../Base";
|
||||
import { BrandContextController } from "./BrandContextController";
|
||||
import { ConfigContextController } from "./ConfigContextController";
|
||||
import { EnterpriseContextController } from "./EnterpriseContextController";
|
||||
|
||||
export type AkInterface = HTMLElement & {
|
||||
getTheme: () => Promise<UiThemeEnum>;
|
||||
brand?: CurrentBrand;
|
||||
uiConfig?: UIConfig;
|
||||
config?: Config;
|
||||
};
|
||||
|
||||
const brandContext = Symbol("brandContext");
|
||||
const configContext = Symbol("configContext");
|
||||
const modalController = Symbol("modalController");
|
||||
const versionContext = Symbol("versionContext");
|
||||
|
||||
export abstract class Interface extends AKElement implements ThemedElement {
|
||||
protected static readonly PFBaseStyleSheet = createStyleSheetUnsafe(PFBase);
|
||||
export class Interface extends AKElement implements AkInterface {
|
||||
[brandContext]!: BrandContextController;
|
||||
|
||||
[configContext]: ConfigContextController;
|
||||
[configContext]!: ConfigContextController;
|
||||
|
||||
[modalController]: ModalOrchestrationController;
|
||||
[modalController]!: ModalOrchestrationController;
|
||||
|
||||
@state()
|
||||
public config?: Config;
|
||||
uiConfig?: UIConfig;
|
||||
|
||||
@state()
|
||||
public brand?: CurrentBrand;
|
||||
config?: Config;
|
||||
|
||||
@state()
|
||||
brand?: CurrentBrand;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const styleParent = resolveStyleSheetParent(document);
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)];
|
||||
this._initContexts();
|
||||
this.dataset.akInterfaceRoot = "true";
|
||||
}
|
||||
|
||||
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
|
||||
|
||||
appendStyleSheet(styleParent, Interface.PFBaseStyleSheet);
|
||||
|
||||
this.addController(new BrandContextController(this));
|
||||
_initContexts() {
|
||||
this[brandContext] = new BrandContextController(this);
|
||||
this[configContext] = new ConfigContextController(this);
|
||||
this[modalController] = new ModalOrchestrationController(this);
|
||||
}
|
||||
|
||||
_activateTheme(theme: UiThemeEnum, ...roots: DocumentOrShadowRoot[]): void {
|
||||
if (theme === this._activeTheme) {
|
||||
return;
|
||||
}
|
||||
console.debug(
|
||||
`authentik/interface[${rootInterface()?.tagName.toLowerCase()}]: Enabling theme ${theme}`,
|
||||
);
|
||||
// Special case for root interfaces, as they need to modify the global document CSS too
|
||||
// Instead of calling ._activateTheme() twice, we insert the root document in the call
|
||||
// since multiple calls to ._activateTheme() would not do anything after the first call
|
||||
// as the theme is already enabled.
|
||||
roots.unshift(document as unknown as DocumentOrShadowRoot);
|
||||
super._activateTheme(theme, ...roots);
|
||||
}
|
||||
|
||||
async getTheme(): Promise<UiThemeEnum> {
|
||||
if (!this.uiConfig) {
|
||||
this.uiConfig = await uiConfig();
|
||||
}
|
||||
return this.uiConfig.theme?.base || UiThemeEnum.Automatic;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AkAuthenticatedInterface extends ThemedElement {
|
||||
export type AkAuthenticatedInterface = AkInterface & {
|
||||
licenseSummary?: LicenseSummary;
|
||||
version?: Version;
|
||||
}
|
||||
};
|
||||
|
||||
const enterpriseContext = Symbol("enterpriseContext");
|
||||
|
||||
export class AuthenticatedInterface extends Interface implements AkAuthenticatedInterface {
|
||||
export class AuthenticatedInterface extends Interface {
|
||||
[enterpriseContext]!: EnterpriseContextController;
|
||||
[versionContext]!: VersionContextController;
|
||||
|
||||
@state()
|
||||
public uiConfig?: UIConfig;
|
||||
licenseSummary?: LicenseSummary;
|
||||
|
||||
@state()
|
||||
public licenseSummary?: LicenseSummary;
|
||||
|
||||
@state()
|
||||
public version?: Version;
|
||||
version?: Version;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
_initContexts(): void {
|
||||
super._initContexts();
|
||||
this[enterpriseContext] = new EnterpriseContextController(this);
|
||||
this[versionContext] = new VersionContextController(this);
|
||||
}
|
||||
|
@ -4,24 +4,21 @@ import {
|
||||
TITLE_DEFAULT,
|
||||
} from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { UIConfig, UserDisplay, getConfigForUser } from "@goauthentik/common/ui/config";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { currentInterface } from "@goauthentik/common/sentry";
|
||||
import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import "@goauthentik/components/ak-nav-buttons";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
import { isAdminRoute } from "@goauthentik/elements/router/utils";
|
||||
import { themeImage } from "@goauthentik/elements/utils/images";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, LitElement, TemplateResult, css, html, nothing } from "lit";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
|
||||
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
|
||||
import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
@ -29,52 +26,34 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { SessionUser } from "@goauthentik/api";
|
||||
|
||||
//#region Page Navbar
|
||||
|
||||
export interface PageNavbarDetails {
|
||||
header?: string;
|
||||
description?: string;
|
||||
@customElement("ak-page-header")
|
||||
export class PageHeader extends WithBrandConfig(AKElement) {
|
||||
@property()
|
||||
icon?: string;
|
||||
iconImage?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A global navbar component at the top of the page.
|
||||
*
|
||||
* Internally, this component listens for the `ak-page-header` event, which is
|
||||
* dispatched by the `ak-page-header` component.
|
||||
*/
|
||||
@customElement("ak-page-navbar")
|
||||
export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavbarDetails {
|
||||
//#region Static Properties
|
||||
@property({ type: Boolean })
|
||||
iconImage = false;
|
||||
|
||||
private static elementRef: AKPageNavbar | null = null;
|
||||
@property()
|
||||
header = "";
|
||||
|
||||
static readonly setNavbarDetails = (detail: Partial<PageNavbarDetails>): void => {
|
||||
const { elementRef } = AKPageNavbar;
|
||||
if (!elementRef) {
|
||||
console.debug(
|
||||
`ak-page-header: Could not find ak-page-navbar, skipping event dispatch.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@property()
|
||||
description?: string;
|
||||
|
||||
const { header, description, icon, iconImage } = detail;
|
||||
@property({ type: Boolean })
|
||||
hasIcon = true;
|
||||
|
||||
elementRef.header = header;
|
||||
elementRef.description = description;
|
||||
elementRef.icon = icon;
|
||||
elementRef.iconImage = iconImage || false;
|
||||
elementRef.hasIcon = !!icon;
|
||||
};
|
||||
@state()
|
||||
me?: SessionUser;
|
||||
|
||||
@state()
|
||||
uiConfig!: UIConfig;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFButton,
|
||||
PFPage,
|
||||
PFDrawer,
|
||||
|
||||
PFNotificationBadge,
|
||||
PFContent,
|
||||
PFAvatar,
|
||||
@ -84,403 +63,143 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavb
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--pf-global--ZIndex--lg);
|
||||
--pf-c-page__header-tools--MarginRight: 0;
|
||||
--ak-brand-logo-height: var(--pf-global--FontSize--4xl, 2.25rem);
|
||||
--ak-brand-background-color: var(
|
||||
--pf-c-page__sidebar--m-light--BackgroundColor
|
||||
);
|
||||
--host-navbar-height: var(--ak-c-page-header--height, 7.5rem);
|
||||
}
|
||||
|
||||
:host([theme="dark"]) {
|
||||
--ak-brand-background-color: var(--pf-c-page__sidebar--BackgroundColor);
|
||||
--pf-c-page__sidebar--BackgroundColor: var(--ak-dark-background-light);
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
|
||||
navbar {
|
||||
.bar {
|
||||
border-bottom: var(--pf-global--BorderWidth--sm);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--pf-global--BorderColor--100);
|
||||
background-color: var(--pf-c-page--BackgroundColor);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
display: grid;
|
||||
row-gap: var(--pf-global--spacer--sm);
|
||||
column-gap: var(--pf-global--spacer--sm);
|
||||
grid-template-columns: [brand] auto [toggle] auto [primary] 1fr [secondary] auto;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
"brand toggle primary secondary"
|
||||
"brand toggle description secondary";
|
||||
|
||||
@media (min-width: 426px) {
|
||||
height: var(--host-navbar-height);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
row-gap: var(--pf-global--spacer--xs);
|
||||
|
||||
align-items: center;
|
||||
grid-template-areas:
|
||||
"toggle primary secondary"
|
||||
"toggle description description";
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
min-height: 114px;
|
||||
max-height: 114px;
|
||||
background-color: var(--pf-c-page--BackgroundColor);
|
||||
}
|
||||
|
||||
.items {
|
||||
display: block;
|
||||
|
||||
&.primary {
|
||||
grid-column: primary;
|
||||
grid-row: primary / description;
|
||||
|
||||
align-content: center;
|
||||
padding-block: var(--pf-global--spacer--md);
|
||||
|
||||
@media (min-width: 426px) {
|
||||
&.block-sibling {
|
||||
padding-block-end: 0;
|
||||
grid-row: primary;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding-block: var(--pf-global--spacer--sm);
|
||||
}
|
||||
|
||||
.accent-icon {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.page-description {
|
||||
grid-area: description;
|
||||
margin-block-end: var(--pf-global--spacer--md);
|
||||
|
||||
display: box;
|
||||
display: -webkit-box;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
box-orient: vertical;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-width: 425px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
grid-area: secondary;
|
||||
flex: 0 0 auto;
|
||||
justify-self: end;
|
||||
padding-block: var(--pf-global--spacer--sm);
|
||||
padding-inline-end: var(--pf-global--spacer--sm);
|
||||
|
||||
@media (min-width: 769px) {
|
||||
align-content: center;
|
||||
padding-block: var(--pf-global--spacer--md);
|
||||
padding-inline-end: var(--pf-global--spacer--xl);
|
||||
}
|
||||
}
|
||||
.pf-c-page__main-section.pf-m-light {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.brand {
|
||||
grid-area: brand;
|
||||
background-color: var(--ak-brand-background-color);
|
||||
height: 100%;
|
||||
width: var(--pf-c-page__sidebar--Width);
|
||||
align-items: center;
|
||||
padding-inline: var(--pf-global--spacer--sm);
|
||||
|
||||
.pf-c-page__main-section {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
&.pf-m-collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-trigger {
|
||||
grid-area: toggle;
|
||||
height: 100%;
|
||||
img.pf-icon {
|
||||
max-height: 24px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
flex: 0 0 auto;
|
||||
height: var(--ak-brand-logo-height);
|
||||
|
||||
& img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-trigger,
|
||||
.notification-trigger {
|
||||
font-size: 1.5rem;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.notification-trigger.has-notifications {
|
||||
color: var(--pf-global--active-color--100);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: flex;
|
||||
gap: var(--pf-global--spacer--xs);
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center !important;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Properties
|
||||
|
||||
@property({ type: String })
|
||||
icon?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
iconImage = false;
|
||||
|
||||
@property({ type: String })
|
||||
header?: string;
|
||||
|
||||
@property({ type: String })
|
||||
description?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
hasIcon = true;
|
||||
|
||||
@property({ type: Boolean })
|
||||
open = true;
|
||||
|
||||
@state()
|
||||
session?: SessionUser;
|
||||
|
||||
@state()
|
||||
uiConfig!: UIConfig;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Private Methods
|
||||
|
||||
#setTitle(header?: string) {
|
||||
let title = this.brand?.brandingTitle || TITLE_DEFAULT;
|
||||
|
||||
if (isAdminRoute()) {
|
||||
title = `${msg("Admin")} - ${title}`;
|
||||
}
|
||||
// Prepend the header to the title
|
||||
if (header) {
|
||||
title = `${header} - ${title}`;
|
||||
}
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
#toggleSidebar() {
|
||||
this.open = !this.open;
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
AKPageNavbar.elementRef = this;
|
||||
|
||||
window.addEventListener(EVENT_WS_MESSAGE, () => {
|
||||
this.firstUpdated();
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
AKPageNavbar.elementRef = null;
|
||||
}
|
||||
|
||||
public async firstUpdated() {
|
||||
this.session = await me();
|
||||
this.uiConfig = getConfigForUser(this.session.user);
|
||||
this.uiConfig.navbar.userDisplay = UserDisplay.none;
|
||||
}
|
||||
|
||||
willUpdate() {
|
||||
// Always update title, even if there's no header value set,
|
||||
// as in that case we still need to return to the generic title
|
||||
this.#setTitle(this.header);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Render
|
||||
|
||||
renderIcon() {
|
||||
if (this.icon) {
|
||||
if (this.iconImage && !this.icon.startsWith("fa://")) {
|
||||
return html`<img class="accent-icon pf-icon" src="${this.icon}" alt="page icon" />`;
|
||||
}
|
||||
|
||||
const icon = this.icon.replaceAll("fa://", "fa ");
|
||||
|
||||
return html`<i class="accent-icon ${icon}"></i>`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<navbar aria-label="Main" class="navbar">
|
||||
<aside class="brand ${this.open ? "" : "pf-m-collapsed"}">
|
||||
<a href="#/">
|
||||
<div class="logo">
|
||||
<img
|
||||
src=${themeImage(
|
||||
this.brand?.brandingLogo ?? DefaultBrand.brandingLogo,
|
||||
)}
|
||||
alt="${msg("authentik Logo")}"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</aside>
|
||||
<button
|
||||
class="sidebar-trigger pf-c-button pf-m-plain"
|
||||
@click=${this.#toggleSidebar}
|
||||
aria-label=${msg("Toggle sidebar")}
|
||||
aria-expanded=${this.open ? "true" : "false"}
|
||||
>
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
|
||||
<section
|
||||
class="items primary pf-c-content ${this.description ? "block-sibling" : ""}"
|
||||
>
|
||||
<h1 class="page-title">
|
||||
${this.hasIcon
|
||||
? html`<slot name="icon">${this.renderIcon()}</slot>`
|
||||
: nothing}
|
||||
${this.header}
|
||||
</h1>
|
||||
</section>
|
||||
${this.description
|
||||
? html`<section class="items page-description pf-c-content">
|
||||
<p>${this.description}</p>
|
||||
</section>`
|
||||
: nothing}
|
||||
|
||||
<section class="items secondary">
|
||||
<div class="pf-c-page__header-tools-group">
|
||||
<ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.session}>
|
||||
<a
|
||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
||||
href="${globalAK().api.base}if/user/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("User interface")}
|
||||
</a>
|
||||
</ak-nav-buttons>
|
||||
</div>
|
||||
</section>
|
||||
</navbar>
|
||||
<slot></slot>`;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Page Header
|
||||
|
||||
/**
|
||||
* A page header component, used to display the page title and description.
|
||||
*
|
||||
* Internally, this component dispatches the `ak-page-header` event, which is
|
||||
* listened to by the `ak-page-navbar` component.
|
||||
*
|
||||
* @singleton
|
||||
*/
|
||||
@customElement("ak-page-header")
|
||||
export class AKPageHeader extends LitElement implements PageNavbarDetails {
|
||||
@property({ type: String })
|
||||
header?: string;
|
||||
|
||||
@property({ type: String })
|
||||
description?: string;
|
||||
|
||||
@property({ type: String })
|
||||
icon?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
iconImage = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
:host {
|
||||
display: none;
|
||||
.pf-c-page__header-tools {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.pf-c-page__header-tools-group {
|
||||
height: 100%;
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-page__header-tools {
|
||||
color: var(--ak-dark-foreground) !important;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
AKPageNavbar.setNavbarDetails({
|
||||
header: this.header,
|
||||
description: this.description,
|
||||
icon: this.icon,
|
||||
iconImage: this.iconImage,
|
||||
constructor() {
|
||||
super();
|
||||
window.addEventListener(EVENT_WS_MESSAGE, () => {
|
||||
this.firstUpdated();
|
||||
});
|
||||
}
|
||||
|
||||
updated(): void {
|
||||
AKPageNavbar.setNavbarDetails({
|
||||
header: this.header,
|
||||
description: this.description,
|
||||
icon: this.icon,
|
||||
iconImage: this.iconImage,
|
||||
});
|
||||
async firstUpdated() {
|
||||
this.me = await me();
|
||||
this.uiConfig = await uiConfig();
|
||||
this.uiConfig.navbar.userDisplay = UserDisplay.none;
|
||||
}
|
||||
|
||||
setTitle(header?: string) {
|
||||
const currentIf = currentInterface();
|
||||
let title = this.brand?.brandingTitle || TITLE_DEFAULT;
|
||||
if (currentIf === "admin") {
|
||||
title = `${msg("Admin")} - ${title}`;
|
||||
}
|
||||
// Prepend the header to the title
|
||||
if (header !== undefined && header !== "") {
|
||||
title = `${header} - ${title}`;
|
||||
}
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
willUpdate() {
|
||||
// Always update title, even if there's no header value set,
|
||||
// as in that case we still need to return to the generic title
|
||||
this.setTitle(this.header);
|
||||
}
|
||||
|
||||
renderIcon() {
|
||||
if (this.icon) {
|
||||
if (this.iconImage && !this.icon.startsWith("fa://")) {
|
||||
return html`<img class="pf-icon" src="${this.icon}" alt="page icon" />`;
|
||||
}
|
||||
const icon = this.icon.replaceAll("fa://", "fa ");
|
||||
return html`<i class=${icon}></i>`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="bar">
|
||||
<button
|
||||
class="sidebar-trigger pf-c-button pf-m-plain"
|
||||
@click=${() => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<h1>
|
||||
${this.hasIcon
|
||||
? html`<slot name="icon">${this.renderIcon()}</slot> `
|
||||
: nothing}
|
||||
<slot name="header">${this.header}</slot>
|
||||
</h1>
|
||||
${this.description ? html`<p>${this.description}</p>` : html``}
|
||||
</div>
|
||||
</section>
|
||||
<div class="pf-c-page__header-tools">
|
||||
<div class="pf-c-page__header-tools-group">
|
||||
<ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.me}>
|
||||
<a
|
||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
||||
href="${globalAK().api.base}if/user/"
|
||||
slot="extra"
|
||||
>
|
||||
${msg("User interface")}
|
||||
</a>
|
||||
</ak-nav-buttons>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-page-header": AKPageHeader;
|
||||
"ak-page-navbar": AKPageNavbar;
|
||||
"ak-page-header": PageHeader;
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @file Utilities for working with the client-side page router.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The name identifier for the current interface.
|
||||
*/
|
||||
export type RouteInterfaceName = "user" | "admin" | "flow" | "unknown";
|
||||
|
||||
/**
|
||||
* Read the current interface route parameter from the URL.
|
||||
*
|
||||
* @param location - The location object to read the pathname from. Defaults to `window.location`.
|
||||
* * @returns The name of the current interface, or "unknown" if not found.
|
||||
*/
|
||||
export function readInterfaceRouteParam(
|
||||
location: Pick<URL, "pathname"> = window.location,
|
||||
): RouteInterfaceName {
|
||||
const [, currentInterface = "unknown"] = location.pathname.match(/.+if\/(\w+)\//) || [];
|
||||
|
||||
return currentInterface.toLowerCase() as RouteInterfaceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate to determine if the current route is for the admin interface.
|
||||
*/
|
||||
export function isAdminRoute(location: Pick<URL, "pathname"> = window.location): boolean {
|
||||
return readInterfaceRouteParam(location) === "admin";
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate to determine if the current route is for the user interface.
|
||||
*/
|
||||
export function isUserRoute(location: Pick<URL, "pathname"> = window.location): boolean {
|
||||
return readInterfaceRouteParam(location) === "user";
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/sidebar/SidebarBrand";
|
||||
import "@goauthentik/elements/sidebar/SidebarVersion";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
@ -21,7 +22,6 @@ export class Sidebar extends AKElement {
|
||||
css`
|
||||
:host {
|
||||
z-index: 100;
|
||||
--pf-c-page__sidebar--Transition: 0 !important;
|
||||
}
|
||||
.pf-c-nav__link.pf-m-current::after,
|
||||
.pf-c-nav__link.pf-m-current:hover::after,
|
||||
@ -35,7 +35,10 @@ export class Sidebar extends AKElement {
|
||||
.pf-c-nav__section + .pf-c-nav__section {
|
||||
--pf-c-nav__section--section--MarginTop: var(--pf-global--spacer--sm);
|
||||
}
|
||||
|
||||
.pf-c-nav__list .sidebar-brand {
|
||||
max-height: 82px;
|
||||
margin-bottom: -0.5rem;
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -67,6 +70,7 @@ export class Sidebar extends AKElement {
|
||||
class="pf-c-nav ${this.activeTheme === UiThemeEnum.Light ? "pf-m-light" : ""}"
|
||||
aria-label=${msg("Global")}
|
||||
>
|
||||
<ak-sidebar-brand></ak-sidebar-brand>
|
||||
<ul class="pf-c-nav__list">
|
||||
<slot></slot>
|
||||
</ul>
|
||||
|
106
web/src/elements/sidebar/SidebarBrand.ts
Normal file
106
web/src/elements/sidebar/SidebarBrand.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
import { themeImage } from "@goauthentik/elements/utils/images";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGlobal from "@patternfly/patternfly/patternfly-base.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
// If the viewport is wider than MIN_WIDTH, the sidebar
|
||||
// is shown besides the content, and not overlaid.
|
||||
export const MIN_WIDTH = 1200;
|
||||
|
||||
export const DefaultBrand: CurrentBrand = {
|
||||
brandingLogo: "/static/dist/assets/icons/icon_left_brand.svg",
|
||||
brandingFavicon: "/static/dist/assets/icons/icon.png",
|
||||
brandingTitle: "authentik",
|
||||
brandingCustomCss: "",
|
||||
uiFooterLinks: [],
|
||||
uiTheme: UiThemeEnum.Automatic,
|
||||
matchedDomain: "",
|
||||
defaultLocale: "",
|
||||
};
|
||||
|
||||
@customElement("ak-sidebar-brand")
|
||||
export class SidebarBrand extends WithBrandConfig(AKElement) {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFGlobal,
|
||||
PFPage,
|
||||
PFButton,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 114px;
|
||||
min-height: 114px;
|
||||
border-bottom: var(--pf-global--BorderWidth--sm);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--pf-global--BorderColor--100);
|
||||
}
|
||||
.pf-c-brand img {
|
||||
padding: 0 0.5rem;
|
||||
height: 42px;
|
||||
}
|
||||
button.pf-c-button.sidebar-trigger {
|
||||
background-color: transparent;
|
||||
border-radius: 0px;
|
||||
height: 100%;
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
window.addEventListener("resize", () => {
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html` ${window.innerWidth <= MIN_WIDTH
|
||||
? html`
|
||||
<button
|
||||
class="sidebar-trigger pf-c-button"
|
||||
@click=${() => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
`
|
||||
: html``}
|
||||
<a href="#/" class="pf-c-page__header-brand-link">
|
||||
<div class="pf-c-brand ak-brand">
|
||||
<img
|
||||
src=${themeImage(this.brand?.brandingLogo ?? DefaultBrand.brandingLogo)}
|
||||
alt="${msg("authentik Logo")}"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-sidebar-brand": SidebarBrand;
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import type { AdminInterface } from "@goauthentik/admin/AdminInterface/index.entrypoint.js";
|
||||
import type { AdminInterface } from "@goauthentik/admin/AdminInterface/AdminInterface";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||
import { AKElement, rootInterface } from "@goauthentik/elements/Base";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
import { WithVersion } from "@goauthentik/elements/Interface/versionProvider";
|
||||
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, css, html, nothing } from "lit";
|
||||
|
@ -1,21 +1,19 @@
|
||||
import {
|
||||
appendStyleSheet,
|
||||
assertAdoptableStyleSheetParent,
|
||||
createStyleSheetUnsafe,
|
||||
} from "@goauthentik/common/stylesheets.js";
|
||||
|
||||
import { TemplateResult, render as litRender } from "lit";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { ensureCSSStyleSheet } from "../utils/ensureCSSStyleSheet.js";
|
||||
|
||||
// A special version of render that ensures our style sheets will always be available
|
||||
// to all elements under test. Ensures they look right during testing, and that any
|
||||
// CSS-based checks for visibility will return correct values.
|
||||
|
||||
export const render = (body: TemplateResult) => {
|
||||
assertAdoptableStyleSheetParent(document);
|
||||
|
||||
appendStyleSheet(document, ...[PFBase, AKGlobal].map(createStyleSheetUnsafe));
|
||||
document.adoptedStyleSheets = [
|
||||
...document.adoptedStyleSheets,
|
||||
ensureCSSStyleSheet(PFBase),
|
||||
ensureCSSStyleSheet(AKGlobal),
|
||||
];
|
||||
return litRender(body, document.body);
|
||||
};
|
||||
|
@ -1,14 +1,9 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
|
||||
import { type LitElement, type ReactiveControllerHost, type TemplateResult, nothing } from "lit";
|
||||
import "lit";
|
||||
|
||||
/**
|
||||
* A custom element which may be used as a host for a ReactiveController.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This type is derived from an internal type in Lit.
|
||||
*/
|
||||
export type ReactiveElementHost<T> = Partial<ReactiveControllerHost & T> & HTMLElement;
|
||||
export type ReactiveElementHost<T = AKElement> = Partial<ReactiveControllerHost> & T;
|
||||
|
||||
export type AbstractLitElementConstructor = abstract new (...args: never[]) => LitElement;
|
||||
|
||||
|
35
web/src/elements/utils/ensureCSSStyleSheet.ts
Normal file
35
web/src/elements/utils/ensureCSSStyleSheet.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { CSSResult, unsafeCSS } from "lit";
|
||||
|
||||
const supportsAdoptingStyleSheets: boolean =
|
||||
window.ShadowRoot &&
|
||||
(window.ShadyCSS === undefined || window.ShadyCSS.nativeShadow) &&
|
||||
"adoptedStyleSheets" in Document.prototype &&
|
||||
"replace" in CSSStyleSheet.prototype;
|
||||
|
||||
function stringToStylesheet(css: string) {
|
||||
if (supportsAdoptingStyleSheets) {
|
||||
const sheet = unsafeCSS(css).styleSheet;
|
||||
if (sheet === undefined) {
|
||||
throw new Error(
|
||||
`CSS processing error: undefined stylesheet from string. Source: ${css}`,
|
||||
);
|
||||
}
|
||||
return sheet;
|
||||
}
|
||||
|
||||
const sheet = new CSSStyleSheet();
|
||||
sheet.replaceSync(css);
|
||||
return sheet;
|
||||
}
|
||||
|
||||
function cssResultToStylesheet(css: CSSResult) {
|
||||
const sheet = css.styleSheet;
|
||||
return sheet ? sheet : stringToStylesheet(css.toString());
|
||||
}
|
||||
|
||||
export const ensureCSSStyleSheet = (css: string | CSSStyleSheet | CSSResult): CSSStyleSheet =>
|
||||
css instanceof CSSResult
|
||||
? cssResultToStylesheet(css)
|
||||
: typeof css === "string"
|
||||
? stringToStylesheet(css)
|
||||
: css;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user