Compare commits
120 Commits
docusaurus
...
workspace-
| Author | SHA1 | Date | |
|---|---|---|---|
| 783b1b0a79 | |||
| 7c69add264 | |||
| 248fcd5d7f | |||
| 2c64e3f9ba | |||
| 99b559893b | |||
| 8014088c3a | |||
| 3ee353126f | |||
| db76c5d9e2 | |||
| 61bff69b7d | |||
| 69651323e3 | |||
| 75a0ac9588 | |||
| 941a697397 | |||
| 4a74db17a1 | |||
| 0cf6bff93c | |||
| 814e438422 | |||
| 2db77a37dd | |||
| e40c5ac617 | |||
| 7440900dac | |||
| ca96b27825 | |||
| ad4a765a80 | |||
| 4dcd481010 | |||
| d0dc14d84d | |||
| 7bf960352b | |||
| c07d01661b | |||
| 427597ec14 | |||
| 7cc77bd387 | |||
| 381a1a2c49 | |||
| 08f8222224 | |||
| 1211c34a18 | |||
| 22efb57369 | |||
| 3eeda53be6 | |||
| 82ace18703 | |||
| 8589079252 | |||
| ae2af6e58e | |||
| 86a7f98ff6 | |||
| 3af45371d3 | |||
| b01ffd934f | |||
| f11ba94603 | |||
| 7d2aa43364 | |||
| f1351a7577 | |||
| 0611eea0e7 | |||
| d0b46fcf9c | |||
| dcbdc37d31 | |||
| d07f396379 | |||
| 0972103b83 | |||
| b448e76db4 | |||
| f2937bd6dd | |||
| 53c2e3e77c | |||
| 7dd62c1f55 | |||
| 33e3510fba | |||
| 0e5fac2642 | |||
| c53b1fe78a | |||
| 838a7457b2 | |||
| a3c07bc9ff | |||
| 121f2c609d | |||
| 365affc28e | |||
| f367822779 | |||
| 848198125d | |||
| 497ac5e3d0 | |||
| 1773d4d681 | |||
| 4edbb51939 | |||
| c7e97ab48e | |||
| 31f7faae1c | |||
| f5dae2ae92 | |||
| 2c043dba0b | |||
| bda10e5db1 | |||
| be9ae7d4f7 | |||
| b4a6189bfa | |||
| bfdb827ff9 | |||
| 488a58e1c5 | |||
| 3f83e69453 | |||
| e92fa5df0b | |||
| f8c22170df | |||
| e3d08a8434 | |||
| 97d3e9afdc | |||
| 1eb08def73 | |||
| 6e3b379e4a | |||
| 264f59775c | |||
| d048f1ecbd | |||
| eb31f31584 | |||
| fe5c842e92 | |||
| b82d3100c9 | |||
| 49bb668036 | |||
| 52c70c7700 | |||
| b99fd36f86 | |||
| 8a5381eca3 | |||
| 2c77830179 | |||
| ffcd7def60 | |||
| ed121bc2a3 | |||
| d5ab9d9167 | |||
| a983321ad6 | |||
| 9c3420ede4 | |||
| 91b40350aa | |||
| 1912991682 | |||
| 71b9117f53 | |||
| b5f947f460 | |||
| 3a2f7e9549 | |||
| 1582ce0920 | |||
| 6d3eea5266 | |||
| e987208bd1 | |||
| 0efab8eef7 | |||
| 9402dac8ae | |||
| f57a290eee | |||
| 5dab0d2b7a | |||
| 2da6036248 | |||
| cdba94cea4 | |||
| c59eca664a | |||
| d5b205f9c0 | |||
| 8ad9ad833e | |||
| 599ce15f68 | |||
| 91310eff52 | |||
| b522d6732a | |||
| 17d96f204e | |||
| 65e4667bc3 | |||
| f67f9e5ed0 | |||
| 62dd6a4393 | |||
| a46eae8276 | |||
| c4acc9fc24 | |||
| e748a03082 | |||
| e473f28e21 |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2025.4.0
|
current_version = 2025.4.1
|
||||||
tag = True
|
tag = True
|
||||||
commit = 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*))?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||||
|
|||||||
3
.github/workflows/ci-main.yml
vendored
3
.github/workflows/ci-main.yml
vendored
@ -200,7 +200,7 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: web/dist
|
path: web/dist
|
||||||
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }}
|
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
|
||||||
- name: prepare web ui
|
- name: prepare web ui
|
||||||
if: steps.cache-web.outputs.cache-hit != 'true'
|
if: steps.cache-web.outputs.cache-hit != 'true'
|
||||||
working-directory: web
|
working-directory: web
|
||||||
@ -208,6 +208,7 @@ jobs:
|
|||||||
npm ci
|
npm ci
|
||||||
make -C .. gen-client-ts
|
make -C .. gen-client-ts
|
||||||
npm run build
|
npm run build
|
||||||
|
npm run build:sfe
|
||||||
- name: run e2e
|
- name: run e2e
|
||||||
run: |
|
run: |
|
||||||
uv run coverage run manage.py test ${{ matrix.job.glob }}
|
uv run coverage run manage.py test ${{ matrix.job.glob }}
|
||||||
|
|||||||
2
.github/workflows/ci-outpost.yml
vendored
2
.github/workflows/ci-outpost.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
- name: Generate API
|
- name: Generate API
|
||||||
run: make gen-client-go
|
run: make gen-client-go
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v7
|
uses: golangci/golangci-lint-action@v8
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --timeout 5000s --verbose
|
args: --timeout 5000s --verbose
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@ -40,7 +40,8 @@ COPY ./web /work/web/
|
|||||||
COPY ./website /work/website/
|
COPY ./website /work/website/
|
||||||
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
|
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
|
# Stage 3: Build go proxy
|
||||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
|
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
|
||||||
@ -85,18 +86,17 @@ FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.0 AS geoip
|
|||||||
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
|
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
|
||||||
ENV GEOIPUPDATE_VERBOSE="1"
|
ENV GEOIPUPDATE_VERBOSE="1"
|
||||||
ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID"
|
ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID"
|
||||||
ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY"
|
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||||
--mount=type=secret,id=GEOIPUPDATE_LICENSE_KEY \
|
--mount=type=secret,id=GEOIPUPDATE_LICENSE_KEY \
|
||||||
mkdir -p /usr/share/GeoIP && \
|
mkdir -p /usr/share/GeoIP && \
|
||||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||||
|
|
||||||
# Stage 5: Download uv
|
# Stage 5: Download uv
|
||||||
FROM ghcr.io/astral-sh/uv:0.7.2 AS uv
|
FROM ghcr.io/astral-sh/uv:0.7.4 AS uv
|
||||||
# Stage 6: Base python image
|
# Stage 6: Base python image
|
||||||
FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base
|
FROM ghcr.io/goauthentik/fips-python:3.13.3-slim-bookworm-fips AS python-base
|
||||||
|
|
||||||
ENV VENV_PATH="/ak-root/.venv" \
|
ENV VENV_PATH="/ak-root/.venv" \
|
||||||
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
|
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
|
||||||
|
|||||||
@ -42,4 +42,4 @@ See [SECURITY.md](SECURITY.md)
|
|||||||
|
|
||||||
## Adoption and Contributions
|
## Adoption and Contributions
|
||||||
|
|
||||||
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [CONTRIBUTING.md file](./CONTRIBUTING.md).
|
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [contribution guide](https://docs.goauthentik.io/docs/developer-docs?utm_source=github).
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
__version__ = "2025.4.0"
|
__version__ = "2025.4.1"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,7 @@ def create_component(generator: SchemaGenerator, name, schema, type_=ResolvedCom
|
|||||||
return component
|
return component
|
||||||
|
|
||||||
|
|
||||||
def postprocess_schema_responses(result, generator: SchemaGenerator, **kwargs): # noqa: W0613
|
def postprocess_schema_responses(result, generator: SchemaGenerator, **kwargs):
|
||||||
"""Workaround to set a default response for endpoints.
|
"""Workaround to set a default response for endpoints.
|
||||||
Workaround suggested at
|
Workaround suggested at
|
||||||
<https://github.com/tfranzel/drf-spectacular/issues/119#issuecomment-656970357>
|
<https://github.com/tfranzel/drf-spectacular/issues/119#issuecomment-656970357>
|
||||||
|
|||||||
@ -164,9 +164,7 @@ class BlueprintEntry:
|
|||||||
"""Get the blueprint model, with yaml tags resolved if present"""
|
"""Get the blueprint model, with yaml tags resolved if present"""
|
||||||
return str(self.tag_resolver(self.model, blueprint))
|
return str(self.tag_resolver(self.model, blueprint))
|
||||||
|
|
||||||
def get_permissions(
|
def get_permissions(self, blueprint: "Blueprint") -> Generator[BlueprintEntryPermission]:
|
||||||
self, blueprint: "Blueprint"
|
|
||||||
) -> Generator[BlueprintEntryPermission, None, None]:
|
|
||||||
"""Get permissions of this entry, with all yaml tags resolved"""
|
"""Get permissions of this entry, with all yaml tags resolved"""
|
||||||
for perm in self.permissions:
|
for perm in self.permissions:
|
||||||
yield BlueprintEntryPermission(
|
yield BlueprintEntryPermission(
|
||||||
|
|||||||
@ -5,10 +5,10 @@ from typing import Any
|
|||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
from django.db.models import Value as V
|
from django.db.models import Value as V
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
from sentry_sdk import get_current_span
|
|
||||||
|
|
||||||
from authentik import get_full_version
|
from authentik import get_full_version
|
||||||
from authentik.brands.models import Brand
|
from authentik.brands.models import Brand
|
||||||
|
from authentik.lib.sentry import get_http_meta
|
||||||
from authentik.tenants.models import Tenant
|
from authentik.tenants.models import Tenant
|
||||||
|
|
||||||
_q_default = Q(default=True)
|
_q_default = Q(default=True)
|
||||||
@ -32,13 +32,9 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
|
|||||||
"""Context Processor that injects brand object into every template"""
|
"""Context Processor that injects brand object into every template"""
|
||||||
brand = getattr(request, "brand", DEFAULT_BRAND)
|
brand = getattr(request, "brand", DEFAULT_BRAND)
|
||||||
tenant = getattr(request, "tenant", Tenant())
|
tenant = getattr(request, "tenant", Tenant())
|
||||||
trace = ""
|
|
||||||
span = get_current_span()
|
|
||||||
if span:
|
|
||||||
trace = span.to_traceparent()
|
|
||||||
return {
|
return {
|
||||||
"brand": brand,
|
"brand": brand,
|
||||||
"footer_links": tenant.footer_links,
|
"footer_links": tenant.footer_links,
|
||||||
"sentry_trace": trace,
|
"html_meta": {**get_http_meta()},
|
||||||
"version": get_full_version(),
|
"version": get_full_version(),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,18 +99,17 @@ class GroupSerializer(ModelSerializer):
|
|||||||
if superuser
|
if superuser
|
||||||
else "authentik_core.disable_group_superuser"
|
else "authentik_core.disable_group_superuser"
|
||||||
)
|
)
|
||||||
has_perm = user.has_perm(perm)
|
if self.instance or superuser:
|
||||||
if self.instance and not has_perm:
|
has_perm = user.has_perm(perm) or user.has_perm(perm, self.instance)
|
||||||
has_perm = user.has_perm(perm, self.instance)
|
if not has_perm:
|
||||||
if not has_perm:
|
raise ValidationError(
|
||||||
raise ValidationError(
|
_(
|
||||||
_(
|
(
|
||||||
(
|
"User does not have permission to set "
|
||||||
"User does not have permission to set "
|
"superuser status to {superuser_status}."
|
||||||
"superuser status to {superuser_status}."
|
).format_map({"superuser_status": superuser})
|
||||||
).format_map({"superuser_status": superuser})
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
return superuser
|
return superuser
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.contrib.auth.management import create_permissions
|
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 django.core.management.base import BaseCommand, no_translations
|
||||||
from guardian.management import create_anonymous_user
|
from guardian.management import create_anonymous_user
|
||||||
|
|
||||||
@ -16,6 +17,10 @@ class Command(BaseCommand):
|
|||||||
"""Check permissions for all apps"""
|
"""Check permissions for all apps"""
|
||||||
for tenant in Tenant.objects.filter(ready=True):
|
for tenant in Tenant.objects.filter(ready=True):
|
||||||
with tenant:
|
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():
|
for app in apps.get_app_configs():
|
||||||
self.stdout.write(f"Checking app {app.name} ({app.label})\n")
|
self.stdout.write(f"Checking app {app.name} ({app.label})\n")
|
||||||
create_permissions(app, verbosity=0)
|
create_permissions(app, verbosity=0)
|
||||||
|
|||||||
@ -31,7 +31,10 @@ class PickleSerializer:
|
|||||||
|
|
||||||
def loads(self, data):
|
def loads(self, data):
|
||||||
"""Unpickle data to be loaded from redis"""
|
"""Unpickle data to be loaded from redis"""
|
||||||
return pickle.loads(data) # nosec
|
try:
|
||||||
|
return pickle.loads(data) # nosec
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def _migrate_session(
|
def _migrate_session(
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.1.9 on 2025-05-14 11:15
|
||||||
|
|
||||||
|
from django.apps.registry import Apps
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
|
||||||
|
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=remove_old_authenticated_session_content_type,
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -21,7 +21,9 @@
|
|||||||
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
{% for key, value in html_meta.items %}
|
||||||
|
<meta name="{{key}}" content="{{ value }}" />
|
||||||
|
{% endfor %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|||||||
@ -124,6 +124,16 @@ class TestGroupsAPI(APITestCase):
|
|||||||
{"is_superuser": ["User does not have permission to set superuser status to True."]},
|
{"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):
|
def test_superuser_update_no_perm(self):
|
||||||
"""Test updating a superuser group without permission"""
|
"""Test updating a superuser group without permission"""
|
||||||
group = Group.objects.create(name=generate_id(), is_superuser=True)
|
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"""
|
"""Get a summarized version of all (not expired) licenses"""
|
||||||
total = LicenseKey(get_license_aud(), 0, "Summarized license", 0, 0)
|
total = LicenseKey(get_license_aud(), 0, "Summarized license", 0, 0)
|
||||||
for lic in License.objects.all():
|
for lic in License.objects.all():
|
||||||
total.internal_users += lic.internal_users
|
if lic.is_valid:
|
||||||
total.external_users += lic.external_users
|
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()))
|
exp_ts = int(mktime(lic.expiry.timetuple()))
|
||||||
if total.exp == 0:
|
if total.exp == 0:
|
||||||
total.exp = exp_ts
|
total.exp = exp_ts
|
||||||
total.exp = max(total.exp, exp_ts)
|
total.exp = max(total.exp, exp_ts)
|
||||||
total.license_flags.extend(lic.status.license_flags)
|
|
||||||
return total
|
return total
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -39,6 +39,10 @@ class License(SerializerModel):
|
|||||||
internal_users = models.BigIntegerField()
|
internal_users = models.BigIntegerField()
|
||||||
external_users = models.BigIntegerField()
|
external_users = models.BigIntegerField()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
return self.expiry >= now()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[BaseSerializer]:
|
def serializer(self) -> type[BaseSerializer]:
|
||||||
from authentik.enterprise.api import LicenseSerializer
|
from authentik.enterprise.api import LicenseSerializer
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from django.test import TestCase
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from authentik.core.models import User
|
||||||
from authentik.enterprise.license import LicenseKey
|
from authentik.enterprise.license import LicenseKey
|
||||||
from authentik.enterprise.models import (
|
from authentik.enterprise.models import (
|
||||||
THRESHOLD_READ_ONLY_WEEKS,
|
THRESHOLD_READ_ONLY_WEEKS,
|
||||||
@ -71,9 +72,9 @@ class TestEnterpriseLicense(TestCase):
|
|||||||
)
|
)
|
||||||
def test_valid_multiple(self):
|
def test_valid_multiple(self):
|
||||||
"""Check license verification"""
|
"""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)
|
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)
|
self.assertTrue(lic2.status.status().is_valid)
|
||||||
total = LicenseKey.get_total()
|
total = LicenseKey.get_total()
|
||||||
self.assertEqual(total.internal_users, 200)
|
self.assertEqual(total.internal_users, 200)
|
||||||
@ -232,7 +233,9 @@ class TestEnterpriseLicense(TestCase):
|
|||||||
)
|
)
|
||||||
def test_expiry_expired(self):
|
def test_expiry_expired(self):
|
||||||
"""Check license verification"""
|
"""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)
|
self.assertEqual(LicenseKey.get_total().summary().status, LicenseUsageStatus.EXPIRED)
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
|
|||||||
@ -57,7 +57,7 @@ class LogEventSerializer(PassiveSerializer):
|
|||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def capture_logs(log_default_output=True) -> Generator[list[LogEvent], None, None]:
|
def capture_logs(log_default_output=True) -> Generator[list[LogEvent]]:
|
||||||
"""Capture log entries created"""
|
"""Capture log entries created"""
|
||||||
logs = []
|
logs = []
|
||||||
cap = LogCapture()
|
cap = LogCapture()
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
|
||||||
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
||||||
|
<link rel="prefetch" href="{{ flow_background_url }}" />
|
||||||
{% include "base/header_js.html" %}
|
{% include "base/header_js.html" %}
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
@ -22,7 +23,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
background-image: url("{{ flow.background_url }}");
|
background-image: url("{{ flow_background_url }}");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
{% block head_before %}
|
{% block head_before %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<link rel="prefetch" href="{{ flow.background_url }}" />
|
<link rel="prefetch" href="{{ flow_background_url }}" />
|
||||||
{% if flow.compatibility_mode and not inspector %}
|
{% if flow.compatibility_mode and not inspector %}
|
||||||
<script>ShadyDOM = { force: !navigator.webdriver };</script>
|
<script>ShadyDOM = { force: true };</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "base/header_js.html" %}
|
{% include "base/header_js.html" %}
|
||||||
<script>
|
<script>
|
||||||
@ -21,7 +21,7 @@ window.authentik.flow = {
|
|||||||
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
|
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--ak-flow-background: url("{{ flow.background_url }}");
|
--ak-flow-background: url("{{ flow_background_url }}");
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -13,7 +13,9 @@ class FlowInterfaceView(InterfaceView):
|
|||||||
"""Flow interface"""
|
"""Flow interface"""
|
||||||
|
|
||||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
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
|
kwargs["inspector"] = "inspector" in self.request.GET
|
||||||
return super().get_context_data(**kwargs)
|
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)
|
pool_options = config.get_dict_from_b64_json("postgresql.pool_options", True)
|
||||||
if not pool_options:
|
if not pool_options:
|
||||||
pool_options = True
|
pool_options = True
|
||||||
|
# FIXME: Temporarily force pool to be deactivated.
|
||||||
|
# See https://github.com/goauthentik/authentik/issues/14320
|
||||||
|
pool_options = False
|
||||||
|
|
||||||
db = {
|
db = {
|
||||||
"default": {
|
"default": {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ from ldap3.core.exceptions import LDAPException
|
|||||||
from redis.exceptions import ConnectionError as RedisConnectionError
|
from redis.exceptions import ConnectionError as RedisConnectionError
|
||||||
from redis.exceptions import RedisError, ResponseError
|
from redis.exceptions import RedisError, ResponseError
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from sentry_sdk import HttpTransport
|
from sentry_sdk import HttpTransport, get_current_scope
|
||||||
from sentry_sdk import init as sentry_sdk_init
|
from sentry_sdk import init as sentry_sdk_init
|
||||||
from sentry_sdk.api import set_tag
|
from sentry_sdk.api import set_tag
|
||||||
from sentry_sdk.integrations.argv import ArgvIntegration
|
from sentry_sdk.integrations.argv import ArgvIntegration
|
||||||
@ -27,6 +27,7 @@ from sentry_sdk.integrations.redis import RedisIntegration
|
|||||||
from sentry_sdk.integrations.socket import SocketIntegration
|
from sentry_sdk.integrations.socket import SocketIntegration
|
||||||
from sentry_sdk.integrations.stdlib import StdlibIntegration
|
from sentry_sdk.integrations.stdlib import StdlibIntegration
|
||||||
from sentry_sdk.integrations.threading import ThreadingIntegration
|
from sentry_sdk.integrations.threading import ThreadingIntegration
|
||||||
|
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, SENTRY_TRACE_HEADER_NAME
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
from websockets.exceptions import WebSocketException
|
from websockets.exceptions import WebSocketException
|
||||||
|
|
||||||
@ -95,6 +96,8 @@ def traces_sampler(sampling_context: dict) -> float:
|
|||||||
return 0
|
return 0
|
||||||
if _type == "websocket":
|
if _type == "websocket":
|
||||||
return 0
|
return 0
|
||||||
|
if CONFIG.get_bool("debug"):
|
||||||
|
return 1
|
||||||
return float(CONFIG.get("error_reporting.sample_rate", 0.1))
|
return float(CONFIG.get("error_reporting.sample_rate", 0.1))
|
||||||
|
|
||||||
|
|
||||||
@ -167,3 +170,14 @@ def before_send(event: dict, hint: dict) -> dict | None:
|
|||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
return None
|
return None
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
|
def get_http_meta():
|
||||||
|
"""Get sentry-related meta key-values"""
|
||||||
|
scope = get_current_scope()
|
||||||
|
meta = {
|
||||||
|
SENTRY_TRACE_HEADER_NAME: scope.get_traceparent() or "",
|
||||||
|
}
|
||||||
|
if bag := scope.get_baggage():
|
||||||
|
meta[BAGGAGE_HEADER_NAME] = bag.serialize()
|
||||||
|
return meta
|
||||||
|
|||||||
@ -59,7 +59,7 @@ class PropertyMappingManager:
|
|||||||
request: HttpRequest | None,
|
request: HttpRequest | None,
|
||||||
return_mapping: bool = False,
|
return_mapping: bool = False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Generator[tuple[dict, PropertyMapping], None]:
|
) -> Generator[tuple[dict, PropertyMapping]]:
|
||||||
"""Iterate over all mappings that were pre-compiled and
|
"""Iterate over all mappings that were pre-compiled and
|
||||||
execute all of them with the given context"""
|
execute all of them with the given context"""
|
||||||
if not self.__has_compiled:
|
if not self.__has_compiled:
|
||||||
|
|||||||
@ -494,86 +494,88 @@ class TestConfig(TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_db_pool(self):
|
# FIXME: Temporarily force pool to be deactivated.
|
||||||
"""Test DB Config with pool"""
|
# See https://github.com/goauthentik/authentik/issues/14320
|
||||||
config = ConfigLoader()
|
# def test_db_pool(self):
|
||||||
config.set("postgresql.host", "foo")
|
# """Test DB Config with pool"""
|
||||||
config.set("postgresql.name", "foo")
|
# config = ConfigLoader()
|
||||||
config.set("postgresql.user", "foo")
|
# config.set("postgresql.host", "foo")
|
||||||
config.set("postgresql.password", "foo")
|
# config.set("postgresql.name", "foo")
|
||||||
config.set("postgresql.port", "foo")
|
# config.set("postgresql.user", "foo")
|
||||||
config.set("postgresql.test.name", "foo")
|
# config.set("postgresql.password", "foo")
|
||||||
config.set("postgresql.use_pool", True)
|
# config.set("postgresql.port", "foo")
|
||||||
conf = django_db_config(config)
|
# config.set("postgresql.test.name", "foo")
|
||||||
self.assertEqual(
|
# config.set("postgresql.use_pool", True)
|
||||||
conf,
|
# conf = django_db_config(config)
|
||||||
{
|
# self.assertEqual(
|
||||||
"default": {
|
# conf,
|
||||||
"ENGINE": "authentik.root.db",
|
# {
|
||||||
"HOST": "foo",
|
# "default": {
|
||||||
"NAME": "foo",
|
# "ENGINE": "authentik.root.db",
|
||||||
"OPTIONS": {
|
# "HOST": "foo",
|
||||||
"pool": True,
|
# "NAME": "foo",
|
||||||
"sslcert": None,
|
# "OPTIONS": {
|
||||||
"sslkey": None,
|
# "pool": True,
|
||||||
"sslmode": None,
|
# "sslcert": None,
|
||||||
"sslrootcert": None,
|
# "sslkey": None,
|
||||||
},
|
# "sslmode": None,
|
||||||
"PASSWORD": "foo",
|
# "sslrootcert": None,
|
||||||
"PORT": "foo",
|
# },
|
||||||
"TEST": {"NAME": "foo"},
|
# "PASSWORD": "foo",
|
||||||
"USER": "foo",
|
# "PORT": "foo",
|
||||||
"CONN_MAX_AGE": 0,
|
# "TEST": {"NAME": "foo"},
|
||||||
"CONN_HEALTH_CHECKS": False,
|
# "USER": "foo",
|
||||||
"DISABLE_SERVER_SIDE_CURSORS": False,
|
# "CONN_MAX_AGE": 0,
|
||||||
}
|
# "CONN_HEALTH_CHECKS": False,
|
||||||
},
|
# "DISABLE_SERVER_SIDE_CURSORS": False,
|
||||||
)
|
# }
|
||||||
|
# },
|
||||||
|
# )
|
||||||
|
|
||||||
def test_db_pool_options(self):
|
# def test_db_pool_options(self):
|
||||||
"""Test DB Config with pool"""
|
# """Test DB Config with pool"""
|
||||||
config = ConfigLoader()
|
# config = ConfigLoader()
|
||||||
config.set("postgresql.host", "foo")
|
# config.set("postgresql.host", "foo")
|
||||||
config.set("postgresql.name", "foo")
|
# config.set("postgresql.name", "foo")
|
||||||
config.set("postgresql.user", "foo")
|
# config.set("postgresql.user", "foo")
|
||||||
config.set("postgresql.password", "foo")
|
# config.set("postgresql.password", "foo")
|
||||||
config.set("postgresql.port", "foo")
|
# config.set("postgresql.port", "foo")
|
||||||
config.set("postgresql.test.name", "foo")
|
# config.set("postgresql.test.name", "foo")
|
||||||
config.set("postgresql.use_pool", True)
|
# config.set("postgresql.use_pool", True)
|
||||||
config.set(
|
# config.set(
|
||||||
"postgresql.pool_options",
|
# "postgresql.pool_options",
|
||||||
base64.b64encode(
|
# base64.b64encode(
|
||||||
dumps(
|
# dumps(
|
||||||
{
|
# {
|
||||||
"max_size": 15,
|
# "max_size": 15,
|
||||||
}
|
# }
|
||||||
).encode()
|
# ).encode()
|
||||||
).decode(),
|
# ).decode(),
|
||||||
)
|
# )
|
||||||
conf = django_db_config(config)
|
# conf = django_db_config(config)
|
||||||
self.assertEqual(
|
# self.assertEqual(
|
||||||
conf,
|
# conf,
|
||||||
{
|
# {
|
||||||
"default": {
|
# "default": {
|
||||||
"ENGINE": "authentik.root.db",
|
# "ENGINE": "authentik.root.db",
|
||||||
"HOST": "foo",
|
# "HOST": "foo",
|
||||||
"NAME": "foo",
|
# "NAME": "foo",
|
||||||
"OPTIONS": {
|
# "OPTIONS": {
|
||||||
"pool": {
|
# "pool": {
|
||||||
"max_size": 15,
|
# "max_size": 15,
|
||||||
},
|
# },
|
||||||
"sslcert": None,
|
# "sslcert": None,
|
||||||
"sslkey": None,
|
# "sslkey": None,
|
||||||
"sslmode": None,
|
# "sslmode": None,
|
||||||
"sslrootcert": None,
|
# "sslrootcert": None,
|
||||||
},
|
# },
|
||||||
"PASSWORD": "foo",
|
# "PASSWORD": "foo",
|
||||||
"PORT": "foo",
|
# "PORT": "foo",
|
||||||
"TEST": {"NAME": "foo"},
|
# "TEST": {"NAME": "foo"},
|
||||||
"USER": "foo",
|
# "USER": "foo",
|
||||||
"CONN_MAX_AGE": 0,
|
# "CONN_MAX_AGE": 0,
|
||||||
"CONN_HEALTH_CHECKS": False,
|
# "CONN_HEALTH_CHECKS": False,
|
||||||
"DISABLE_SERVER_SIDE_CURSORS": False,
|
# "DISABLE_SERVER_SIDE_CURSORS": False,
|
||||||
}
|
# }
|
||||||
},
|
# },
|
||||||
)
|
# )
|
||||||
|
|||||||
@ -199,7 +199,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
|||||||
chunk_size = len(ops)
|
chunk_size = len(ops)
|
||||||
if len(ops) < 1:
|
if len(ops) < 1:
|
||||||
return
|
return
|
||||||
for chunk in batched(ops, chunk_size):
|
for chunk in batched(ops, chunk_size, strict=False):
|
||||||
req = PatchRequest(Operations=list(chunk))
|
req = PatchRequest(Operations=list(chunk))
|
||||||
self._request(
|
self._request(
|
||||||
"PATCH",
|
"PATCH",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema",
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "authentik 2025.4.0 Blueprint schema",
|
"title": "authentik 2025.4.1 Blueprint schema",
|
||||||
"required": [
|
"required": [
|
||||||
"version",
|
"version",
|
||||||
"entries"
|
"entries"
|
||||||
|
|||||||
@ -31,7 +31,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.0}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.1}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
@ -55,7 +55,7 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
worker:
|
worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.0}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.1}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
12
go.mod
12
go.mod
@ -5,7 +5,7 @@ go 1.24.0
|
|||||||
require (
|
require (
|
||||||
beryju.io/ldap v0.1.0
|
beryju.io/ldap v0.1.0
|
||||||
github.com/coreos/go-oidc/v3 v3.14.1
|
github.com/coreos/go-oidc/v3 v3.14.1
|
||||||
github.com/getsentry/sentry-go v0.32.0
|
github.com/getsentry/sentry-go v0.33.0
|
||||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||||
github.com/go-ldap/ldap/v3 v3.4.11
|
github.com/go-ldap/ldap/v3 v3.4.11
|
||||||
github.com/go-openapi/runtime v0.28.0
|
github.com/go-openapi/runtime v0.28.0
|
||||||
@ -19,7 +19,7 @@ require (
|
|||||||
github.com/jellydator/ttlcache/v3 v3.3.0
|
github.com/jellydator/ttlcache/v3 v3.3.0
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||||
github.com/pires/go-proxyproto v0.8.0
|
github.com/pires/go-proxyproto v0.8.1
|
||||||
github.com/prometheus/client_golang v1.22.0
|
github.com/prometheus/client_golang v1.22.0
|
||||||
github.com/redis/go-redis/v9 v9.8.0
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
github.com/sethvargo/go-envconfig v1.3.0
|
github.com/sethvargo/go-envconfig v1.3.0
|
||||||
@ -27,10 +27,10 @@ require (
|
|||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/wwt/guac v1.3.2
|
github.com/wwt/guac v1.3.2
|
||||||
goauthentik.io/api/v3 v3.2025040.1
|
goauthentik.io/api/v3 v3.2025041.1
|
||||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.29.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/sync v0.13.0
|
golang.org/x/sync v0.14.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
||||||
)
|
)
|
||||||
@ -75,7 +75,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
28
go.sum
28
go.sum
@ -69,8 +69,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
|||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY=
|
github.com/getsentry/sentry-go v0.33.0 h1:YWyDii0KGVov3xOaamOnF0mjOrqSjBqwv48UEzn7QFg=
|
||||||
github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
|
github.com/getsentry/sentry-go v0.33.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
@ -230,8 +230,8 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+
|
|||||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
|
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||||
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@ -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.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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
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.2025041.1 h1:GAN6AoTmfnCGgx1SyM07jP4/LR/T3rkTEyShSBd3Co8=
|
||||||
goauthentik.io/api/v3 v3.2025040.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
goauthentik.io/api/v3 v3.2025041.1/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-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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@ -358,16 +358,16 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
|
|||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -376,8 +376,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -412,8 +412,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
|||||||
@ -29,4 +29,4 @@ func UserAgent() string {
|
|||||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2025.4.0"
|
const VERSION = "2025.4.1"
|
||||||
|
|||||||
@ -56,6 +56,7 @@ EXPOSE 3389 6636 9300
|
|||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
ENV GOFIPS=1
|
ENV TMPDIR=/dev/shm/ \
|
||||||
|
GOFIPS=1
|
||||||
|
|
||||||
ENTRYPOINT ["/ldap"]
|
ENTRYPOINT ["/ldap"]
|
||||||
|
|||||||
@ -97,6 +97,7 @@ elif [[ "$1" == "test-all" ]]; then
|
|||||||
elif [[ "$1" == "healthcheck" ]]; then
|
elif [[ "$1" == "healthcheck" ]]; then
|
||||||
run_authentik healthcheck $(cat $MODE_FILE)
|
run_authentik healthcheck $(cat $MODE_FILE)
|
||||||
elif [[ "$1" == "dump_config" ]]; then
|
elif [[ "$1" == "dump_config" ]]; then
|
||||||
|
shift
|
||||||
exec python -m authentik.lib.config $@
|
exec python -m authentik.lib.config $@
|
||||||
elif [[ "$1" == "debug" ]]; then
|
elif [[ "$1" == "debug" ]]; then
|
||||||
exec sleep infinity
|
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",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1013.0",
|
"aws-cdk": "^2.1015.0",
|
||||||
"cross-env": "^7.0.3"
|
"cross-env": "^7.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -17,9 +17,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk": {
|
"node_modules/aws-cdk": {
|
||||||
"version": "2.1013.0",
|
"version": "2.1015.0",
|
||||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1013.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1015.0.tgz",
|
||||||
"integrity": "sha512-cbq4cOoEIZueMWenGgfI4RujS+AQ9GaMCTlW/3CnvEIhMD8j/tgZx7PTtgMuvwYrRoEeb/wTxgLPgUd5FhsoHA==",
|
"integrity": "sha512-txd+yMVVybtLfiwT409+fahbP0SkiwhmQvQf6PVVYnWzDPSknxYlUNJHisHV4tJEcbHWn1QPsLmqqMT0bw8hBg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1013.0",
|
"aws-cdk": "^2.1015.0",
|
||||||
"cross-env": "^7.0.3"
|
"cross-env": "^7.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ Parameters:
|
|||||||
Description: authentik Docker image
|
Description: authentik Docker image
|
||||||
AuthentikVersion:
|
AuthentikVersion:
|
||||||
Type: String
|
Type: String
|
||||||
Default: 2025.4.0
|
Default: 2025.4.1
|
||||||
Description: authentik Docker image tag
|
Description: authentik Docker image tag
|
||||||
AuthentikServerCPU:
|
AuthentikServerCPU:
|
||||||
Type: Number
|
Type: Number
|
||||||
|
|||||||
Binary file not shown.
BIN
locale/pt/LC_MESSAGES/django.mo
Normal file
BIN
locale/pt/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
3924
locale/pt/LC_MESSAGES/django.po
Normal file
3924
locale/pt/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@goauthentik/authentik",
|
"name": "@goauthentik/authentik",
|
||||||
"version": "2025.4.0",
|
"version": "2025.4.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
4
packages/docusaurus-config/package-lock.json
generated
4
packages/docusaurus-config/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@goauthentik/docusaurus-config",
|
"name": "@goauthentik/docusaurus-config",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@goauthentik/docusaurus-config",
|
"name": "@goauthentik/docusaurus-config",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"deepmerge-ts": "^7.1.5",
|
"deepmerge-ts": "^7.1.5",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@goauthentik/docusaurus-config",
|
"name": "@goauthentik/docusaurus-config",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"description": "authentik's Docusaurus config",
|
"description": "authentik's Docusaurus config",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -76,6 +76,7 @@ EXPOSE 9000 9300 9443
|
|||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
ENV GOFIPS=1
|
ENV TMPDIR=/dev/shm/ \
|
||||||
|
GOFIPS=1
|
||||||
|
|
||||||
ENTRYPOINT ["/proxy"]
|
ENTRYPOINT ["/proxy"]
|
||||||
|
|||||||
200
pyproject.toml
200
pyproject.toml
@ -1,104 +1,116 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "authentik"
|
name = "authentik"
|
||||||
version = "2025.4.0"
|
version = "2025.4.1"
|
||||||
description = ""
|
description = ""
|
||||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||||
requires-python = "==3.12.*"
|
requires-python = "==3.13.*"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2-cffi",
|
"argon2-cffi==23.1.0",
|
||||||
"celery",
|
"celery==5.5.2",
|
||||||
"channels",
|
"channels==4.2.2",
|
||||||
"channels-redis",
|
"channels-redis==4.2.1",
|
||||||
"cryptography",
|
"cryptography==44.0.3",
|
||||||
"dacite",
|
"dacite==1.9.2",
|
||||||
"deepmerge",
|
"deepmerge==2.0",
|
||||||
"defusedxml",
|
"defusedxml==0.7.1",
|
||||||
"django",
|
"django==5.1.9",
|
||||||
"django-countries",
|
"django-countries==7.6.1",
|
||||||
"django-cte",
|
"django-cte==1.3.3",
|
||||||
"django-filter",
|
"django-filter==25.1",
|
||||||
"django-guardian",
|
"django-guardian<3.0.0",
|
||||||
"django-model-utils",
|
"django-model-utils==5.0.0",
|
||||||
"django-pglock",
|
"django-pglock==1.7.2",
|
||||||
"django-prometheus",
|
"django-prometheus==2.3.1",
|
||||||
"django-redis",
|
"django-redis==5.4.0",
|
||||||
"django-storages[s3]",
|
"django-storages[s3]==1.14.6",
|
||||||
"django-tenants",
|
"django-tenants==3.7.0",
|
||||||
"djangorestframework",
|
"djangorestframework==3.16.0",
|
||||||
"djangorestframework-guardian",
|
"djangorestframework-guardian==0.3.0",
|
||||||
"docker",
|
"docker==7.1.0",
|
||||||
"drf-orjson-renderer",
|
"drf-orjson-renderer==1.7.3",
|
||||||
"drf-spectacular",
|
"drf-spectacular==0.28.0",
|
||||||
"dumb-init",
|
"dumb-init==1.2.5.post1",
|
||||||
"duo-client",
|
"duo-client==5.5.0",
|
||||||
"fido2",
|
"fido2==1.2.0",
|
||||||
"flower",
|
"flower==2.0.1",
|
||||||
"geoip2",
|
"geoip2==5.1.0",
|
||||||
"geopy",
|
"geopy==2.4.1",
|
||||||
"google-api-python-client",
|
"google-api-python-client==2.169.0",
|
||||||
"gssapi",
|
"gssapi==1.9.0",
|
||||||
"gunicorn",
|
"gunicorn==23.0.0",
|
||||||
"jsonpatch",
|
"jsonpatch==1.33",
|
||||||
"jwcrypto",
|
"jwcrypto==1.5.6",
|
||||||
"kubernetes",
|
"kubernetes==32.0.1",
|
||||||
"ldap3",
|
"ldap3==2.9.1",
|
||||||
"lxml",
|
"lxml==5.4.0",
|
||||||
"msgraph-sdk",
|
"msgraph-sdk==1.30.0",
|
||||||
"opencontainers",
|
"opencontainers==0.0.14",
|
||||||
"packaging",
|
"packaging==25.0",
|
||||||
"paramiko",
|
"paramiko==3.5.1",
|
||||||
"psycopg[c, pool]",
|
"psycopg[c,pool]==3.2.9",
|
||||||
"pydantic",
|
"pydantic==2.11.4",
|
||||||
"pydantic-scim",
|
"pydantic-scim==0.0.8",
|
||||||
"pyjwt",
|
"pyjwt==2.10.1",
|
||||||
"pyrad",
|
"pyrad==2.4",
|
||||||
"python-kadmin-rs ==0.6.0",
|
"python-kadmin-rs==0.6.0",
|
||||||
"pyyaml",
|
"pyyaml==6.0.2",
|
||||||
"requests-oauthlib",
|
"requests-oauthlib==2.0.0",
|
||||||
"scim2-filter-parser",
|
"scim2-filter-parser==0.7.0",
|
||||||
"sentry-sdk",
|
"sentry-sdk==2.28.0",
|
||||||
"service_identity",
|
"service-identity==24.2.0",
|
||||||
"setproctitle",
|
"setproctitle==1.3.6",
|
||||||
"structlog",
|
"structlog==25.3.0",
|
||||||
"swagger-spec-validator",
|
"swagger-spec-validator==3.0.4",
|
||||||
"tenant-schemas-celery",
|
"tenant-schemas-celery==4.0.1",
|
||||||
"twilio",
|
"twilio==9.6.1",
|
||||||
"ua-parser",
|
"ua-parser==1.0.1",
|
||||||
"unidecode",
|
"unidecode==1.4.0",
|
||||||
"urllib3 <3",
|
"urllib3<3",
|
||||||
"uvicorn[standard]",
|
"uvicorn[standard]==0.34.2",
|
||||||
"watchdog",
|
"watchdog==6.0.0",
|
||||||
"webauthn",
|
"webauthn==2.5.2",
|
||||||
"wsproto",
|
"wsproto==1.2.0",
|
||||||
"xmlsec <= 1.3.14",
|
"xmlsec==1.3.15",
|
||||||
"zxcvbn",
|
"zxcvbn==4.5.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"aws-cdk-lib",
|
"aws-cdk-lib==2.188.0",
|
||||||
"bandit",
|
"bandit==1.8.3",
|
||||||
"black",
|
"black==25.1.0",
|
||||||
"bump2version",
|
"bump2version==1.0.1",
|
||||||
"channels[daphne]",
|
"channels[daphne]==4.2.2",
|
||||||
"codespell",
|
"codespell==2.4.1",
|
||||||
"colorama",
|
"colorama==0.4.6",
|
||||||
"constructs",
|
"constructs==10.4.2",
|
||||||
"coverage[toml]",
|
"coverage[toml]==7.8.0",
|
||||||
"debugpy",
|
"debugpy==1.8.14",
|
||||||
"drf-jsonschema-serializer",
|
"drf-jsonschema-serializer==3.0.0",
|
||||||
"freezegun",
|
"freezegun==1.5.1",
|
||||||
"importlib-metadata",
|
"importlib-metadata==8.6.1",
|
||||||
"k5test",
|
"k5test==0.10.4",
|
||||||
"pdoc",
|
"pdoc==15.0.3",
|
||||||
"pytest",
|
"pytest==8.3.5",
|
||||||
"pytest-django",
|
"pytest-django==4.11.1",
|
||||||
"pytest-github-actions-annotate-failures",
|
"pytest-github-actions-annotate-failures==0.3.0",
|
||||||
"pytest-randomly",
|
"pytest-randomly==3.16.0",
|
||||||
"pytest-timeout",
|
"pytest-timeout==2.4.0",
|
||||||
"requests-mock",
|
"requests-mock==1.12.1",
|
||||||
"ruff",
|
"ruff==0.11.9",
|
||||||
"selenium",
|
"selenium==4.32.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
no-binary-package = [
|
||||||
|
# This differs from the no-binary packages in the Dockerfile. This is due to the fact
|
||||||
|
# that these packages are built from source for different reasons than cryptography and kadmin.
|
||||||
|
# These packages are built from source to link against the libxml2 on the system which is
|
||||||
|
# required for functionality and to stay up-to-date on both libraries.
|
||||||
|
# The other packages specified in the dockerfile are compiled from source to link against the
|
||||||
|
# correct FIPS OpenSSL libraries
|
||||||
|
"lxml",
|
||||||
|
"xmlsec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
@ -143,12 +155,12 @@ ignore-words = ".github/codespell-words.txt"
|
|||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = ['py312']
|
target-version = ['py313']
|
||||||
exclude = 'node_modules'
|
exclude = 'node_modules'
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = "py312"
|
target-version = "py313"
|
||||||
exclude = ["**/migrations/**", "**/node_modules/**"]
|
exclude = ["**/migrations/**", "**/node_modules/**"]
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
|
|||||||
@ -56,6 +56,7 @@ HEALTHCHECK --interval=5s --retries=20 --start-period=3s CMD [ "/rac", "healthch
|
|||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
ENV GOFIPS=1
|
ENV TMPDIR=/dev/shm/ \
|
||||||
|
GOFIPS=1
|
||||||
|
|
||||||
ENTRYPOINT ["/rac"]
|
ENTRYPOINT ["/rac"]
|
||||||
|
|||||||
@ -56,6 +56,7 @@ EXPOSE 1812/udp 9300
|
|||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
ENV GOFIPS=1
|
ENV TMPDIR=/dev/shm/ \
|
||||||
|
GOFIPS=1
|
||||||
|
|
||||||
ENTRYPOINT ["/radius"]
|
ENTRYPOINT ["/radius"]
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: authentik
|
title: authentik
|
||||||
version: 2025.4.0
|
version: 2025.4.1
|
||||||
description: Making authentication simple.
|
description: Making authentication simple.
|
||||||
contact:
|
contact:
|
||||||
email: hello@goauthentik.io
|
email: hello@goauthentik.io
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
services:
|
services:
|
||||||
chrome:
|
chrome:
|
||||||
image: docker.io/selenium/standalone-chrome:122.0
|
image: docker.io/selenium/standalone-chrome:136.0
|
||||||
volumes:
|
volumes:
|
||||||
- /dev/shm:/dev/shm
|
- /dev/shm:/dev/shm
|
||||||
network_mode: host
|
network_mode: host
|
||||||
restart: always
|
restart: always
|
||||||
mailpit:
|
mailpit:
|
||||||
image: docker.io/axllent/mailpit:v1.6.5
|
image: docker.io/axllent/mailpit:v1.24.2
|
||||||
ports:
|
ports:
|
||||||
- 1025:1025
|
- 1025:1025
|
||||||
- 8025:8025
|
- 8025:8025
|
||||||
|
|||||||
@ -1,12 +1,19 @@
|
|||||||
"""test default login flow"""
|
"""test default login flow"""
|
||||||
|
|
||||||
from authentik.blueprints.tests import apply_blueprint
|
from authentik.blueprints.tests import apply_blueprint
|
||||||
|
from authentik.flows.models import Flow
|
||||||
from tests.e2e.utils import SeleniumTestCase, retry
|
from tests.e2e.utils import SeleniumTestCase, retry
|
||||||
|
|
||||||
|
|
||||||
class TestFlowsLogin(SeleniumTestCase):
|
class TestFlowsLogin(SeleniumTestCase):
|
||||||
"""test default login flow"""
|
"""test default login flow"""
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# Reset authentication flow's compatibility mode; we need to do this as its
|
||||||
|
# not specified in the blueprint
|
||||||
|
Flow.objects.filter(slug="default-authentication-flow").update(compatibility_mode=False)
|
||||||
|
return super().tearDown()
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
@ -23,3 +30,21 @@ class TestFlowsLogin(SeleniumTestCase):
|
|||||||
self.login()
|
self.login()
|
||||||
self.wait_for_url(self.if_user_url("/library"))
|
self.wait_for_url(self.if_user_url("/library"))
|
||||||
self.assert_user(self.user)
|
self.assert_user(self.user)
|
||||||
|
|
||||||
|
@retry()
|
||||||
|
@apply_blueprint(
|
||||||
|
"default/flow-default-authentication-flow.yaml",
|
||||||
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
|
def test_login_compatibility_mode(self):
|
||||||
|
"""test default login flow with compatibility mode enabled"""
|
||||||
|
Flow.objects.filter(slug="default-authentication-flow").update(compatibility_mode=True)
|
||||||
|
self.driver.get(
|
||||||
|
self.url(
|
||||||
|
"authentik_core:if-flow",
|
||||||
|
flow_slug="default-authentication-flow",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.login(shadow_dom=False)
|
||||||
|
self.wait_for_url(self.if_user_url("/library"))
|
||||||
|
self.assert_user(self.user)
|
||||||
|
|||||||
51
tests/e2e/test_flows_login_sfe.py
Normal file
51
tests/e2e/test_flows_login_sfe.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""test default login (using SFE interface) flow"""
|
||||||
|
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
|
from authentik.blueprints.tests import apply_blueprint
|
||||||
|
from tests.e2e.utils import SeleniumTestCase, retry
|
||||||
|
|
||||||
|
|
||||||
|
class TestFlowsLoginSFE(SeleniumTestCase):
|
||||||
|
"""test default login flow"""
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
"""Do entire login flow adjusted for SFE"""
|
||||||
|
flow_executor = self.driver.find_element(By.ID, "flow-sfe-container")
|
||||||
|
identification_stage = flow_executor.find_element(By.ID, "ident-form")
|
||||||
|
|
||||||
|
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").click()
|
||||||
|
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").send_keys(
|
||||||
|
self.user.username
|
||||||
|
)
|
||||||
|
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").send_keys(
|
||||||
|
Keys.ENTER
|
||||||
|
)
|
||||||
|
|
||||||
|
password_stage = flow_executor.find_element(By.ID, "password-form")
|
||||||
|
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
|
||||||
|
self.user.username
|
||||||
|
)
|
||||||
|
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(Keys.ENTER)
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
@retry()
|
||||||
|
@apply_blueprint(
|
||||||
|
"default/flow-default-authentication-flow.yaml",
|
||||||
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
|
def test_login(self):
|
||||||
|
"""test default login flow"""
|
||||||
|
self.driver.get(
|
||||||
|
self.url(
|
||||||
|
"authentik_core:if-flow",
|
||||||
|
flow_slug="default-authentication-flow",
|
||||||
|
query={"sfe": True},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.login()
|
||||||
|
self.wait_for_url(self.if_user_url("/library"))
|
||||||
|
self.assert_user(self.user)
|
||||||
@ -26,8 +26,10 @@ from selenium import webdriver
|
|||||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
|
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.common.keys import Keys
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
from selenium.webdriver.remote.command import Command
|
||||||
from selenium.webdriver.remote.webdriver import WebDriver
|
from selenium.webdriver.remote.webdriver import WebDriver
|
||||||
from selenium.webdriver.remote.webelement import WebElement
|
from selenium.webdriver.remote.webelement import WebElement
|
||||||
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
from selenium.webdriver.support.wait import WebDriverWait
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
@ -36,8 +38,8 @@ from authentik.core.models import User
|
|||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
|
|
||||||
RETRIES = int(environ.get("RETRIES", "3"))
|
|
||||||
IS_CI = "CI" in environ
|
IS_CI = "CI" in environ
|
||||||
|
RETRIES = int(environ.get("RETRIES", "3")) if IS_CI else 1
|
||||||
|
|
||||||
|
|
||||||
def get_docker_tag() -> str:
|
def get_docker_tag() -> str:
|
||||||
@ -197,7 +199,12 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
|||||||
super().tearDown()
|
super().tearDown()
|
||||||
if IS_CI:
|
if IS_CI:
|
||||||
print("::group::Browser logs")
|
print("::group::Browser logs")
|
||||||
for line in self.driver.get_log("browser"):
|
# Very verbose way to get browser logs
|
||||||
|
# https://github.com/SeleniumHQ/selenium/pull/15641
|
||||||
|
# for some reason this removes the `get_log` API from Remote Webdriver
|
||||||
|
# and only keeps it on the local Chrome web driver, even when using
|
||||||
|
# a remote chrome driver...? (nvm the fact this was released as a minor version)
|
||||||
|
for line in self.driver.execute(Command.GET_LOG, {"type": "browser"})["value"]:
|
||||||
print(line["message"])
|
print(line["message"])
|
||||||
if IS_CI:
|
if IS_CI:
|
||||||
print("::endgroup::")
|
print("::endgroup::")
|
||||||
@ -234,10 +241,30 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
|||||||
element = self.driver.execute_script("return arguments[0].shadowRoot", shadow_root)
|
element = self.driver.execute_script("return arguments[0].shadowRoot", shadow_root)
|
||||||
return element
|
return element
|
||||||
|
|
||||||
def login(self):
|
def shady_dom(self) -> WebElement:
|
||||||
"""Do entire login flow and check user afterwards"""
|
class wrapper:
|
||||||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
def __init__(self, container: WebDriver):
|
||||||
identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor)
|
self.container = container
|
||||||
|
|
||||||
|
def find_element(self, by: str, selector: str) -> WebElement:
|
||||||
|
return self.container.execute_script(
|
||||||
|
"return document.__shady_native_querySelector(arguments[0])", selector
|
||||||
|
)
|
||||||
|
|
||||||
|
return wrapper(self.driver)
|
||||||
|
|
||||||
|
def login(self, shadow_dom=True):
|
||||||
|
"""Do entire login flow"""
|
||||||
|
|
||||||
|
if shadow_dom:
|
||||||
|
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||||
|
identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor)
|
||||||
|
else:
|
||||||
|
flow_executor = self.shady_dom()
|
||||||
|
identification_stage = self.shady_dom()
|
||||||
|
|
||||||
|
wait = WebDriverWait(identification_stage, self.wait_timeout)
|
||||||
|
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "input[name=uidField]")))
|
||||||
|
|
||||||
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").click()
|
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").click()
|
||||||
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").send_keys(
|
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").send_keys(
|
||||||
@ -247,8 +274,16 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
|||||||
Keys.ENTER
|
Keys.ENTER
|
||||||
)
|
)
|
||||||
|
|
||||||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
if shadow_dom:
|
||||||
password_stage = self.get_shadow_root("ak-stage-password", flow_executor)
|
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||||
|
password_stage = self.get_shadow_root("ak-stage-password", flow_executor)
|
||||||
|
else:
|
||||||
|
flow_executor = self.shady_dom()
|
||||||
|
password_stage = self.shady_dom()
|
||||||
|
|
||||||
|
wait = WebDriverWait(password_stage, self.wait_timeout)
|
||||||
|
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "input[name=password]")))
|
||||||
|
|
||||||
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
|
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
|
||||||
self.user.username
|
self.user.username
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
import { create } from "@storybook/theming/create";
|
|
||||||
|
|
||||||
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
||||||
|
|
||||||
export default create({
|
|
||||||
base: isDarkMode ? "dark" : "light",
|
|
||||||
brandTitle: "authentik Storybook",
|
|
||||||
brandUrl: "https://goauthentik.io",
|
|
||||||
brandImage: "https://goauthentik.io/img/icon_left_brand_colour.svg",
|
|
||||||
brandTarget: "_self",
|
|
||||||
});
|
|
||||||
63
web/.storybook/main.js
Normal file
63
web/.storybook/main.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* @file Storybook configuration.
|
||||||
|
* @import { StorybookConfig } from "@storybook/web-components-vite";
|
||||||
|
* @import { InlineConfig, Plugin } from "vite";
|
||||||
|
*/
|
||||||
|
import { createBundleDefinitions } from "@goauthentik/web/scripts/esbuild/environment";
|
||||||
|
import postcssLit from "rollup-plugin-postcss-lit";
|
||||||
|
import tsconfigPaths from "vite-tsconfig-paths";
|
||||||
|
|
||||||
|
const CSSImportPattern = /import [\w$]+ from .+\.(css)/g;
|
||||||
|
const JavaScriptFilePattern = /\.m?(js|ts|tsx)$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @satisfies {Plugin<never>}
|
||||||
|
*/
|
||||||
|
const inlineCSSPlugin = {
|
||||||
|
name: "inline-css-plugin",
|
||||||
|
transform: (source, id) => {
|
||||||
|
if (!JavaScriptFilePattern.test(id)) return;
|
||||||
|
|
||||||
|
const code = source.replace(CSSImportPattern, (match) => {
|
||||||
|
return `${match}?inline`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @satisfies {StorybookConfig}
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||||
|
addons: [
|
||||||
|
"@storybook/addon-controls",
|
||||||
|
"@storybook/addon-links",
|
||||||
|
"@storybook/addon-essentials",
|
||||||
|
"storybook-addon-mock",
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: "@storybook/web-components-vite",
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
autodocs: "tag",
|
||||||
|
},
|
||||||
|
viteFinal({ plugins = [], ...config }) {
|
||||||
|
/**
|
||||||
|
* @satisfies {InlineConfig}
|
||||||
|
*/
|
||||||
|
const mergedConfig = {
|
||||||
|
...config,
|
||||||
|
define: createBundleDefinitions(),
|
||||||
|
plugins: [inlineCSSPlugin, ...plugins, postcssLit(), tsconfigPaths()],
|
||||||
|
};
|
||||||
|
|
||||||
|
return mergedConfig;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@ -1,81 +0,0 @@
|
|||||||
import replace from "@rollup/plugin-replace";
|
|
||||||
import type { StorybookConfig } from "@storybook/web-components-vite";
|
|
||||||
import { cwd } from "process";
|
|
||||||
import modify from "rollup-plugin-modify";
|
|
||||||
import postcssLit from "rollup-plugin-postcss-lit";
|
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
|
||||||
|
|
||||||
export const isProdBuild = process.env.NODE_ENV === "production";
|
|
||||||
export const apiBasePath = process.env.AK_API_BASE_PATH || "";
|
|
||||||
|
|
||||||
const importInlinePatterns = [
|
|
||||||
'import AKGlobal from "(\\.\\./)*common/styles/authentik\\.css',
|
|
||||||
'import AKGlobal from "@goauthentik/common/styles/authentik\\.css',
|
|
||||||
'import PF.+ from "@patternfly/patternfly/\\S+\\.css',
|
|
||||||
'import ThemeDark from "@goauthentik/common/styles/theme-dark\\.css',
|
|
||||||
'import OneDark from "@goauthentik/common/styles/one-dark\\.css',
|
|
||||||
'import styles from "\\./LibraryPageImpl\\.css',
|
|
||||||
];
|
|
||||||
|
|
||||||
const importInlineRegexp = new RegExp(importInlinePatterns.map((a) => `(${a})`).join("|"));
|
|
||||||
|
|
||||||
const config: StorybookConfig = {
|
|
||||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
|
||||||
addons: [
|
|
||||||
"@storybook/addon-controls",
|
|
||||||
"@storybook/addon-links",
|
|
||||||
"@storybook/addon-essentials",
|
|
||||||
"storybook-addon-mock",
|
|
||||||
],
|
|
||||||
staticDirs: [
|
|
||||||
{
|
|
||||||
from: "../node_modules/@patternfly/patternfly/patternfly-base.css",
|
|
||||||
to: "@patternfly/patternfly/patternfly-base.css",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
from: "../src/common/styles/authentik.css",
|
|
||||||
to: "@goauthentik/common/styles/authentik.css",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
from: "../src/common/styles/theme-dark.css",
|
|
||||||
to: "@goauthentik/common/styles/theme-dark.css",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
from: "../src/common/styles/one-dark.css",
|
|
||||||
to: "@goauthentik/common/styles/one-dark.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
framework: {
|
|
||||||
name: "@storybook/web-components-vite",
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
docs: {
|
|
||||||
autodocs: "tag",
|
|
||||||
},
|
|
||||||
async viteFinal(config) {
|
|
||||||
return {
|
|
||||||
...config,
|
|
||||||
plugins: [
|
|
||||||
modify({
|
|
||||||
find: importInlineRegexp,
|
|
||||||
replace: (match: RegExpMatchArray) => {
|
|
||||||
return `${match}?inline`;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
replace({
|
|
||||||
"process.env.NODE_ENV": JSON.stringify(
|
|
||||||
isProdBuild ? "production" : "development",
|
|
||||||
),
|
|
||||||
"process.env.CWD": JSON.stringify(cwd()),
|
|
||||||
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
|
|
||||||
"preventAssignment": true,
|
|
||||||
}),
|
|
||||||
...config.plugins,
|
|
||||||
postcssLit(),
|
|
||||||
tsconfigPaths(),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
38
web/.storybook/manager.js
Normal file
38
web/.storybook/manager.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* @file Storybook manager configuration.
|
||||||
|
*
|
||||||
|
* @import { ThemeVarsPartial } from "storybook/internal/theming";
|
||||||
|
*/
|
||||||
|
import { createUIThemeEffect, resolveUITheme } from "@goauthentik/web/common/theme.ts";
|
||||||
|
import { addons } from "@storybook/manager-api";
|
||||||
|
import { create } from "@storybook/theming/create";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @satisfies {Partial<ThemeVarsPartial>}
|
||||||
|
*/
|
||||||
|
const baseTheme = {
|
||||||
|
brandTitle: "authentik Storybook",
|
||||||
|
brandUrl: "https://goauthentik.io",
|
||||||
|
brandImage: "https://goauthentik.io/img/icon_left_brand_colour.svg",
|
||||||
|
brandTarget: "_self",
|
||||||
|
};
|
||||||
|
|
||||||
|
const uiTheme = resolveUITheme();
|
||||||
|
|
||||||
|
addons.setConfig({
|
||||||
|
theme: create({
|
||||||
|
...baseTheme,
|
||||||
|
base: uiTheme,
|
||||||
|
}),
|
||||||
|
enableShortcuts: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
createUIThemeEffect((nextUITheme) => {
|
||||||
|
addons.setConfig({
|
||||||
|
theme: create({
|
||||||
|
...baseTheme,
|
||||||
|
base: nextUITheme,
|
||||||
|
}),
|
||||||
|
enableShortcuts: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,9 +0,0 @@
|
|||||||
// .storybook/manager.js
|
|
||||||
import { addons } from "@storybook/manager-api";
|
|
||||||
|
|
||||||
import authentikTheme from "./authentikTheme";
|
|
||||||
|
|
||||||
addons.setConfig({
|
|
||||||
theme: authentikTheme,
|
|
||||||
enableShortcuts: false,
|
|
||||||
});
|
|
||||||
@ -1,5 +1,3 @@
|
|||||||
<link rel="stylesheet" href="@patternfly/patternfly/patternfly-base.css" />
|
|
||||||
<link rel="stylesheet" href="@goauthentik/common/styles/authentik.css" />
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|||||||
32
web/.storybook/preview.js
Normal file
32
web/.storybook/preview.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/// <reference types="../types/css.js" />
|
||||||
|
/**
|
||||||
|
* @file Storybook manager configuration.
|
||||||
|
*
|
||||||
|
* @import { Preview } from "@storybook/web-components";
|
||||||
|
*/
|
||||||
|
import { applyDocumentTheme } from "@goauthentik/web/common/theme.ts";
|
||||||
|
|
||||||
|
applyDocumentTheme();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @satisfies {Preview}
|
||||||
|
*/
|
||||||
|
const preview = {
|
||||||
|
parameters: {
|
||||||
|
options: {
|
||||||
|
storySort: {
|
||||||
|
method: "alphabetical",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||||
|
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import type { Preview } from "@storybook/web-components";
|
|
||||||
|
|
||||||
import "@goauthentik/common/styles/authentik.css";
|
|
||||||
// import "@goauthentik/common/styles/theme-dark.css";
|
|
||||||
import "@patternfly/patternfly/components/Brand/brand.css";
|
|
||||||
import "@patternfly/patternfly/components/Page/page.css";
|
|
||||||
// .storybook/preview.js
|
|
||||||
import "@patternfly/patternfly/patternfly-base.css";
|
|
||||||
|
|
||||||
const preview: Preview = {
|
|
||||||
parameters: {
|
|
||||||
options: {
|
|
||||||
storySort: {
|
|
||||||
method: "alphabetical",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
|
||||||
cssUserPrefs: {
|
|
||||||
"prefers-color-scheme": "light",
|
|
||||||
},
|
|
||||||
controls: {
|
|
||||||
matchers: {
|
|
||||||
color: /(background|color)$/i,
|
|
||||||
date: /Date$/,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default preview;
|
|
||||||
2781
web/package-lock.json
generated
2781
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,6 @@
|
|||||||
"lint:precommit": "wireit",
|
"lint:precommit": "wireit",
|
||||||
"lint:types": "wireit",
|
"lint:types": "wireit",
|
||||||
"lit-analyse": "wireit",
|
"lit-analyse": "wireit",
|
||||||
"postinstall": "bash scripts/patch-spotlight.sh",
|
|
||||||
"precommit": "wireit",
|
"precommit": "wireit",
|
||||||
"prettier": "wireit",
|
"prettier": "wireit",
|
||||||
"prettier-check": "wireit",
|
"prettier-check": "wireit",
|
||||||
@ -37,7 +36,14 @@
|
|||||||
"exports": {
|
"exports": {
|
||||||
"./package.json": "./package.json",
|
"./package.json": "./package.json",
|
||||||
"./paths": "./paths.js",
|
"./paths": "./paths.js",
|
||||||
"./scripts/*": "./scripts/*.mjs"
|
"./scripts/*": "./scripts/*.mjs",
|
||||||
|
"./elements/*": "./src/elements/*",
|
||||||
|
"./common/*": "./src/common/*",
|
||||||
|
"./components/*": "./src/components/*",
|
||||||
|
"./flow/*": "./src/flow/*",
|
||||||
|
"./locales/*": "./src/locales/*",
|
||||||
|
"./user/*": "./src/user/*",
|
||||||
|
"./admin/*": "./src/admin/*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
@ -50,7 +56,7 @@
|
|||||||
"@floating-ui/dom": "^1.6.11",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@formatjs/intl-listformat": "^7.5.7",
|
"@formatjs/intl-listformat": "^7.5.7",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
"@goauthentik/api": "^2025.4.0-1746018955",
|
"@goauthentik/api": "^2025.4.1-1747332783",
|
||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@lit/localize": "^0.12.2",
|
"@lit/localize": "^0.12.2",
|
||||||
"@lit/reactive-element": "^2.0.4",
|
"@lit/reactive-element": "^2.0.4",
|
||||||
@ -106,14 +112,14 @@
|
|||||||
"@hcaptcha/types": "^1.0.4",
|
"@hcaptcha/types": "^1.0.4",
|
||||||
"@lit/localize-tools": "^0.8.0",
|
"@lit/localize-tools": "^0.8.0",
|
||||||
"@rollup/plugin-replace": "^6.0.1",
|
"@rollup/plugin-replace": "^6.0.1",
|
||||||
"@storybook/addon-essentials": "^8.3.4",
|
"@storybook/addon-essentials": "^8.6.12",
|
||||||
"@storybook/addon-links": "^8.3.4",
|
"@storybook/addon-links": "^8.6.12",
|
||||||
"@storybook/api": "^7.6.17",
|
"@storybook/blocks": "^8.6.12",
|
||||||
"@storybook/blocks": "^8.3.4",
|
"@storybook/experimental-addon-test": "^8.6.12",
|
||||||
"@storybook/builder-vite": "^8.3.4",
|
"@storybook/manager-api": "^8.6.12",
|
||||||
"@storybook/manager-api": "^8.3.4",
|
"@storybook/test": "^8.6.12",
|
||||||
"@storybook/web-components": "^8.3.4",
|
"@storybook/web-components": "^8.6.12",
|
||||||
"@storybook/web-components-vite": "^8.3.4",
|
"@storybook/web-components-vite": "^8.6.12",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||||
"@types/chart.js": "^2.9.41",
|
"@types/chart.js": "^2.9.41",
|
||||||
"@types/codemirror": "^5.60.15",
|
"@types/codemirror": "^5.60.15",
|
||||||
@ -145,9 +151,8 @@
|
|||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"pseudolocale": "^2.1.0",
|
"pseudolocale": "^2.1.0",
|
||||||
"rollup-plugin-modify": "^3.0.0",
|
|
||||||
"rollup-plugin-postcss-lit": "^2.1.0",
|
"rollup-plugin-postcss-lit": "^2.1.0",
|
||||||
"storybook": "^8.3.4",
|
"storybook": "^8.6.12",
|
||||||
"storybook-addon-mock": "^5.0.0",
|
"storybook-addon-mock": "^5.0.0",
|
||||||
"turnstile-types": "^1.2.3",
|
"turnstile-types": "^1.2.3",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
* @import { Message as ESBuildMessage } from "esbuild";
|
* @import { Message as ESBuildMessage } from "esbuild";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const logPrefix = "👷 [ESBuild]";
|
const logPrefix = "authentik/dev/web: ";
|
||||||
const log = console.debug.bind(console, logPrefix);
|
const log = console.debug.bind(console, logPrefix);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,7 +21,7 @@ const log = console.debug.bind(console, logPrefix);
|
|||||||
* ESBuild may tree-shake it out of production builds.
|
* ESBuild may tree-shake it out of production builds.
|
||||||
*
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* if (process.env.NODE_ENV === "development") {
|
* if (import.meta.env.NODE_ENV=== "development") {
|
||||||
* await import("@goauthentik/esbuild-plugin-live-reload/client")
|
* await import("@goauthentik/esbuild-plugin-live-reload/client")
|
||||||
* .catch(() => console.warn("Failed to import watcher"))
|
* .catch(() => console.warn("Failed to import watcher"))
|
||||||
* }
|
* }
|
||||||
@ -76,7 +76,7 @@ export class ESBuildObserver extends EventSource {
|
|||||||
*/
|
*/
|
||||||
#startListener = () => {
|
#startListener = () => {
|
||||||
this.#trackActivity();
|
this.#trackActivity();
|
||||||
log("⏰ Build started...");
|
log("⏰ Build started...");
|
||||||
};
|
};
|
||||||
|
|
||||||
#internalErrorListener = () => {
|
#internalErrorListener = () => {
|
||||||
@ -86,7 +86,7 @@ export class ESBuildObserver extends EventSource {
|
|||||||
clearTimeout(this.#keepAliveInterval);
|
clearTimeout(this.#keepAliveInterval);
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
log("⛔️ Closing connection");
|
log("⛔️ Closing connection");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,13 +126,13 @@ export class ESBuildObserver extends EventSource {
|
|||||||
this.#trackActivity();
|
this.#trackActivity();
|
||||||
|
|
||||||
if (!this.online) {
|
if (!this.online) {
|
||||||
log("🚫 Build finished while offline.");
|
log("🚫 Build finished while offline.");
|
||||||
this.deferredReload = true;
|
this.deferredReload = true;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("🛎️ Build completed! Reloading...");
|
log("🛎️ Build completed! Reloading...");
|
||||||
|
|
||||||
// We use an animation frame to keep the reload from happening before the
|
// We use an animation frame to keep the reload from happening before the
|
||||||
// event loop has a chance to process the message.
|
// event loop has a chance to process the message.
|
||||||
@ -189,13 +189,13 @@ export class ESBuildObserver extends EventSource {
|
|||||||
|
|
||||||
if (!this.deferredReload) return;
|
if (!this.deferredReload) return;
|
||||||
|
|
||||||
log("🛎️ Reloading after offline build...");
|
log("🛎️ Reloading after offline build...");
|
||||||
this.deferredReload = false;
|
this.deferredReload = false;
|
||||||
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
log("🛎️ Listening for build changes...");
|
log("🛎️ Listening for build changes...");
|
||||||
|
|
||||||
this.#keepAliveInterval = setInterval(() => {
|
this.#keepAliveInterval = setInterval(() => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@ -203,7 +203,7 @@ export class ESBuildObserver extends EventSource {
|
|||||||
if (now - this.lastUpdatedAt < 10_000) return;
|
if (now - this.lastUpdatedAt < 10_000) return;
|
||||||
|
|
||||||
this.alive = false;
|
this.alive = false;
|
||||||
log("👋 Waiting for build to start...");
|
log("👋 Waiting for build to start...");
|
||||||
}, 15_000);
|
}, 15_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,15 +4,20 @@
|
|||||||
|
|
||||||
export {};
|
export {};
|
||||||
declare global {
|
declare global {
|
||||||
|
/**
|
||||||
|
* Environment variables injected by ESBuild.
|
||||||
|
*/
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
/**
|
||||||
|
* The injected watcher URL for ESBuild.
|
||||||
|
* This is used for live reloading in development mode.
|
||||||
|
*
|
||||||
|
* @format url
|
||||||
|
*/
|
||||||
|
readonly ESBUILD_WATCHER_URL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: {
|
readonly env: ImportMetaEnv;
|
||||||
/**
|
|
||||||
* The injected watcher URL for ESBuild.
|
|
||||||
* This is used for live reloading in development mode.
|
|
||||||
*
|
|
||||||
* @format url
|
|
||||||
*/
|
|
||||||
ESBUILD_WATCHER_URL: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Constants for JavaScript and TypeScript files.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// <reference types="../../types/global.js" />
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current Node.js environment, defaulting to "development" when not set.
|
|
||||||
*
|
|
||||||
* Note, this should only be used during the build process.
|
|
||||||
*
|
|
||||||
* If you need to check the environment at runtime, use `process.env.NODE_ENV` to
|
|
||||||
* ensure that module tree-shaking works correctly.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export const NodeEnvironment = process.env.NODE_ENV || "development";
|
|
||||||
@ -1,6 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* @file Utility functions for building and copying files.
|
* @file Utility functions for working with environment variables.
|
||||||
*/
|
*/
|
||||||
|
/// <reference types="./types/global.js" />
|
||||||
|
|
||||||
|
//#region Constants
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current Node.js environment, defaulting to "development" when not set.
|
||||||
|
*
|
||||||
|
* Note, this should only be used during the build process.
|
||||||
|
*
|
||||||
|
* If you need to check the environment at runtime, use `process.env.NODE_ENV` to
|
||||||
|
* ensure that module tree-shaking works correctly.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const NodeEnvironment = process.env.NODE_ENV || "development";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A source environment variable, which can be a string, number, boolean, null, or undefined.
|
* A source environment variable, which can be a string, number, boolean, null, or undefined.
|
||||||
@ -14,19 +28,26 @@
|
|||||||
* @typedef {T extends string ? `"${T}"` : T} JSONify
|
* @typedef {T extends string ? `"${T}"` : T} JSONify
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Utilities
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an object of environment variables, returns a new object with the same keys and values, but
|
* Given an object of environment variables, returns a new object with the same keys and values, but
|
||||||
* with the values serialized as strings.
|
* with the values serialized as strings.
|
||||||
*
|
*
|
||||||
* @template {Record<string, EnvironmentVariable>} EnvRecord
|
* @template {Record<string, EnvironmentVariable>} EnvRecord
|
||||||
* @template {string} [Prefix='process.env.']
|
* @template {string} [Prefix='import.meta.env.']
|
||||||
*
|
*
|
||||||
* @param {EnvRecord} input
|
* @param {EnvRecord} input
|
||||||
* @param {Prefix} [prefix='process.env.']
|
* @param {Prefix} [prefix='import.meta.env.']
|
||||||
*
|
*
|
||||||
* @returns {{[K in keyof EnvRecord as `${Prefix}${K}`]: JSONify<EnvRecord[K]>}}
|
* @returns {{[K in keyof EnvRecord as `${Prefix}${K}`]: JSONify<EnvRecord[K]>}}
|
||||||
*/
|
*/
|
||||||
export function serializeEnvironmentVars(input, prefix = /** @type {Prefix} */ ("process.env.")) {
|
export function serializeEnvironmentVars(
|
||||||
|
input,
|
||||||
|
prefix = /** @type {Prefix} */ ("import.meta.env."),
|
||||||
|
) {
|
||||||
/**
|
/**
|
||||||
* @type {Record<string, string>}
|
* @type {Record<string, string>}
|
||||||
*/
|
*/
|
||||||
@ -40,3 +61,5 @@ export function serializeEnvironmentVars(input, prefix = /** @type {Prefix} */ (
|
|||||||
|
|
||||||
return /** @type {any} */ (env);
|
return /** @type {any} */ (env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
@ -1,7 +1,6 @@
|
|||||||
/// <reference types="./types/global.js" />
|
/// <reference types="./types/global.js" />
|
||||||
|
|
||||||
export * from "./paths.js";
|
export * from "./paths.js";
|
||||||
export * from "./constants.js";
|
export * from "./environment.js";
|
||||||
export * from "./build.js";
|
|
||||||
export * from "./version.js";
|
export * from "./version.js";
|
||||||
export * from "./scripting.js";
|
export * from "./scripting.js";
|
||||||
|
|||||||
@ -47,7 +47,16 @@ class SimpleFlowExecutor {
|
|||||||
return `${ak().api.base}api/v3/flows/executor/${this.flowSlug}/?query=${encodeURIComponent(window.location.search.substring(1))}`;
|
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() {
|
start() {
|
||||||
|
this.loading();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url: this.apiURL,
|
url: this.apiURL,
|
||||||
@ -201,6 +210,9 @@ class PasswordStage extends Stage<PasswordChallenge> {
|
|||||||
<form id="password-form">
|
<form id="password-form">
|
||||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||||
|
<div class="form-label-group my-3">
|
||||||
|
<input type="text" readonly class="form-control-plaintext" value="Welcome, ${this.challenge?.pendingUser}.">
|
||||||
|
</div>
|
||||||
<div class="form-label-group my-3 has-validation">
|
<div class="form-label-group my-3 has-validation">
|
||||||
<input type="password" autofocus class="form-control ${this.error("password").length > 0 ? IS_INVALID : ""}" name="password" placeholder="Password">
|
<input type="password" autofocus class="form-control ${this.error("password").length > 0 ? IS_INVALID : ""}" name="password" placeholder="Password">
|
||||||
${this.renderInputError("password")}
|
${this.renderInputError("password")}
|
||||||
|
|||||||
@ -1,17 +1,32 @@
|
|||||||
import { spawnSync } from "child_process";
|
/**
|
||||||
import fs from "fs";
|
* @file Lit Localize build script.
|
||||||
import path from "path";
|
*
|
||||||
import process from "process";
|
* @remarks
|
||||||
|
* Determines if all the Xliff translation source files are present and if the Typescript source files generated from those sources are up-to-date.
|
||||||
|
*
|
||||||
|
* If they are not, it runs the locale building script,
|
||||||
|
* intercepting the long spew of "this string is not translated" and replacing it with a
|
||||||
|
* summary of how many strings are missing with respect to the source locale.
|
||||||
|
*
|
||||||
|
* @import { ConfigFile } from "@lit/localize-tools/lib/types/config"
|
||||||
|
*/
|
||||||
|
import { PackageRoot } from "@goauthentik/web/paths";
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import { readFileSync, statSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if all the Xliff translation source files are present and if the Typescript source
|
* @type {ConfigFile}
|
||||||
* files generated from those sources are up-to-date. If they are not, it runs the locale building
|
|
||||||
* script, intercepting the long spew of "this string is not translated" and replacing it with a
|
|
||||||
* summary of how many strings are missing with respect to the source locale.
|
|
||||||
*/
|
*/
|
||||||
|
const localizeRules = JSON.parse(
|
||||||
|
readFileSync(path.join(PackageRoot, "lit-localize.json"), "utf-8"),
|
||||||
|
);
|
||||||
|
|
||||||
const localizeRules = JSON.parse(fs.readFileSync("./lit-localize.json", "utf-8"));
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} loc
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
function generatedFileIsUpToDateWithXliffSource(loc) {
|
function generatedFileIsUpToDateWithXliffSource(loc) {
|
||||||
const xliff = path.join("./xliff", `${loc}.xlf`);
|
const xliff = path.join("./xliff", `${loc}.xlf`);
|
||||||
const gened = path.join("./src/locales", `${loc}.ts`);
|
const gened = path.join("./src/locales", `${loc}.ts`);
|
||||||
@ -22,7 +37,7 @@ function generatedFileIsUpToDateWithXliffSource(loc) {
|
|||||||
// generates a unique error message and halts the build.
|
// generates a unique error message and halts the build.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var xlfStat = fs.statSync(xliff);
|
var xlfStat = statSync(xliff);
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
console.error(`lit-localize expected '${loc}.xlf', but XLF file is not present`);
|
console.error(`lit-localize expected '${loc}.xlf', but XLF file is not present`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@ -30,7 +45,7 @@ function generatedFileIsUpToDateWithXliffSource(loc) {
|
|||||||
|
|
||||||
// If the generated file doesn't exist, of course it's not up to date.
|
// If the generated file doesn't exist, of course it's not up to date.
|
||||||
try {
|
try {
|
||||||
var genedStat = fs.statSync(gened);
|
var genedStat = statSync(gened);
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
/// <reference types="../types/esbuild.js" />
|
||||||
/**
|
/**
|
||||||
* @file ESBuild script for building the authentik web UI.
|
* @file ESBuild script for building the authentik web UI.
|
||||||
*
|
*
|
||||||
@ -9,7 +10,6 @@ import {
|
|||||||
NodeEnvironment,
|
NodeEnvironment,
|
||||||
readBuildIdentifier,
|
readBuildIdentifier,
|
||||||
resolvePackage,
|
resolvePackage,
|
||||||
serializeEnvironmentVars,
|
|
||||||
} from "@goauthentik/monorepo";
|
} from "@goauthentik/monorepo";
|
||||||
import { DistDirectory, DistDirectoryName, EntryPoint, PackageRoot } from "@goauthentik/web/paths";
|
import { DistDirectory, DistDirectoryName, EntryPoint, PackageRoot } from "@goauthentik/web/paths";
|
||||||
import { deepmerge } from "deepmerge-ts";
|
import { deepmerge } from "deepmerge-ts";
|
||||||
@ -20,15 +20,10 @@ import * as fs from "node:fs/promises";
|
|||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
|
|
||||||
import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs";
|
import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs";
|
||||||
|
import { createBundleDefinitions } from "./esbuild/environment.mjs";
|
||||||
|
|
||||||
const logPrefix = "[Build]";
|
const logPrefix = "[Build]";
|
||||||
|
|
||||||
const definitions = serializeEnvironmentVars({
|
|
||||||
NODE_ENV: NodeEnvironment,
|
|
||||||
CWD: process.cwd(),
|
|
||||||
AK_API_BASE_PATH: process.env.AK_API_BASE_PATH,
|
|
||||||
});
|
|
||||||
|
|
||||||
const patternflyPath = resolvePackage("@patternfly/patternfly");
|
const patternflyPath = resolvePackage("@patternfly/patternfly");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,7 +81,7 @@ const BASE_ESBUILD_OPTIONS = {
|
|||||||
root: MonoRepoRoot,
|
root: MonoRepoRoot,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
define: definitions,
|
define: createBundleDefinitions(),
|
||||||
format: "esm",
|
format: "esm",
|
||||||
logOverride: {
|
logOverride: {
|
||||||
/**
|
/**
|
||||||
|
|||||||
29
web/scripts/esbuild/environment.mjs
Normal file
29
web/scripts/esbuild/environment.mjs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* @file ESBuild environment utilities.
|
||||||
|
*/
|
||||||
|
import { AuthentikVersion, NodeEnvironment, serializeEnvironmentVars } from "@goauthentik/monorepo";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mapping of environment variables to their respective runtime constants.
|
||||||
|
*/
|
||||||
|
export function createBundleDefinitions() {
|
||||||
|
const SerializedNodeEnvironment = /** @type {`"development"` | `"production"`} */ (
|
||||||
|
JSON.stringify(NodeEnvironment)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @satisfies {Record<ESBuildImportEnvKey, string>}
|
||||||
|
*/
|
||||||
|
const envRecord = {
|
||||||
|
AK_VERSION: AuthentikVersion,
|
||||||
|
AK_API_BASE_PATH: process.env.AK_API_BASE_PATH ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...serializeEnvironmentVars(envRecord),
|
||||||
|
// We need to explicitly set this for NPM packages that use `process`
|
||||||
|
// to determine their environment.
|
||||||
|
"process.env.NODE_ENV": SerializedNodeEnvironment,
|
||||||
|
"import.meta.env.NODE_ENV": SerializedNodeEnvironment,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -35,6 +35,11 @@ const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|||||||
const projectRoot = path.join(__dirname, "..");
|
const projectRoot = path.join(__dirname, "..");
|
||||||
process.chdir(projectRoot);
|
process.chdir(projectRoot);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string[]} flags
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
const hasFlag = (flags) => process.argv.length > 1 && flags.includes(process.argv[2]);
|
const hasFlag = (flags) => process.argv.length > 1 && flags.includes(process.argv[2]);
|
||||||
|
|
||||||
const [configFile, files] = hasFlag(["-n", "--nightmare"])
|
const [configFile, files] = hasFlag(["-n", "--nightmare"])
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
TARGET="./node_modules/@spotlightjs/overlay/dist/index-"[0-9a-f]*.js
|
|
||||||
|
|
||||||
if [[ $(grep -L "QX2" "$TARGET" > /dev/null 2> /dev/null) ]]; then
|
|
||||||
patch --forward -V none --no-backup-if-mismatch -p0 $TARGET <<EOF
|
|
||||||
|
|
||||||
TARGET=$(find "./node_modules/@spotlightjs/overlay/dist/" -name "index-[0-9a-f]*.js");
|
|
||||||
|
|
||||||
if ! grep -GL 'QX2 = ' "$TARGET" > /dev/null ; then
|
|
||||||
patch --forward --no-backup-if-mismatch -p0 "$TARGET" <<EOF
|
|
||||||
>>>>>>> main
|
|
||||||
--- a/index-5682ce90.js 2024-06-13 16:19:28
|
|
||||||
+++ b/index-5682ce90.js 2024-06-13 16:20:23
|
|
||||||
@@ -4958,11 +4958,10 @@
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
-const q2 = w.lazy(() => import("./main-3257b7fc.js").then((n) => n.m));
|
|
||||||
+const q2 = w.lazy(() => import("./main-3257b7fc.js").then((n) => n.m)), QX2 = () => {};
|
|
||||||
function Gp({
|
|
||||||
data: n,
|
|
||||||
- onUpdateData: a = () => {
|
|
||||||
- },
|
|
||||||
+ onUpdateData: a = QX2,
|
|
||||||
editingEnabled: s = !1,
|
|
||||||
clipboardEnabled: o = !1,
|
|
||||||
displayDataTypes: c = !1,
|
|
||||||
EOF
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "spotlight overlay.js patch already applied"
|
|
||||||
fi
|
|
||||||
@ -1,22 +1,36 @@
|
|||||||
import { readFileSync } from "fs";
|
/**
|
||||||
import path from "path";
|
* @file Pseudo-localization script.
|
||||||
|
*
|
||||||
|
* @import { ConfigFile } from "@lit/localize-tools/lib/types/config.js"
|
||||||
|
* @import { Config } from '@lit/localize-tools/lib/types/config.js';
|
||||||
|
* @import { ProgramMessage } from "@lit/localize-tools/src/messages.js"
|
||||||
|
* @import { Locale } from "@lit/localize-tools/src/types/locale.js"
|
||||||
|
*/
|
||||||
|
import { PackageRoot } from "@goauthentik/web/paths";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
import pseudolocale from "pseudolocale";
|
import pseudolocale from "pseudolocale";
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
|
|
||||||
import { makeFormatter } from "@lit/localize-tools/lib/formatters/index.js";
|
import { makeFormatter } from "@lit/localize-tools/lib/formatters/index.js";
|
||||||
import { sortProgramMessages } from "@lit/localize-tools/lib/messages.js";
|
import { sortProgramMessages } from "@lit/localize-tools/lib/messages.js";
|
||||||
import { TransformLitLocalizer } from "@lit/localize-tools/lib/modes/transform.js";
|
import { TransformLitLocalizer } from "@lit/localize-tools/lib/modes/transform.js";
|
||||||
|
|
||||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
const pseudoLocale = /** @type {Locale} */ ("pseudo-LOCALE");
|
||||||
const pseudoLocale = "pseudo-LOCALE";
|
|
||||||
const targetLocales = [pseudoLocale];
|
const targetLocales = [pseudoLocale];
|
||||||
const baseConfig = JSON.parse(readFileSync(path.join(__dirname, "../lit-localize.json"), "utf-8"));
|
|
||||||
|
/**
|
||||||
|
* @type {ConfigFile}
|
||||||
|
*/
|
||||||
|
const baseConfig = JSON.parse(readFileSync(path.join(PackageRoot, "lit-localize.json"), "utf-8"));
|
||||||
|
|
||||||
// Need to make some internal specifications to satisfy the transformer. It doesn't actually matter
|
// Need to make some internal specifications to satisfy the transformer. It doesn't actually matter
|
||||||
// which Localizer we use (transformer or runtime), because all of the functionality we care about
|
// which Localizer we use (transformer or runtime), because all of the functionality we care about
|
||||||
// is in their common parent class, but I had to pick one. Everything else here is just pure
|
// is in their common parent class, but I had to pick one. Everything else here is just pure
|
||||||
// exploitation of the lit/localize-tools internals.
|
// exploitation of the lit/localize-tools internals.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @satisfies {Config}
|
||||||
|
*/
|
||||||
const config = {
|
const config = {
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
baseDir: path.join(__dirname, ".."),
|
baseDir: path.join(__dirname, ".."),
|
||||||
@ -28,6 +42,11 @@ const config = {
|
|||||||
resolve: (path) => path,
|
resolve: (path) => path,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {ProgramMessage} message
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
const pseudoMessagify = (message) => ({
|
const pseudoMessagify = (message) => ({
|
||||||
name: message.name,
|
name: message.name,
|
||||||
contents: message.contents.map((content) =>
|
contents: message.contents.map((content) =>
|
||||||
@ -36,7 +55,7 @@ const pseudoMessagify = (message) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const localizer = new TransformLitLocalizer(config);
|
const localizer = new TransformLitLocalizer(config);
|
||||||
const messages = localizer.extractSourceMessages().messages;
|
const { messages } = localizer.extractSourceMessages();
|
||||||
const translations = messages.map(pseudoMessagify);
|
const translations = messages.map(pseudoMessagify);
|
||||||
const sorted = sortProgramMessages([...messages]);
|
const sorted = sortProgramMessages([...messages]);
|
||||||
const formatter = makeFormatter(config);
|
const formatter = makeFormatter(config);
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { VERSION } from "@goauthentik/common/constants";
|
|
||||||
import { globalAK } from "@goauthentik/common/global";
|
import { globalAK } from "@goauthentik/common/global";
|
||||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
import { DefaultBrand } from "@goauthentik/common/ui/config";
|
||||||
import "@goauthentik/elements/EmptyState";
|
import "@goauthentik/elements/EmptyState";
|
||||||
@ -45,7 +44,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
|||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
[msg("Version"), version.versionCurrent],
|
[msg("Version"), version.versionCurrent],
|
||||||
[msg("UI Version"), VERSION],
|
[msg("UI Version"), import.meta.env.AK_VERSION],
|
||||||
[msg("Build"), build],
|
[msg("Build"), build],
|
||||||
[msg("Python version"), status.runtime.pythonVersion],
|
[msg("Python version"), status.runtime.pythonVersion],
|
||||||
[msg("Platform"), status.runtime.platform],
|
[msg("Platform"), status.runtime.platform],
|
||||||
|
|||||||
@ -4,13 +4,13 @@ import { ROUTES } from "@goauthentik/admin/Routes";
|
|||||||
import {
|
import {
|
||||||
EVENT_API_DRAWER_TOGGLE,
|
EVENT_API_DRAWER_TOGGLE,
|
||||||
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
||||||
EVENT_SIDEBAR_TOGGLE,
|
|
||||||
} from "@goauthentik/common/constants";
|
} from "@goauthentik/common/constants";
|
||||||
import { configureSentry } from "@goauthentik/common/sentry";
|
import { configureSentry } from "@goauthentik/common/sentry";
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||||
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
|
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
|
||||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
|
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
|
||||||
|
import { SidebarToggleEventDetail } from "@goauthentik/elements/PageHeader";
|
||||||
import "@goauthentik/elements/ak-locale-context";
|
import "@goauthentik/elements/ak-locale-context";
|
||||||
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
|
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
|
||||||
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
|
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
|
||||||
@ -26,7 +26,7 @@ import "@goauthentik/elements/sidebar/Sidebar";
|
|||||||
import "@goauthentik/elements/sidebar/SidebarItem";
|
import "@goauthentik/elements/sidebar/SidebarItem";
|
||||||
|
|
||||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators.js";
|
import { customElement, eventOptions, property, query } from "lit/decorators.js";
|
||||||
import { classMap } from "lit/directives/class-map.js";
|
import { classMap } from "lit/directives/class-map.js";
|
||||||
|
|
||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
@ -43,7 +43,7 @@ import {
|
|||||||
renderSidebarItems,
|
renderSidebarItems,
|
||||||
} from "./AdminSidebar.js";
|
} from "./AdminSidebar.js";
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (import.meta.env.NODE_ENV === "development") {
|
||||||
await import("@goauthentik/esbuild-plugin-live-reload/client");
|
await import("@goauthentik/esbuild-plugin-live-reload/client");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,28 +52,33 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
//#region Properties
|
//#region Properties
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
public notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
apiDrawerOpen = getURLParam("apiDrawerOpen", false);
|
public apiDrawerOpen = getURLParam("apiDrawerOpen", false);
|
||||||
|
|
||||||
ws: WebsocketClient;
|
protected readonly ws: WebsocketClient;
|
||||||
|
|
||||||
@state()
|
@property({
|
||||||
user?: SessionUser;
|
type: Object,
|
||||||
|
attribute: false,
|
||||||
|
reflect: false,
|
||||||
|
})
|
||||||
|
public user?: SessionUser;
|
||||||
|
|
||||||
@query("ak-about-modal")
|
@query("ak-about-modal")
|
||||||
aboutModal?: AboutModal;
|
public aboutModal?: AboutModal;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
public sidebarOpen: boolean;
|
public sidebarOpen: boolean;
|
||||||
|
|
||||||
#toggleSidebar = () => {
|
@eventOptions({ passive: true })
|
||||||
this.sidebarOpen = !this.sidebarOpen;
|
protected sidebarListener(event: CustomEvent<SidebarToggleEventDetail>) {
|
||||||
};
|
this.sidebarOpen = !!event.detail.open;
|
||||||
|
}
|
||||||
|
|
||||||
#sidebarMatcher: MediaQueryList;
|
#sidebarMatcher: MediaQueryList;
|
||||||
#sidebarListener = (event: MediaQueryListEvent) => {
|
#sidebarMediaQueryListener = (event: MediaQueryListEvent) => {
|
||||||
this.sidebarOpen = event.matches;
|
this.sidebarOpen = event.matches;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,59 +86,57 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
|
|
||||||
//#region Styles
|
//#region Styles
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static styles: CSSResult[] = [
|
||||||
return [
|
PFBase,
|
||||||
PFBase,
|
PFPage,
|
||||||
PFPage,
|
PFButton,
|
||||||
PFButton,
|
PFDrawer,
|
||||||
PFDrawer,
|
PFNav,
|
||||||
PFNav,
|
css`
|
||||||
css`
|
.pf-c-page__main,
|
||||||
.pf-c-page__main,
|
.pf-c-drawer__content,
|
||||||
.pf-c-drawer__content,
|
.pf-c-page__drawer {
|
||||||
.pf-c-page__drawer {
|
z-index: auto !important;
|
||||||
z-index: auto !important;
|
background-color: transparent;
|
||||||
background-color: transparent;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.display-none {
|
.display-none {
|
||||||
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 {
|
||||||
background-color: var(--pf-c-page--BackgroundColor) !important;
|
--pf-c-page--BackgroundColor: var(--ak-dark-background);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:host([theme="dark"]) {
|
ak-page-navbar {
|
||||||
/* Global page background colour */
|
grid-area: header;
|
||||||
.pf-c-page {
|
}
|
||||||
--pf-c-page--BackgroundColor: var(--ak-dark-background);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ak-page-navbar {
|
.ak-sidebar {
|
||||||
grid-area: header;
|
grid-area: nav;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ak-sidebar {
|
.pf-c-drawer__panel {
|
||||||
grid-area: nav;
|
z-index: var(--pf-global--ZIndex--xl);
|
||||||
}
|
}
|
||||||
|
`,
|
||||||
.pf-c-drawer__panel {
|
];
|
||||||
z-index: var(--pf-global--ZIndex--xl);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Lifecycle
|
//#region Lifecycle
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
configureSentry(true);
|
||||||
super();
|
super();
|
||||||
this.ws = new WebsocketClient();
|
this.ws = new WebsocketClient();
|
||||||
|
|
||||||
this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
|
this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
|
||||||
this.sidebarOpen = this.#sidebarMatcher.matches;
|
this.sidebarOpen = this.#sidebarMatcher.matches;
|
||||||
}
|
}
|
||||||
@ -141,8 +144,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
window.addEventListener(EVENT_SIDEBAR_TOGGLE, this.#toggleSidebar);
|
|
||||||
|
|
||||||
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
|
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
|
||||||
this.notificationDrawerOpen = !this.notificationDrawerOpen;
|
this.notificationDrawerOpen = !this.notificationDrawerOpen;
|
||||||
updateURLParams({
|
updateURLParams({
|
||||||
@ -157,17 +158,17 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#sidebarMatcher.addEventListener("change", this.#sidebarListener);
|
this.#sidebarMatcher.addEventListener("change", this.#sidebarMediaQueryListener, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
window.removeEventListener(EVENT_SIDEBAR_TOGGLE, this.#toggleSidebar);
|
this.#sidebarMatcher.removeEventListener("change", this.#sidebarMediaQueryListener);
|
||||||
this.#sidebarMatcher.removeEventListener("change", this.#sidebarListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated(): Promise<void> {
|
async firstUpdated(): Promise<void> {
|
||||||
configureSentry(true);
|
|
||||||
this.user = await me();
|
this.user = await me();
|
||||||
|
|
||||||
const canAccessAdmin =
|
const canAccessAdmin =
|
||||||
@ -197,7 +198,7 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
|
|
||||||
return html` <ak-locale-context>
|
return html` <ak-locale-context>
|
||||||
<div class="pf-c-page">
|
<div class="pf-c-page">
|
||||||
<ak-page-navbar>
|
<ak-page-navbar ?open=${this.sidebarOpen} @sidebar-toggle=${this.sidebarListener}>
|
||||||
<ak-version-banner></ak-version-banner>
|
<ak-version-banner></ak-version-banner>
|
||||||
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||||
</ak-page-navbar>
|
</ak-page-navbar>
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import "@goauthentik/admin/admin-overview/cards/WorkerStatusCard";
|
|||||||
import "@goauthentik/admin/admin-overview/charts/AdminLoginAuthorizeChart";
|
import "@goauthentik/admin/admin-overview/charts/AdminLoginAuthorizeChart";
|
||||||
import "@goauthentik/admin/admin-overview/charts/OutpostStatusChart";
|
import "@goauthentik/admin/admin-overview/charts/OutpostStatusChart";
|
||||||
import "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
|
import "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
|
||||||
import { VERSION } from "@goauthentik/common/constants";
|
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
|
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
|
||||||
@ -22,8 +21,6 @@ import { msg, str } from "@lit/localize";
|
|||||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
import { classMap } from "lit/directives/class-map.js";
|
import { classMap } from "lit/directives/class-map.js";
|
||||||
import { map } from "lit/directives/map.js";
|
|
||||||
import { when } from "lit/directives/when.js";
|
|
||||||
|
|
||||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||||
import PFDivider from "@patternfly/patternfly/components/Divider/divider.css";
|
import PFDivider from "@patternfly/patternfly/components/Divider/divider.css";
|
||||||
@ -33,21 +30,17 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
|||||||
|
|
||||||
import { SessionUser } from "@goauthentik/api";
|
import { SessionUser } from "@goauthentik/api";
|
||||||
|
|
||||||
export function versionFamily(): string {
|
function createReleaseNotesURL(semver: string): URL {
|
||||||
const parts = VERSION.split(".");
|
const segments = semver.split(".");
|
||||||
parts.pop();
|
const versionFamily = segments.slice(0, -1).join(".");
|
||||||
return parts.join(".");
|
|
||||||
|
const release = `${versionFamily}#fixed-in-${segments.join("")}`;
|
||||||
|
|
||||||
|
return new URL(`/docs/releases/${release}`, "https://goauthentik.io");
|
||||||
}
|
}
|
||||||
|
|
||||||
const RELEASE = `${VERSION.split(".").slice(0, -1).join(".")}#fixed-in-${VERSION.replaceAll(
|
|
||||||
".",
|
|
||||||
"",
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
const AdminOverviewBase = WithLicenseSummary(AKElement);
|
const AdminOverviewBase = WithLicenseSummary(AKElement);
|
||||||
|
|
||||||
type Renderer = () => TemplateResult | typeof nothing;
|
|
||||||
|
|
||||||
@customElement("ak-admin-overview")
|
@customElement("ak-admin-overview")
|
||||||
export class AdminOverviewPage extends AdminOverviewBase {
|
export class AdminOverviewPage extends AdminOverviewBase {
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
@ -83,7 +76,11 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
|||||||
[msg("Check the logs"), paramURL("/events/log")],
|
[msg("Check the logs"), paramURL("/events/log")],
|
||||||
[msg("Explore integrations"), "https://goauthentik.io/integrations/", true],
|
[msg("Explore integrations"), "https://goauthentik.io/integrations/", true],
|
||||||
[msg("Manage users"), paramURL("/identity/users")],
|
[msg("Manage users"), paramURL("/identity/users")],
|
||||||
[msg("Check the release notes"), `https://goauthentik.io/docs/releases/${RELEASE}`, true],
|
[
|
||||||
|
msg("Check the release notes"),
|
||||||
|
createReleaseNotesURL(import.meta.env.AK_VERSION).href,
|
||||||
|
true,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
@ -193,45 +190,6 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
|||||||
</div>`
|
</div>`
|
||||||
: nothing} `;
|
: nothing} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderActions() {
|
|
||||||
const release = `${versionFamily()}#fixed-in-${VERSION.replaceAll(".", "")}`;
|
|
||||||
|
|
||||||
const quickActions: [string, string][] = [
|
|
||||||
[msg("Create a new application"), paramURL("/core/applications", { createForm: true })],
|
|
||||||
[msg("Check the logs"), paramURL("/events/log")],
|
|
||||||
[msg("Explore integrations"), "https://goauthentik.io/integrations/"],
|
|
||||||
[msg("Manage users"), paramURL("/identity/users")],
|
|
||||||
[msg("Check the release notes"), `https://goauthentik.io/docs/releases/${release}`],
|
|
||||||
];
|
|
||||||
|
|
||||||
const action = ([label, url]: [string, string]) => {
|
|
||||||
const isExternal = url.startsWith("https://");
|
|
||||||
const ex = (truecase: Renderer, falsecase: Renderer) =>
|
|
||||||
when(isExternal, truecase, falsecase);
|
|
||||||
|
|
||||||
const content = html`${label}${ex(
|
|
||||||
() => html`<i class="fas fa-external-link-alt ak-external-link"></i>`,
|
|
||||||
() => nothing,
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
return html`<li>
|
|
||||||
${ex(
|
|
||||||
() =>
|
|
||||||
html`<a
|
|
||||||
href="${url}"
|
|
||||||
class="pf-u-mb-xl"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>${content}</a
|
|
||||||
>`,
|
|
||||||
() => html`<a href="${url}" class="pf-u-mb-xl" )>${content}</a>`,
|
|
||||||
)}
|
|
||||||
</li>`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return html`${map(quickActions, action)}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@ -113,8 +113,7 @@ export class ApplicationViewPage extends AKElement {
|
|||||||
|
|
||||||
renderApp(): TemplateResult {
|
renderApp(): TemplateResult {
|
||||||
if (!this.application) {
|
if (!this.application) {
|
||||||
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
|
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
||||||
</ak-empty-state>`;
|
|
||||||
}
|
}
|
||||||
return html`<ak-tabs>
|
return html`<ak-tabs>
|
||||||
${this.missingOutpost
|
${this.missingOutpost
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import { type LocalTypeCreate } from "./ProviderChoices.js";
|
|||||||
|
|
||||||
@customElement("ak-application-wizard-provider-choice-step")
|
@customElement("ak-application-wizard-provider-choice-step")
|
||||||
export class ApplicationWizardProviderChoiceStep extends WithLicenseSummary(ApplicationWizardStep) {
|
export class ApplicationWizardProviderChoiceStep extends WithLicenseSummary(ApplicationWizardStep) {
|
||||||
label = msg("Choose A Provider");
|
label = msg("Choose a Provider");
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
failureMessage = "";
|
failureMessage = "";
|
||||||
|
|||||||
@ -45,9 +45,9 @@ const providerListArgs = (page: number, search = "") => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dualSelectPairMaker = (item: ProviderBase): DualSelectPair => {
|
const dualSelectPairMaker = (item: ProviderBase): DualSelectPair => {
|
||||||
const label = item.assignedBackchannelApplicationName
|
const label =
|
||||||
? item.assignedBackchannelApplicationName
|
item.assignedBackchannelApplicationName || item.assignedApplicationName || item.name;
|
||||||
: item.assignedApplicationName;
|
|
||||||
return [
|
return [
|
||||||
`${item.pk}`,
|
`${item.pk}`,
|
||||||
html`<div class="selection-main">${label}</div>
|
html`<div class="selection-main">${label}</div>
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { DetailedCountry, GeoIPPolicy, PoliciesApi } from "@goauthentik/api";
|
|||||||
import { countryCache } from "./CountryCache";
|
import { countryCache } from "./CountryCache";
|
||||||
|
|
||||||
function countryToPair(country: DetailedCountry): DualSelectPair {
|
function countryToPair(country: DetailedCountry): DualSelectPair {
|
||||||
return [country.code, country.name];
|
return [country.code, country.name, country.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ak-policy-geoip-form")
|
@customElement("ak-policy-geoip-form")
|
||||||
@ -210,17 +210,16 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> {
|
|||||||
.getCountries()
|
.getCountries()
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
if (!search) return results;
|
if (!search) return results;
|
||||||
|
|
||||||
return results.filter((result) =>
|
return results.filter((result) =>
|
||||||
result.name
|
result.name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(search.toLowerCase()),
|
.includes(search.toLowerCase()),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.then((results) => {
|
.then((results) => ({
|
||||||
return {
|
options: results.map(countryToPair),
|
||||||
options: results.map(countryToPair),
|
}));
|
||||||
};
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
.selected=${(this.instance?.countriesObj ?? []).map(countryToPair)}
|
.selected=${(this.instance?.countriesObj ?? []).map(countryToPair)}
|
||||||
available-label="${msg("Available Countries")}"
|
available-label="${msg("Available Countries")}"
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export class ProviderViewPage extends AKElement {
|
|||||||
|
|
||||||
renderProvider(): TemplateResult {
|
renderProvider(): TemplateResult {
|
||||||
if (!this.provider) {
|
if (!this.provider) {
|
||||||
return html`<ak-empty-state ?loading=${true} ?fullHeight=${true}></ak-empty-state>`;
|
return html`<ak-empty-state loading ?fullHeight=${true}></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
switch (this.provider?.component) {
|
switch (this.provider?.component) {
|
||||||
case "ak-provider-saml-form":
|
case "ak-provider-saml-form":
|
||||||
|
|||||||
@ -432,7 +432,7 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
${this.preview
|
${this.preview
|
||||||
? html`<pre>${JSON.stringify(this.preview?.preview, null, 4)}</pre>`
|
? html`<pre>${JSON.stringify(this.preview?.preview, null, 4)}</pre>`
|
||||||
: html` <ak-empty-state ?loading=${true}></ak-empty-state> `}
|
: html` <ak-empty-state loading></ak-empty-state> `}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|||||||
@ -502,7 +502,7 @@ export class SAMLProviderViewPage extends AKElement {
|
|||||||
|
|
||||||
renderTabPreview(): TemplateResult {
|
renderTabPreview(): TemplateResult {
|
||||||
if (!this.preview) {
|
if (!this.preview) {
|
||||||
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
return html`<ak-empty-state loading></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
return html` <div
|
return html` <div
|
||||||
class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"
|
class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export class SourceViewPage extends AKElement {
|
|||||||
|
|
||||||
renderSource(): TemplateResult {
|
renderSource(): TemplateResult {
|
||||||
if (!this.source) {
|
if (!this.source) {
|
||||||
return html`<ak-empty-state ?loading=${true} ?fullHeight=${true}></ak-empty-state>`;
|
return html`<ak-empty-state loading ?fullHeight=${true}></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
switch (this.source?.component) {
|
switch (this.source?.component) {
|
||||||
case "ak-source-kerberos-form":
|
case "ak-source-kerberos-form":
|
||||||
|
|||||||
@ -1,18 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
CSRFHeaderName,
|
|
||||||
CSRFMiddleware,
|
CSRFMiddleware,
|
||||||
EventMiddleware,
|
EventMiddleware,
|
||||||
LoggingMiddleware,
|
LoggingMiddleware,
|
||||||
} from "@goauthentik/common/api/middleware";
|
} from "@goauthentik/common/api/middleware.js";
|
||||||
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants";
|
import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants.js";
|
||||||
import { globalAK } from "@goauthentik/common/global";
|
import { globalAK } from "@goauthentik/common/global.js";
|
||||||
|
import { SentryMiddleware } from "@goauthentik/common/sentry";
|
||||||
|
|
||||||
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
|
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
|
||||||
|
|
||||||
// HACK: Workaround for ESBuild not being able to hoist import statement across entrypoints.
|
|
||||||
// This can be removed after ESBuild uses a single build context for all entrypoints.
|
|
||||||
export { CSRFHeaderName };
|
|
||||||
|
|
||||||
let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config);
|
let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config);
|
||||||
export function config(): Promise<Config> {
|
export function config(): Promise<Config> {
|
||||||
if (!globalConfigPromise) {
|
if (!globalConfigPromise) {
|
||||||
@ -66,21 +62,13 @@ export function brand(): Promise<CurrentBrand> {
|
|||||||
return globalBrandPromise;
|
return globalBrandPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMetaContent(key: string): string {
|
|
||||||
const metaEl = document.querySelector<HTMLMetaElement>(`meta[name=${key}]`);
|
|
||||||
if (!metaEl) return "";
|
|
||||||
return metaEl.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DEFAULT_CONFIG = new Configuration({
|
export const DEFAULT_CONFIG = new Configuration({
|
||||||
basePath: `${globalAK().api.base}api/v3`,
|
basePath: `${globalAK().api.base}api/v3`,
|
||||||
headers: {
|
|
||||||
"sentry-trace": getMetaContent("sentry-trace"),
|
|
||||||
},
|
|
||||||
middleware: [
|
middleware: [
|
||||||
new CSRFMiddleware(),
|
new CSRFMiddleware(),
|
||||||
new EventMiddleware(),
|
new EventMiddleware(),
|
||||||
new LoggingMiddleware(globalAK().brand),
|
new LoggingMiddleware(globalAK().brand),
|
||||||
|
new SentryMiddleware(),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -91,4 +79,6 @@ export function AndNext(url: string): string {
|
|||||||
return `?next=${encodeURIComponent(url)}`;
|
return `?next=${encodeURIComponent(url)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(`authentik(early): version ${VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`);
|
console.debug(
|
||||||
|
`authentik(early): version ${import.meta.env.AK_VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`,
|
||||||
|
);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { EVENT_REQUEST_POST } from "@goauthentik/common/constants";
|
import { EVENT_REQUEST_POST } from "@goauthentik/common/constants.js";
|
||||||
import { getCookie } from "@goauthentik/common/utils";
|
import { getCookie } from "@goauthentik/common/utils.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CurrentBrand,
|
CurrentBrand,
|
||||||
|
|||||||
@ -1,17 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @file Global constants used throughout the application.
|
||||||
|
*
|
||||||
|
* @todo Much of this content can be moved to a specific file, element, or component.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference types="../../types/esbuild.js" />
|
||||||
|
|
||||||
|
//#region Patternfly
|
||||||
|
|
||||||
export const SECONDARY_CLASS = "pf-m-secondary";
|
export const SECONDARY_CLASS = "pf-m-secondary";
|
||||||
export const SUCCESS_CLASS = "pf-m-success";
|
export const SUCCESS_CLASS = "pf-m-success";
|
||||||
export const ERROR_CLASS = "pf-m-danger";
|
export const ERROR_CLASS = "pf-m-danger";
|
||||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||||
export const CURRENT_CLASS = "pf-m-current";
|
export const CURRENT_CLASS = "pf-m-current";
|
||||||
export const VERSION = "2025.4.0";
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Application
|
||||||
|
|
||||||
export const TITLE_DEFAULT = "authentik";
|
export const TITLE_DEFAULT = "authentik";
|
||||||
|
/**
|
||||||
|
* The delimiter used to parse the URL for the current route.
|
||||||
|
*
|
||||||
|
* @todo Move this to the ak-router.
|
||||||
|
*/
|
||||||
export const ROUTE_SEPARATOR = ";";
|
export const ROUTE_SEPARATOR = ";";
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Events
|
||||||
|
|
||||||
export const EVENT_REFRESH = "ak-refresh";
|
export const EVENT_REFRESH = "ak-refresh";
|
||||||
export const EVENT_NOTIFICATION_DRAWER_TOGGLE = "ak-notification-toggle";
|
export const EVENT_NOTIFICATION_DRAWER_TOGGLE = "ak-notification-toggle";
|
||||||
export const EVENT_API_DRAWER_TOGGLE = "ak-api-drawer-toggle";
|
export const EVENT_API_DRAWER_TOGGLE = "ak-api-drawer-toggle";
|
||||||
export const EVENT_FLOW_INSPECTOR_TOGGLE = "ak-flow-inspector-toggle";
|
export const EVENT_FLOW_INSPECTOR_TOGGLE = "ak-flow-inspector-toggle";
|
||||||
export const EVENT_SIDEBAR_TOGGLE = "ak-sidebar-toggle";
|
|
||||||
export const EVENT_WS_MESSAGE = "ak-ws-message";
|
export const EVENT_WS_MESSAGE = "ak-ws-message";
|
||||||
export const EVENT_FLOW_ADVANCE = "ak-flow-advance";
|
export const EVENT_FLOW_ADVANCE = "ak-flow-advance";
|
||||||
export const EVENT_LOCALE_CHANGE = "ak-locale-change";
|
export const EVENT_LOCALE_CHANGE = "ak-locale-change";
|
||||||
@ -21,7 +43,17 @@ export const EVENT_MESSAGE = "ak-message";
|
|||||||
export const EVENT_THEME_CHANGE = "ak-theme-change";
|
export const EVENT_THEME_CHANGE = "ak-theme-change";
|
||||||
export const EVENT_REFRESH_ENTERPRISE = "ak-refresh-enterprise";
|
export const EVENT_REFRESH_ENTERPRISE = "ak-refresh-enterprise";
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region WebSocket
|
||||||
|
|
||||||
export const WS_MSG_TYPE_MESSAGE = "message";
|
export const WS_MSG_TYPE_MESSAGE = "message";
|
||||||
export const WS_MSG_TYPE_REFRESH = "refresh";
|
export const WS_MSG_TYPE_REFRESH = "refresh";
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region LocalStorage
|
||||||
|
|
||||||
export const LOCALSTORAGE_AUTHENTIK_KEY = "authentik-local-settings";
|
export const LOCALSTORAGE_AUTHENTIK_KEY = "authentik-local-settings";
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { VERSION } from "@goauthentik/common/constants";
|
|
||||||
import { SentryIgnoredError } from "@goauthentik/common/sentry";
|
import { SentryIgnoredError } from "@goauthentik/common/sentry";
|
||||||
|
|
||||||
export interface PlexPinResponse {
|
export interface PlexPinResponse {
|
||||||
@ -19,7 +18,7 @@ export const DEFAULT_HEADERS = {
|
|||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-Plex-Product": "authentik",
|
"X-Plex-Product": "authentik",
|
||||||
"X-Plex-Version": VERSION,
|
"X-Plex-Version": import.meta.env.AK_VERSION,
|
||||||
"X-Plex-Device-Vendor": "goauthentik.io",
|
"X-Plex-Device-Vendor": "goauthentik.io",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { config } from "@goauthentik/common/api/config";
|
import { globalAK } from "@goauthentik/common/global";
|
||||||
import { VERSION } from "@goauthentik/common/constants";
|
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
|
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
|
||||||
import {
|
import {
|
||||||
@ -10,8 +9,16 @@ import {
|
|||||||
setTag,
|
setTag,
|
||||||
setUser,
|
setUser,
|
||||||
} from "@sentry/browser";
|
} from "@sentry/browser";
|
||||||
|
import { getTraceData } from "@sentry/core";
|
||||||
|
import * as Spotlight from "@spotlightjs/spotlight";
|
||||||
|
|
||||||
import { CapabilitiesEnum, Config, ResponseError } from "@goauthentik/api";
|
import {
|
||||||
|
CapabilitiesEnum,
|
||||||
|
FetchParams,
|
||||||
|
Middleware,
|
||||||
|
RequestContext,
|
||||||
|
ResponseError,
|
||||||
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic error that can be thrown without triggering Sentry's reporting.
|
* A generic error that can be thrown without triggering Sentry's reporting.
|
||||||
@ -21,69 +28,94 @@ export class SentryIgnoredError extends Error {}
|
|||||||
export const TAG_SENTRY_COMPONENT = "authentik.component";
|
export const TAG_SENTRY_COMPONENT = "authentik.component";
|
||||||
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
|
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
|
||||||
|
|
||||||
export async function configureSentry(canDoPpi = false): Promise<Config> {
|
let _sentryConfigured = false;
|
||||||
const cfg = await config();
|
|
||||||
|
|
||||||
if (cfg.errorReporting.enabled) {
|
export function configureSentry(canDoPpi = false) {
|
||||||
init({
|
const cfg = globalAK().config;
|
||||||
dsn: cfg.errorReporting.sentryDsn,
|
const debug = cfg.capabilities.includes(CapabilitiesEnum.CanDebug);
|
||||||
ignoreErrors: [
|
if (!cfg.errorReporting.enabled && !debug) {
|
||||||
/network/gi,
|
return cfg;
|
||||||
/fetch/gi,
|
}
|
||||||
/module/gi,
|
init({
|
||||||
// Error on edge on ios,
|
dsn: cfg.errorReporting.sentryDsn,
|
||||||
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
|
ignoreErrors: [
|
||||||
/instantSearchSDKJSBridgeClearHighlight/gi,
|
/network/gi,
|
||||||
// Seems to be an issue in Safari and Firefox
|
/fetch/gi,
|
||||||
/MutationObserver.observe/gi,
|
/module/gi,
|
||||||
/NS_ERROR_FAILURE/gi,
|
// Error on edge on ios,
|
||||||
],
|
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
|
||||||
release: `authentik@${VERSION}`,
|
/instantSearchSDKJSBridgeClearHighlight/gi,
|
||||||
|
// Seems to be an issue in Safari and Firefox
|
||||||
|
/MutationObserver.observe/gi,
|
||||||
|
/NS_ERROR_FAILURE/gi,
|
||||||
|
],
|
||||||
|
release: `authentik@${import.meta.env.AK_VERSION}`,
|
||||||
|
integrations: [
|
||||||
|
browserTracingIntegration({
|
||||||
|
// https://docs.sentry.io/platforms/javascript/tracing/instrumentation/automatic-instrumentation/#custom-routing
|
||||||
|
instrumentNavigation: false,
|
||||||
|
instrumentPageLoad: false,
|
||||||
|
traceFetch: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
tracePropagationTargets: [window.location.origin],
|
||||||
|
tracesSampleRate: debug ? 1.0 : cfg.errorReporting.tracesSampleRate,
|
||||||
|
environment: cfg.errorReporting.environment,
|
||||||
|
beforeSend: (
|
||||||
|
event: ErrorEvent,
|
||||||
|
hint: EventHint,
|
||||||
|
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
|
||||||
|
if (!hint) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
if (hint.originalException instanceof SentryIgnoredError) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
hint.originalException instanceof ResponseError ||
|
||||||
|
hint.originalException instanceof DOMException
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
|
||||||
|
if (window.location.pathname.includes("if/")) {
|
||||||
|
setTag(TAG_SENTRY_COMPONENT, `web/${readInterfaceRouteParam()}`);
|
||||||
|
}
|
||||||
|
if (debug) {
|
||||||
|
Spotlight.init({
|
||||||
|
injectImmediately: true,
|
||||||
integrations: [
|
integrations: [
|
||||||
browserTracingIntegration({
|
Spotlight.sentry({
|
||||||
shouldCreateSpanForRequest: (url: string) => {
|
injectIntoSDK: true,
|
||||||
return url.startsWith(window.location.host);
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
tracesSampleRate: cfg.errorReporting.tracesSampleRate,
|
|
||||||
environment: cfg.errorReporting.environment,
|
|
||||||
beforeSend: (
|
|
||||||
event: ErrorEvent,
|
|
||||||
hint: EventHint,
|
|
||||||
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
|
|
||||||
if (!hint) {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
if (hint.originalException instanceof SentryIgnoredError) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
hint.originalException instanceof ResponseError ||
|
|
||||||
hint.originalException instanceof DOMException
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return event;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
|
console.debug("authentik/config: Enabled Sentry Spotlight");
|
||||||
if (window.location.pathname.includes("if/")) {
|
}
|
||||||
setTag(TAG_SENTRY_COMPONENT, `web/${readInterfaceRouteParam()}`);
|
if (cfg.errorReporting.sendPii && canDoPpi) {
|
||||||
}
|
me().then((user) => {
|
||||||
if (cfg.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
setUser({ email: user.user.email });
|
||||||
const Spotlight = await import("@spotlightjs/spotlight");
|
console.debug("authentik/config: Sentry with PII enabled.");
|
||||||
|
});
|
||||||
Spotlight.init({ injectImmediately: true });
|
} else {
|
||||||
}
|
console.debug("authentik/config: Sentry enabled.");
|
||||||
if (cfg.errorReporting.sendPii && canDoPpi) {
|
}
|
||||||
me().then((user) => {
|
_sentryConfigured = true;
|
||||||
setUser({ email: user.user.email });
|
}
|
||||||
console.debug("authentik/config: Sentry with PII enabled.");
|
|
||||||
});
|
export class SentryMiddleware implements Middleware {
|
||||||
} else {
|
pre?(context: RequestContext): Promise<FetchParams | void> {
|
||||||
console.debug("authentik/config: Sentry enabled.");
|
if (!_sentryConfigured) {
|
||||||
}
|
return Promise.resolve(context);
|
||||||
|
}
|
||||||
|
const traceData = getTraceData();
|
||||||
|
// @ts-ignore
|
||||||
|
context.init.headers["baggage"] = traceData["baggage"];
|
||||||
|
// @ts-ignore
|
||||||
|
context.init.headers["sentry-trace"] = traceData["sentry-trace"];
|
||||||
|
return Promise.resolve(context);
|
||||||
}
|
}
|
||||||
return cfg;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file authentik base UI theme.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Defined to better identify the base theme when debugging constructed stylesheets. */
|
||||||
|
.__AK_UI_BASE__ {
|
||||||
|
--__AK_UI_BASE__: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* #region Global */
|
/* #region Global */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@ -1,42 +1,48 @@
|
|||||||
/*
|
/**
|
||||||
|
* @file Atom One Dark syntax highlighting theme.
|
||||||
|
*
|
||||||
|
* @see https://github.com/atom/one-dark-syntax
|
||||||
|
*/
|
||||||
|
|
||||||
Atom One Dark by Daniel Gamage
|
/* Defined to better identify the One Dark theme when debugging constructed stylesheets. */
|
||||||
Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax
|
.__HIGHLIGHT_THEME_ONE_DARK__ {
|
||||||
|
--__HIGHLIGHT_THEME_ONE_DARK__: 1;
|
||||||
|
}
|
||||||
|
|
||||||
base: #282c34
|
:root {
|
||||||
mono-1: #abb2bf
|
--one-dark-base: #282c34;
|
||||||
mono-2: #818896
|
--one-dark-mono-1: #abb2bf;
|
||||||
mono-3: #5c6370
|
--one-dark-mono-2: #818896;
|
||||||
hue-1: #56b6c2
|
--one-dark-mono-3: #5c6370;
|
||||||
hue-2: #61aeee
|
--one-dark-hue-1: #56b6c2;
|
||||||
hue-3: #c678dd
|
--one-dark-hue-2: #61aeee;
|
||||||
hue-4: #98c379
|
--one-dark-hue-3: #c678dd;
|
||||||
hue-5: #e06c75
|
--one-dark-hue-4: #98c379;
|
||||||
hue-5-2: #be5046
|
--one-dark-hue-5: #e06c75;
|
||||||
hue-6: #d19a66
|
--one-dark-hue-5-2: #be5046;
|
||||||
hue-6-2: #e6c07b
|
--one-dark-hue-6: #d19a66;
|
||||||
|
--one-dark-hue-6-2: #e6c07b;
|
||||||
*/
|
}
|
||||||
|
|
||||||
.hljs {
|
.hljs {
|
||||||
color: #abb2bf;
|
color: var(--one-dark-mono-1);
|
||||||
background: #282c34;
|
background: var(--one-dark-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
pre:has(.hljs) {
|
pre:has(.hljs) {
|
||||||
background: #282c34;
|
background: var(--one-dark-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-comment,
|
.hljs-comment,
|
||||||
.hljs-quote {
|
.hljs-quote {
|
||||||
color: #5c6370;
|
color: var(--one-dark-mono-3);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-doctag,
|
.hljs-doctag,
|
||||||
.hljs-keyword,
|
.hljs-keyword,
|
||||||
.hljs-formula {
|
.hljs-formula {
|
||||||
color: #c678dd;
|
color: var(--one-dark-hue-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-section,
|
.hljs-section,
|
||||||
@ -44,11 +50,11 @@ pre:has(.hljs) {
|
|||||||
.hljs-selector-tag,
|
.hljs-selector-tag,
|
||||||
.hljs-deletion,
|
.hljs-deletion,
|
||||||
.hljs-subst {
|
.hljs-subst {
|
||||||
color: #e06c75;
|
color: var(--one-dark-hue-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-literal {
|
.hljs-literal {
|
||||||
color: #56b6c2;
|
color: var(--one-dark-hue-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-string,
|
.hljs-string,
|
||||||
@ -56,7 +62,7 @@ pre:has(.hljs) {
|
|||||||
.hljs-addition,
|
.hljs-addition,
|
||||||
.hljs-attribute,
|
.hljs-attribute,
|
||||||
.hljs-meta .hljs-string {
|
.hljs-meta .hljs-string {
|
||||||
color: #98c379;
|
color: var(--one-dark-hue-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-attr,
|
.hljs-attr,
|
||||||
@ -67,7 +73,7 @@ pre:has(.hljs) {
|
|||||||
.hljs-selector-attr,
|
.hljs-selector-attr,
|
||||||
.hljs-selector-pseudo,
|
.hljs-selector-pseudo,
|
||||||
.hljs-number {
|
.hljs-number {
|
||||||
color: #d19a66;
|
color: var(--one-dark-hue-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-symbol,
|
.hljs-symbol,
|
||||||
@ -76,13 +82,13 @@ pre:has(.hljs) {
|
|||||||
.hljs-meta,
|
.hljs-meta,
|
||||||
.hljs-selector-id,
|
.hljs-selector-id,
|
||||||
.hljs-title {
|
.hljs-title {
|
||||||
color: #61aeee;
|
color: var(--one-dark-hue-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-built_in,
|
.hljs-built_in,
|
||||||
.hljs-title.class_,
|
.hljs-title.class_,
|
||||||
.hljs-class .hljs-title {
|
.hljs-class .hljs-title {
|
||||||
color: #e6c07b;
|
color: var(--one-dark-hue-6-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-emphasis {
|
.hljs-emphasis {
|
||||||
|
|||||||
@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file authentik dark UI theme.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Defined to better identify the dark theme when debugging constructed stylesheets. */
|
||||||
|
.__AK_UI_DARK__ {
|
||||||
|
--__AK_UI_DARK__: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* #region Global */
|
/* #region Global */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@ -5,9 +14,6 @@
|
|||||||
--ak-global--Color--100: var(--ak-dark-foreground) !important;
|
--ak-global--Color--100: var(--ak-dark-foreground) !important;
|
||||||
--pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker);
|
--pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker);
|
||||||
--pf-global--BorderColor--100: var(--ak-dark-background-lighter) !important;
|
--pf-global--BorderColor--100: var(--ak-dark-background-lighter) !important;
|
||||||
--ak-mermaid-message-text: var(--ak-dark-foreground) !important;
|
|
||||||
--ak-mermaid-box-background-color: var(--ak-dark-background-lighter) !important;
|
|
||||||
--ak-table-stripe-background: var(--pf-global--BackgroundColor--dark-200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -256,8 +262,13 @@ input[type="date"]::-webkit-calendar-picker-indicator {
|
|||||||
color: var(--ak-dark-background-lighter);
|
color: var(--ak-dark-background-lighter);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-button.pf-m-plain:hover {
|
.pf-c-button.pf-m-plain {
|
||||||
color: var(--ak-dark-foreground);
|
--pf-c-button--m-plain--focus--Color: var(--pf-global--Color--200);
|
||||||
|
--pf-c-button--m-plain--hover--Color: var(--ak-dark-foreground);
|
||||||
|
|
||||||
|
&:focus:hover {
|
||||||
|
color: var(--pf-c-button--m-plain--hover--Color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-button.pf-m-control {
|
.pf-c-button.pf-m-control {
|
||||||
|
|||||||
@ -1,17 +1,27 @@
|
|||||||
/**
|
/**
|
||||||
* @file Stylesheet utilities.
|
* @file Stylesheet utilities.
|
||||||
*/
|
*/
|
||||||
import { CSSResult, CSSResultOrNative, ReactiveElement, css } from "lit";
|
import { CSSResultOrNative, ReactiveElement, adoptStyles as adoptStyleSheetsShim, css } from "lit";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elements containing adoptable stylesheets.
|
* Element-like objects containing adoptable stylesheets.
|
||||||
|
*
|
||||||
|
* Note that while these all possess the `adoptedStyleSheets` property,
|
||||||
|
* browser differences and polyfills may make them not actually adoptable.
|
||||||
|
*
|
||||||
|
* This type exists to normalize the different ways of accessing the property.
|
||||||
*/
|
*/
|
||||||
export type StyleSheetParent = Pick<DocumentOrShadowRoot, "adoptedStyleSheets">;
|
export type StyleRoot =
|
||||||
|
| Document
|
||||||
|
| ShadowRoot
|
||||||
|
| DocumentFragment
|
||||||
|
| HTMLElement
|
||||||
|
| DocumentOrShadowRoot;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type-predicate to determine if a given object has adoptable stylesheets.
|
* Type-predicate to determine if a given object has adoptable stylesheets.
|
||||||
*/
|
*/
|
||||||
export function isAdoptableStyleSheetParent(input: unknown): input is StyleSheetParent {
|
export function isStyleRoot(input: StyleRoot): input is ShadowRoot {
|
||||||
// Sanity check - Does the input have the right shape?
|
// Sanity check - Does the input have the right shape?
|
||||||
|
|
||||||
if (!input || typeof input !== "object") return false;
|
if (!input || typeof input !== "object") return false;
|
||||||
@ -25,39 +35,12 @@ export function isAdoptableStyleSheetParent(input: unknown): input is StyleSheet
|
|||||||
// All we care about is that it's shaped like an array.
|
// All we care about is that it's shaped like an array.
|
||||||
if (!("length" in input.adoptedStyleSheets)) return false;
|
if (!("length" in input.adoptedStyleSheets)) return false;
|
||||||
|
|
||||||
if (typeof input.adoptedStyleSheets.length !== "number") return false;
|
return typeof input.adoptedStyleSheets.length === "number";
|
||||||
|
|
||||||
// Finally is the array mutable?
|
|
||||||
return "push" in input.adoptedStyleSheets;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert that the given input can adopt stylesheets.
|
* Create a lazy-loaded `CSSResult` compatible with Lit's
|
||||||
*/
|
* element lifecycle.
|
||||||
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`
|
* @throw {@linkcode TypeError} if the input cannot be converted to a `CSSStyleSheet`
|
||||||
*
|
*
|
||||||
@ -68,8 +51,12 @@ export type StyleSheetInit = string | CSSResult | CSSStyleSheet;
|
|||||||
*
|
*
|
||||||
* It works well when Storybook is running in `dev`, but in `build` it fails.
|
* It works well when Storybook is running in `dev`, but in `build` it fails.
|
||||||
* Storied components will have to map their textual CSS imports.
|
* Storied components will have to map their textual CSS imports.
|
||||||
|
*
|
||||||
|
* @see {@linkcode createStyleSheetUnsafe} to create a `CSSStyleSheet` from the given input.
|
||||||
*/
|
*/
|
||||||
export function createStyleSheet(input: string): CSSResult {
|
export function createCSSResult(input: string | CSSModule | CSSResultOrNative): CSSResultOrNative {
|
||||||
|
if (typeof input !== "string") return input;
|
||||||
|
|
||||||
const inputTemplate = [input] as unknown as TemplateStringsArray;
|
const inputTemplate = [input] as unknown as TemplateStringsArray;
|
||||||
|
|
||||||
const result = css(inputTemplate, []);
|
const result = css(inputTemplate, []);
|
||||||
@ -78,74 +65,91 @@ export function createStyleSheet(input: string): CSSResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a source of CSS, create a `CSSStyleSheet`.
|
* Create a `CSSStyleSheet` from the given input, if it is not already a `CSSStyleSheet`.
|
||||||
*
|
*
|
||||||
* @see {@linkcode createStyleSheet}
|
* @throw {@linkcode TypeError} if the input cannot be converted to a `CSSStyleSheet`
|
||||||
|
*
|
||||||
|
* @see {@linkcode createCSSResult} for the lazy-loaded `CSSResult` normalization.
|
||||||
*/
|
*/
|
||||||
export function normalizeCSSSource(css: string): CSSStyleSheet;
|
export function createStyleSheetUnsafe(
|
||||||
export function normalizeCSSSource(styleSheet: CSSStyleSheet): CSSStyleSheet;
|
input: string | CSSModule | CSSResultOrNative,
|
||||||
export function normalizeCSSSource(cssResult: CSSResult): CSSResult;
|
): CSSStyleSheet {
|
||||||
export function normalizeCSSSource(input: StyleSheetInit): CSSResultOrNative;
|
const result = typeof input === "string" ? createCSSResult(input) : input;
|
||||||
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 instanceof CSSStyleSheet) return result;
|
||||||
|
|
||||||
if (!result.styleSheet) {
|
if (result.styleSheet) return result.styleSheet;
|
||||||
console.debug(
|
|
||||||
"authentik/common/stylesheets: CSSResult missing styleSheet, returning empty",
|
|
||||||
{ result, input },
|
|
||||||
);
|
|
||||||
|
|
||||||
throw new TypeError("Expected a CSSStyleSheet");
|
const styleSheet = new CSSStyleSheet();
|
||||||
}
|
|
||||||
|
|
||||||
return result.styleSheet;
|
styleSheet.replaceSync(result.cssText);
|
||||||
|
|
||||||
|
return styleSheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StyleSheetsAction =
|
||||||
|
| Iterable<CSSStyleSheet>
|
||||||
|
| ((currentStyleSheets: CSSStyleSheet[]) => Iterable<CSSStyleSheet>);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Append stylesheet(s) to the given roots.
|
* Set the adopted stylesheets of a given style parent.
|
||||||
*
|
*
|
||||||
* @see {@linkcode removeStyleSheet} to remove a stylesheet from a given roots.
|
* ```ts
|
||||||
|
* setAdoptedStyleSheets(document.body, (currentStyleSheets) => [
|
||||||
|
* ...currentStyleSheets,
|
||||||
|
* myStyleSheet,
|
||||||
|
* ]);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Replacing `adoptedStyleSheets` more than once in the same frame may result in
|
||||||
|
* the `currentStyleSheets` parameter being out of sync with the actual sheets.
|
||||||
|
*
|
||||||
|
* A style root's `adoptedStyleSheets` is a proxy object that only updates when
|
||||||
|
* DOM is repainted. We can't easily cache the previous entries since the style root
|
||||||
|
* may polyfilled via ShadyDOM.
|
||||||
|
*
|
||||||
|
* Short of using {@linkcode requestAnimationFrame} to sequence the adoption,
|
||||||
|
* and a visibility toggle to avoid a flash of styles between renders,
|
||||||
|
* we can't reliably cache the previous entries.
|
||||||
|
*
|
||||||
|
* In the meantime, we should try to apply all the sheets in a single frame.
|
||||||
*/
|
*/
|
||||||
export function appendStyleSheet(
|
export function setAdoptedStyleSheets(styleRoot: StyleRoot, styleSheets: StyleSheetsAction): void {
|
||||||
styleParent: StyleSheetParent,
|
let changed = false;
|
||||||
...insertions: CSSStyleSheet[]
|
|
||||||
): void {
|
|
||||||
insertions = Array.isArray(insertions) ? insertions : [insertions];
|
|
||||||
|
|
||||||
for (const styleSheetInsertion of insertions) {
|
const currentAdoptedStyleSheets = isStyleRoot(styleRoot)
|
||||||
if (styleParent.adoptedStyleSheets.includes(styleSheetInsertion)) return;
|
? [...styleRoot.adoptedStyleSheets]
|
||||||
|
: [];
|
||||||
|
|
||||||
styleParent.adoptedStyleSheets = [...styleParent.adoptedStyleSheets, styleSheetInsertion];
|
const result =
|
||||||
|
typeof styleSheets === "function" ? styleSheets(currentAdoptedStyleSheets) : styleSheets;
|
||||||
|
|
||||||
|
const nextAdoptedStyleSheets: CSSStyleSheet[] = [];
|
||||||
|
|
||||||
|
for (const [idx, styleSheet] of Array.from(result).entries()) {
|
||||||
|
const previousStyleSheet = currentAdoptedStyleSheets[idx];
|
||||||
|
|
||||||
|
changed ||= previousStyleSheet !== styleSheet;
|
||||||
|
|
||||||
|
if (nextAdoptedStyleSheets.includes(styleSheet)) continue;
|
||||||
|
|
||||||
|
nextAdoptedStyleSheets.push(styleSheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changed ||= nextAdoptedStyleSheets.length !== currentAdoptedStyleSheets.length;
|
||||||
|
|
||||||
|
if (!changed) return;
|
||||||
|
|
||||||
|
if (styleRoot === document) {
|
||||||
|
document.adoptedStyleSheets = nextAdoptedStyleSheets;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
adoptStyleSheetsShim(styleRoot as unknown as ShadowRoot, nextAdoptedStyleSheets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
//#region Debugging
|
||||||
* 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.
|
* Serialize a stylesheet to a string.
|
||||||
@ -159,8 +163,8 @@ export function serializeStyleSheet(stylesheet: CSSStyleSheet): string {
|
|||||||
/**
|
/**
|
||||||
* Inspect the adopted stylesheets of a given style parent, serializing them to strings.
|
* Inspect the adopted stylesheets of a given style parent, serializing them to strings.
|
||||||
*/
|
*/
|
||||||
export function inspectStyleSheets(styleParent: StyleSheetParent): string[] {
|
export function inspectStyleSheets(styleRoot: ShadowRoot): string[] {
|
||||||
return styleParent.adoptedStyleSheets.map((styleSheet) => serializeStyleSheet(styleSheet));
|
return styleRoot.adoptedStyleSheets.map((styleSheet) => serializeStyleSheet(styleSheet));
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InspectedStyleSheetEntry {
|
interface InspectedStyleSheetEntry {
|
||||||
@ -174,8 +178,11 @@ interface InspectedStyleSheetEntry {
|
|||||||
* Recursively inspect the adopted stylesheets of a given style parent, serializing them to strings.
|
* Recursively inspect the adopted stylesheets of a given style parent, serializing them to strings.
|
||||||
*/
|
*/
|
||||||
export function inspectStyleSheetTree(element: ReactiveElement): InspectedStyleSheetEntry {
|
export function inspectStyleSheetTree(element: ReactiveElement): InspectedStyleSheetEntry {
|
||||||
const styleParent = resolveStyleSheetParent(element.renderRoot);
|
if (!isStyleRoot(element.renderRoot)) {
|
||||||
const styles = inspectStyleSheets(styleParent);
|
throw new TypeError("Cannot inspect a render root that doesn't have adoptable stylesheets");
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = inspectStyleSheets(element.renderRoot);
|
||||||
const tagName = element.tagName.toLowerCase();
|
const tagName = element.tagName.toLowerCase();
|
||||||
|
|
||||||
const treewalker = document.createTreeWalker(element.renderRoot, NodeFilter.SHOW_ELEMENT, {
|
const treewalker = document.createTreeWalker(element.renderRoot, NodeFilter.SHOW_ELEMENT, {
|
||||||
@ -186,12 +193,14 @@ export function inspectStyleSheetTree(element: ReactiveElement): InspectedStyleS
|
|||||||
return NodeFilter.FILTER_SKIP;
|
return NodeFilter.FILTER_SKIP;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const children: InspectedStyleSheetEntry[] = [];
|
const children: InspectedStyleSheetEntry[] = [];
|
||||||
let currentNode: Node | null = treewalker.nextNode();
|
let currentNode: Node | null = treewalker.nextNode();
|
||||||
|
|
||||||
while (currentNode) {
|
while (currentNode) {
|
||||||
const childElement = currentNode as ReactiveElement;
|
const childElement = currentNode as ReactiveElement;
|
||||||
|
|
||||||
if (!isAdoptableStyleSheetParent(childElement.renderRoot)) {
|
if (!isStyleRoot(childElement.renderRoot)) {
|
||||||
currentNode = treewalker.nextNode();
|
currentNode = treewalker.nextNode();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -214,10 +223,12 @@ export function inspectStyleSheetTree(element: ReactiveElement): InspectedStyleS
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (import.meta.env.NODE_ENV === "development") {
|
||||||
Object.assign(window, {
|
Object.assign(window, {
|
||||||
inspectStyleSheetTree,
|
inspectStyleSheetTree,
|
||||||
serializeStyleSheet,
|
serializeStyleSheet,
|
||||||
inspectStyleSheets,
|
inspectStyleSheets,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|||||||
@ -1,10 +1,47 @@
|
|||||||
/**
|
/**
|
||||||
* @file Theme utilities.
|
* @file Theme utilities.
|
||||||
*/
|
*/
|
||||||
import { UIConfig } from "@goauthentik/common/ui/config";
|
import {
|
||||||
|
type StyleRoot,
|
||||||
|
createStyleSheetUnsafe,
|
||||||
|
setAdoptedStyleSheets,
|
||||||
|
} from "@goauthentik/web/common/stylesheets.js";
|
||||||
|
import { UIConfig } from "@goauthentik/web/common/ui/config.js";
|
||||||
|
|
||||||
|
import AKBase from "@goauthentik/web/common/styles/authentik.css";
|
||||||
|
import AKBaseDark from "@goauthentik/web/common/styles/theme-dark.css";
|
||||||
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
|
//#region Stylesheet Exports
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A global style sheet for the Patternfly base styles.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
*
|
||||||
|
* While a component *may* import its own instance of the PFBase style sheet,
|
||||||
|
* this instance ensures referential identity.
|
||||||
|
*/
|
||||||
|
export const $PFBase = createStyleSheetUnsafe(PFBase);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A global style sheet for the authentik base styles.
|
||||||
|
*
|
||||||
|
* @see {@linkcode $PFBase} for details.
|
||||||
|
*/
|
||||||
|
export const $AKBase = createStyleSheetUnsafe(AKBase);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A global style sheet for the authentik dark theme.
|
||||||
|
*
|
||||||
|
* @see {@linkcode $PFBase} for details.
|
||||||
|
*/
|
||||||
|
export const $AKBaseDark = createStyleSheetUnsafe(AKBaseDark);
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
//#region Scheme Types
|
//#region Scheme Types
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,15 +171,21 @@ export function resolveUITheme(
|
|||||||
* Effect listener invoked when the color scheme changes.
|
* Effect listener invoked when the color scheme changes.
|
||||||
*/
|
*/
|
||||||
export type UIThemeListener = (currentUITheme: ResolvedUITheme) => void;
|
export type UIThemeListener = (currentUITheme: ResolvedUITheme) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an effect that runs
|
* Effect destructor invoked when cleanup is required.
|
||||||
|
*/
|
||||||
|
export type UIThemeDestructor = () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an effect that runs UI theme changes.
|
||||||
*
|
*
|
||||||
* @returns A cleanup function that removes the effect.
|
* @returns A cleanup function that removes the effect.
|
||||||
*/
|
*/
|
||||||
export function createUIThemeEffect(
|
export function createUIThemeEffect(
|
||||||
effect: UIThemeListener,
|
effect: UIThemeListener,
|
||||||
listenerOptions?: AddEventListenerOptions,
|
listenerOptions?: AddEventListenerOptions,
|
||||||
): () => void {
|
): UIThemeDestructor {
|
||||||
const colorSchemeTarget = resolveUITheme();
|
const colorSchemeTarget = resolveUITheme();
|
||||||
const invertedColorSchemeTarget = UIThemeInversion[colorSchemeTarget];
|
const invertedColorSchemeTarget = UIThemeInversion[colorSchemeTarget];
|
||||||
|
|
||||||
@ -174,6 +217,8 @@ export function createUIThemeEffect(
|
|||||||
mediaQueryList.removeEventListener("change", changeListener);
|
mediaQueryList.removeEventListener("change", changeListener);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
listenerOptions?.signal?.addEventListener("abort", cleanup);
|
||||||
|
|
||||||
return cleanup;
|
return cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,16 +226,96 @@ export function createUIThemeEffect(
|
|||||||
|
|
||||||
//#region Theme Element
|
//#region Theme Element
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the current UI theme to the given style root.
|
||||||
|
*
|
||||||
|
* @param styleRoot The style root to apply the theme to.
|
||||||
|
* @param currentUITheme The current UI theme to apply.
|
||||||
|
* @param additionalStyleSheets Additional style sheets to apply, in addition to the theme's base sheets.
|
||||||
|
* @category CSS
|
||||||
|
*
|
||||||
|
* @see {@linkcode setAdoptedStyleSheets} for caveats.
|
||||||
|
*/
|
||||||
|
export function applyUITheme(
|
||||||
|
styleRoot: StyleRoot,
|
||||||
|
currentUITheme: ResolvedUITheme = resolveUITheme(),
|
||||||
|
...additionalStyleSheets: Array<CSSStyleSheet | undefined | null>
|
||||||
|
): void {
|
||||||
|
setAdoptedStyleSheets(styleRoot, (currentStyleSheets) => {
|
||||||
|
const appendedSheets = additionalStyleSheets.filter(Boolean) as CSSStyleSheet[];
|
||||||
|
|
||||||
|
if (currentUITheme === UiThemeEnum.Dark) {
|
||||||
|
return [...currentStyleSheets, $AKBaseDark, ...appendedSheets];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...currentStyleSheets.filter((styleSheet) => styleSheet !== $AKBaseDark),
|
||||||
|
...appendedSheets,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the given theme to the document, i.e. the `<html>` element.
|
||||||
|
*
|
||||||
|
* @param hint The color scheme hint to use.
|
||||||
|
*/
|
||||||
|
export function applyDocumentTheme(hint: CSSColorSchemeValue | UIThemeHint = "auto"): void {
|
||||||
|
const preferredColorScheme = formatColorScheme(hint);
|
||||||
|
|
||||||
|
const applyStyleSheets: UIThemeListener = (currentUITheme) => {
|
||||||
|
console.debug(`authentik/theme (document): switching to ${currentUITheme} theme`);
|
||||||
|
|
||||||
|
setAdoptedStyleSheets(document, (currentStyleSheets) => {
|
||||||
|
if (currentUITheme === "dark") {
|
||||||
|
return [...currentStyleSheets, $PFBase, $AKBase, $AKBaseDark];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...currentStyleSheets.filter((styleSheet) => styleSheet !== $AKBaseDark),
|
||||||
|
$PFBase,
|
||||||
|
$AKBase,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
document.documentElement.dataset.theme = currentUITheme;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (preferredColorScheme === "auto") {
|
||||||
|
createUIThemeEffect(applyStyleSheets);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyStyleSheets(preferredColorScheme);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An element that can be themed.
|
* An element that can be themed.
|
||||||
*/
|
*/
|
||||||
export interface ThemedElement extends HTMLElement {
|
export interface ThemedElement extends HTMLElement {
|
||||||
brand?: CurrentBrand;
|
/**
|
||||||
uiConfig?: UIConfig;
|
* The brand information for the current theme.
|
||||||
config?: Config;
|
*/
|
||||||
|
readonly brand?: CurrentBrand;
|
||||||
|
/**
|
||||||
|
* The UI configuration for the current theme,
|
||||||
|
* typically injected through a Lit Mixin.
|
||||||
|
*
|
||||||
|
* @see {@linkcode UIConfig} for details.
|
||||||
|
*/
|
||||||
|
readonly uiConfig?: UIConfig;
|
||||||
|
/**
|
||||||
|
* An authentik configuration initially provided by the server.
|
||||||
|
*/
|
||||||
|
readonly config?: Config;
|
||||||
activeTheme: ResolvedUITheme;
|
activeTheme: ResolvedUITheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the root interface element of the page.
|
||||||
|
*
|
||||||
|
* @todo Can this be handled with a Lit Mixin?
|
||||||
|
*/
|
||||||
export function rootInterface<T extends ThemedElement = ThemedElement>(): T | null {
|
export function rootInterface<T extends ThemedElement = ThemedElement>(): T | null {
|
||||||
const element = document.body.querySelector<T>("[data-ak-interface-root]");
|
const element = document.body.querySelector<T>("[data-ak-interface-root]");
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users.js";
|
||||||
import { isUserRoute } from "@goauthentik/elements/router/utils";
|
import { isUserRoute } from "@goauthentik/elements/router/utils.js";
|
||||||
|
|
||||||
import { UiThemeEnum, UserSelf } from "@goauthentik/api";
|
import { UiThemeEnum, UserSelf } from "@goauthentik/api";
|
||||||
import { CurrentBrand } from "@goauthentik/api";
|
import { CurrentBrand } from "@goauthentik/api";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
|
||||||
import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants";
|
import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants.js";
|
||||||
import { isResponseErrorLike } from "@goauthentik/common/errors/network";
|
import { isResponseErrorLike } from "@goauthentik/common/errors/network.js";
|
||||||
|
|
||||||
import { CoreApi, SessionUser } from "@goauthentik/api";
|
import { CoreApi, SessionUser } from "@goauthentik/api";
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user