Compare commits

..

17 Commits

Author SHA1 Message Date
1dc4fbbb2b Attempting coercion to ESM with Vite & Wdio 2024-08-09 10:55:44 -07:00
3332de267d Testing needs to be able to import from dependent packages. 2024-08-09 10:53:20 -07:00
ab366d0ec2 Testing needs to be able to import from dependent packages. 2024-08-09 10:50:51 -07:00
ac162582aa Modernization continues. 2024-08-09 10:43:28 -07:00
7d82e029d5 wdio does not need the storybook cssimport hack. 2024-08-09 10:37:17 -07:00
9b40ecb023 web: restricted all eslints to local checks only and blocked them from cache analysis 2024-08-09 09:56:55 -07:00
0cc0fdaae3 Not ready for primetime. 2024-08-09 09:35:46 -07:00
b55b168718 web: move common into its own package.
```
$ mkdir ./packages/common
$ git mv ./src/common ./packages/common/src
```

... and then added all of the boilerplate needed to drive with Wireit, build with ESlint, typecheck
with TSC, and then spell check documentation and comments, security checks of package.json and
package-lock.json, format.

... and _then_ fix all of the minor, nitpicky things ESLint 9 found in the package.

... and _then_ wire the whole thing into our build so that we can find it as a package, removing
it as an alias from the base package definition and turning it into a workspace.  Although it is
a workspace package, it's currently configured to build completely independently.

It could be published as an independent NPM package, although I don't recommended that at this time.

I've wanted to break the UI up into smaller, more digestible chunks for awhile, but was always
reluctant to, since I didn't want to mess with other teams' mental models of the code layout.
@Beryju, seeing the success of the Simple Flow Executor as an independent package, thought it might
be worthwhile to see what effort it took to break the graph of our independent apps (User, Flow, and
Admin) and their dependencies (Common <- Elements <- Components, Common <- Locales) into packages.

Turns out, it's not too bad.  It's going to be fiddly for awhile until things settle down, but
overall the experiment has been a success.

The `tsconfig.json` doesn't refer to the base because we want this to build independently; tooling
will be needed to ensure all of our `tsconfig` files in the future will be consistent across all
packages.

- We can use the ESLint boilerplate as-is.
- We have to run TSC as a separate (but fortunately parallel) build step, as client code will need
  the built types. Final builds will be fractionally slower, but Wireit can detect when a monorepo
  package is unchanged and can skip rebuilding `common` if it's not needed, so the development loop
  will be faster.
- The ESBuild boilerplate is different for libraries with UI, libraries without UI (like this one),
  and apps, and we'll have to have three different routines for them. Once we are building
  independent _apps_, getting them into the `dist` folder will be an interesting challenge; we may
  end up with two different builds, one to bundle it in *in the app*, and another to bundle it *for
  Django*. That's mostly an issue of targeting and integration, and shouldn't take too much time.
- Spelling, formatting, and package checking aren't affected.
- `Locales` is our biggest challenge, as usual. I have found only [one article on it
  anywhere](https://medium.com/tech-at-zet/streamlining-localization-in-a-monorepo-using-i18n-js-e7c521ff69d4),
  and it recommends creating a single package in which to keep all of the localizations and the
  localization machinery. That seems like a sound approach, but we haven't (yet) gotten there.

`common` is a bit of a junk drawer: there are global utilities in there, there are app-specific
helpers, there are plug-in specific helpers, and so on. Figuring out exactly what does what and
making more specific packages may be in our future.
2024-08-09 08:38:30 -07:00
c46dc8f290 Not sure how that happened. 2024-08-08 16:10:07 -07:00
e48da3520c Fix type checking issues at the TSC level. 2024-08-08 16:03:37 -07:00
1ec4652c60 Fix dependent types needed before attempting typecheck. 2024-08-08 15:51:09 -07:00
e375646705 Made linting the subpackages a requirement of success. 2024-08-08 15:47:54 -07:00
b84652d9d3 Fix eslint so it only lints the local package. Other packages have their own responsibilities. 2024-08-08 15:44:43 -07:00
74b8da28ca Added common:build" to the list of dependencies for building, which is what you want, right? 2024-08-08 15:32:11 -07:00
9084c7c6b4 web: all the basic commands are working: build, build types, lint source, lint lockfile, lint packagefile, lint types, lint spelling, format source, format package. 2024-08-08 15:08:05 -07:00
7a0b227b46 Interim commit 2024-08-08 14:25:14 -07:00
cc9128fd46 Move begun; sfe cleanup completed. 2024-08-08 11:14:50 -07:00
369 changed files with 7786 additions and 4670 deletions

View File

@ -94,7 +94,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 5: Python dependencies
FROM ghcr.io/goauthentik/fips-python:3.12.5-slim-bookworm-fips-full AS python-deps
FROM ghcr.io/goauthentik/fips-python:3.12.3-slim-bookworm-fips-full AS python-deps
WORKDIR /ak-root/poetry
@ -121,7 +121,7 @@ RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \
pip install --force-reinstall /wheels/*"
# Stage 6: Run
FROM ghcr.io/goauthentik/fips-python:3.12.5-slim-bookworm-fips-full AS final-image
FROM ghcr.io/goauthentik/fips-python:3.12.3-slim-bookworm-fips-full AS final-image
ARG GIT_BUILD_HASH
ARG VERSION

View File

@ -73,7 +73,7 @@ class SystemInfoSerializer(PassiveSerializer):
"authentik_version": get_full_version(),
"environment": get_env(),
"openssl_fips_enabled": (
backend._fips_enabled if LicenseKey.get_total().status().is_valid else None
backend._fips_enabled if LicenseKey.get_total().is_valid() else None
),
"openssl_version": OPENSSL_VERSION,
"platform": platform.platform(),

View File

@ -171,7 +171,7 @@ class Importer:
def default_context(self):
"""Default context"""
return {
"goauthentik.io/enterprise/licensed": LicenseKey.get_total().status().is_valid,
"goauthentik.io/enterprise/licensed": LicenseKey.get_total().is_valid(),
"goauthentik.io/rbac/models": rbac_models(),
}

View File

@ -19,7 +19,7 @@ from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
from authentik.core.models import User, UserTypes
from authentik.enterprise.license import LicenseKey, LicenseSummarySerializer
from authentik.enterprise.models import License, LicenseUsageStatus
from authentik.enterprise.models import License
from authentik.rbac.decorators import permission_required
from authentik.tenants.utils import get_unique_identifier
@ -30,7 +30,7 @@ class EnterpriseRequiredMixin:
def validate(self, attrs: dict) -> dict:
"""Check that a valid license exists"""
if LicenseKey.cached_summary().status != LicenseUsageStatus.UNLICENSED:
if not LicenseKey.cached_summary().has_license:
raise ValidationError(_("Enterprise is required to create/update this object."))
return super().validate(attrs)
@ -128,7 +128,7 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
forecast_for_months = 12
response = LicenseForecastSerializer(
data={
"internal_users": LicenseKey.get_internal_user_count(),
"internal_users": LicenseKey.get_default_user_count(),
"external_users": LicenseKey.get_external_user_count(),
"forecasted_internal_users": (internal_in_last_month * forecast_for_months),
"forecasted_external_users": (external_in_last_month * forecast_for_months),

View File

@ -25,4 +25,4 @@ class AuthentikEnterpriseConfig(EnterpriseConfig):
"""Actual enterprise check, cached"""
from authentik.enterprise.license import LicenseKey
return LicenseKey.cached_summary().status
return LicenseKey.cached_summary().valid

View File

@ -3,36 +3,24 @@
from base64 import b64decode
from binascii import Error
from dataclasses import asdict, dataclass, field
from datetime import UTC, datetime, timedelta
from datetime import datetime, timedelta
from enum import Enum
from functools import lru_cache
from time import mktime
from cryptography.exceptions import InvalidSignature
from cryptography.x509 import Certificate, load_der_x509_certificate, load_pem_x509_certificate
from dacite import DaciteError, from_dict
from dacite import from_dict
from django.core.cache import cache
from django.db.models.query import QuerySet
from django.utils.timezone import now
from jwt import PyJWTError, decode, get_unverified_header
from rest_framework.exceptions import ValidationError
from rest_framework.fields import (
ChoiceField,
DateTimeField,
IntegerField,
)
from rest_framework.fields import BooleanField, DateTimeField, IntegerField
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import User, UserTypes
from authentik.enterprise.models import (
THRESHOLD_READ_ONLY_WEEKS,
THRESHOLD_WARNING_ADMIN_WEEKS,
THRESHOLD_WARNING_EXPIRY_WEEKS,
THRESHOLD_WARNING_USER_WEEKS,
License,
LicenseUsage,
LicenseUsageStatus,
)
from authentik.enterprise.models import License, LicenseUsage
from authentik.tenants.utils import get_unique_identifier
CACHE_KEY_ENTERPRISE_LICENSE = "goauthentik.io/enterprise/license"
@ -54,8 +42,6 @@ def get_license_aud() -> str:
class LicenseFlags(Enum):
"""License flags"""
TRIAL = "trial"
@dataclass
class LicenseSummary:
@ -63,8 +49,12 @@ class LicenseSummary:
internal_users: int
external_users: int
status: LicenseUsageStatus
valid: bool
show_admin_warning: bool
show_user_warning: bool
read_only: bool
latest_valid: datetime
has_license: bool
class LicenseSummarySerializer(PassiveSerializer):
@ -72,8 +62,12 @@ class LicenseSummarySerializer(PassiveSerializer):
internal_users = IntegerField(required=True)
external_users = IntegerField(required=True)
status = ChoiceField(choices=LicenseUsageStatus.choices)
valid = BooleanField()
show_admin_warning = BooleanField()
show_user_warning = BooleanField()
read_only = BooleanField()
latest_valid = DateTimeField()
has_license = BooleanField()
@dataclass
@ -89,7 +83,7 @@ class LicenseKey:
flags: list[LicenseFlags] = field(default_factory=list)
@staticmethod
def validate(jwt: str, check_expiry=True) -> "LicenseKey":
def validate(jwt: str) -> "LicenseKey":
"""Validate the license from a given JWT"""
try:
headers = get_unverified_header(jwt)
@ -113,7 +107,6 @@ class LicenseKey:
our_cert.public_key(),
algorithms=["ES512"],
audience=get_license_aud(),
options={"verify_exp": check_expiry},
),
)
except PyJWTError:
@ -123,8 +116,9 @@ class LicenseKey:
@staticmethod
def get_total() -> "LicenseKey":
"""Get a summarized version of all (not expired) licenses"""
active_licenses = License.objects.filter(expiry__gte=now())
total = LicenseKey(get_license_aud(), 0, "Summarized license", 0, 0)
for lic in License.objects.all():
for lic in active_licenses:
total.internal_users += lic.internal_users
total.external_users += lic.external_users
exp_ts = int(mktime(lic.expiry.timetuple()))
@ -141,7 +135,7 @@ class LicenseKey:
return User.objects.all().exclude_anonymous().exclude(is_active=False)
@staticmethod
def get_internal_user_count():
def get_default_user_count():
"""Get current default user count"""
return LicenseKey.base_user_qs().filter(type=UserTypes.INTERNAL).count()
@ -150,72 +144,59 @@ class LicenseKey:
"""Get current external user count"""
return LicenseKey.base_user_qs().filter(type=UserTypes.EXTERNAL).count()
def _last_valid_date(self):
last_valid_date = (
LicenseUsage.objects.order_by("-record_date")
.filter(status=LicenseUsageStatus.VALID)
.first()
)
if not last_valid_date:
return datetime.fromtimestamp(0, UTC)
return last_valid_date.record_date
def is_valid(self) -> bool:
"""Check if the given license body covers all users
def status(self) -> LicenseUsageStatus:
"""Check if the given license body covers all users, and is valid."""
last_valid = self._last_valid_date()
if self.exp == 0 and not License.objects.exists():
return LicenseUsageStatus.UNLICENSED
_now = now()
# Check limit-exceeded based status
internal_users = self.get_internal_user_count()
external_users = self.get_external_user_count()
if internal_users > self.internal_users or external_users > self.external_users:
if last_valid < _now - timedelta(weeks=THRESHOLD_READ_ONLY_WEEKS):
return LicenseUsageStatus.READ_ONLY
if last_valid < _now - timedelta(weeks=THRESHOLD_WARNING_USER_WEEKS):
return LicenseUsageStatus.LIMIT_EXCEEDED_USER
if last_valid < _now - timedelta(weeks=THRESHOLD_WARNING_ADMIN_WEEKS):
return LicenseUsageStatus.LIMIT_EXCEEDED_ADMIN
# Check expiry based status
if datetime.fromtimestamp(self.exp, UTC) < _now:
if datetime.fromtimestamp(self.exp, UTC) < _now - timedelta(
weeks=THRESHOLD_READ_ONLY_WEEKS
):
return LicenseUsageStatus.READ_ONLY
return LicenseUsageStatus.EXPIRED
# Expiry warning
if datetime.fromtimestamp(self.exp, UTC) <= _now + timedelta(
weeks=THRESHOLD_WARNING_EXPIRY_WEEKS
):
return LicenseUsageStatus.EXPIRY_SOON
return LicenseUsageStatus.VALID
Only checks the current count, no historical data is checked"""
default_users = self.get_default_user_count()
if default_users > self.internal_users:
return False
active_users = self.get_external_user_count()
if active_users > self.external_users:
return False
return True
def record_usage(self):
"""Capture the current validity status and metrics and save them"""
threshold = now() - timedelta(hours=8)
usage = (
LicenseUsage.objects.order_by("-record_date").filter(record_date__gte=threshold).first()
)
if not usage:
usage = LicenseUsage.objects.create(
internal_user_count=self.get_internal_user_count(),
if not LicenseUsage.objects.filter(record_date__gte=threshold).exists():
LicenseUsage.objects.create(
user_count=self.get_default_user_count(),
external_user_count=self.get_external_user_count(),
status=self.status(),
within_limits=self.is_valid(),
)
summary = asdict(self.summary())
# Also cache the latest summary for the middleware
cache.set(CACHE_KEY_ENTERPRISE_LICENSE, summary, timeout=CACHE_EXPIRY_ENTERPRISE_LICENSE)
return usage
return summary
@staticmethod
def last_valid_date() -> datetime:
"""Get the last date the license was valid"""
usage: LicenseUsage = (
LicenseUsage.filter_not_expired(within_limits=True).order_by("-record_date").first()
)
if not usage:
return now()
return usage.record_date
def summary(self) -> LicenseSummary:
"""Summary of license status"""
status = self.status()
has_license = License.objects.all().count() > 0
last_valid = LicenseKey.last_valid_date()
show_admin_warning = last_valid < now() - timedelta(weeks=2)
show_user_warning = last_valid < now() - timedelta(weeks=4)
read_only = last_valid < now() - timedelta(weeks=6)
latest_valid = datetime.fromtimestamp(self.exp)
return LicenseSummary(
show_admin_warning=show_admin_warning and has_license,
show_user_warning=show_user_warning and has_license,
read_only=read_only and has_license,
latest_valid=latest_valid,
internal_users=self.internal_users,
external_users=self.external_users,
status=status,
valid=self.is_valid(),
has_license=has_license,
)
@staticmethod
@ -224,8 +205,4 @@ class LicenseKey:
summary = cache.get(CACHE_KEY_ENTERPRISE_LICENSE)
if not summary:
return LicenseKey.get_total().summary()
try:
return from_dict(LicenseSummary, summary)
except DaciteError:
cache.delete(CACHE_KEY_ENTERPRISE_LICENSE)
return LicenseKey.get_total().summary()
return from_dict(LicenseSummary, summary)

View File

@ -8,7 +8,6 @@ from structlog.stdlib import BoundLogger, get_logger
from authentik.enterprise.api import LicenseViewSet
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import LicenseUsageStatus
from authentik.flows.views.executor import FlowExecutorView
from authentik.lib.utils.reflection import class_to_path
@ -44,7 +43,7 @@ class EnterpriseMiddleware:
cached_status = LicenseKey.cached_summary()
if not cached_status:
return True
if cached_status.status == LicenseUsageStatus.READ_ONLY:
if cached_status.read_only:
return False
return True
@ -54,10 +53,10 @@ class EnterpriseMiddleware:
if request.method.lower() in ["get", "head", "options", "trace"]:
return True
# Always allow requests to manage licenses
if request.resolver_match._func_path == class_to_path(LicenseViewSet):
if class_to_path(request.resolver_match.func) == class_to_path(LicenseViewSet):
return True
# Flow executor is mounted as an API path but explicitly allowed
if request.resolver_match._func_path == class_to_path(FlowExecutorView):
if class_to_path(request.resolver_match.func) == class_to_path(FlowExecutorView):
return True
# Only apply these restrictions to the API
if "authentik_api" not in request.resolver_match.app_names:

View File

@ -1,68 +0,0 @@
# Generated by Django 5.0.8 on 2024-08-08 14:15
from django.db import migrations, models
from django.apps.registry import Apps
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def migrate_license_usage(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
LicenseUsage = apps.get_model("authentik_enterprise", "licenseusage")
db_alias = schema_editor.connection.alias
for usage in LicenseUsage.objects.using(db_alias).all():
usage.status = "valid" if usage.within_limits else "limit_exceeded_admin"
usage.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_enterprise", "0002_rename_users_license_internal_users_and_more"),
]
operations = [
migrations.AddField(
model_name="licenseusage",
name="status",
field=models.TextField(
choices=[
("unlicensed", "Unlicensed"),
("valid", "Valid"),
("expired", "Expired"),
("expiry_soon", "Expiry Soon"),
("limit_exceeded_admin", "Limit Exceeded Admin"),
("limit_exceeded_user", "Limit Exceeded User"),
("read_only", "Read Only"),
],
default=None,
null=True,
),
preserve_default=False,
),
migrations.RunPython(migrate_license_usage),
migrations.RemoveField(
model_name="licenseusage",
name="within_limits",
),
migrations.AlterField(
model_name="licenseusage",
name="status",
field=models.TextField(
choices=[
("unlicensed", "Unlicensed"),
("valid", "Valid"),
("expired", "Expired"),
("expiry_soon", "Expiry Soon"),
("limit_exceeded_admin", "Limit Exceeded Admin"),
("limit_exceeded_user", "Limit Exceeded User"),
("read_only", "Read Only"),
],
),
preserve_default=False,
),
migrations.RenameField(
model_name="licenseusage",
old_name="user_count",
new_name="internal_user_count",
),
]

View File

@ -17,17 +17,6 @@ if TYPE_CHECKING:
from authentik.enterprise.license import LicenseKey
def usage_expiry():
"""Keep license usage records for 3 months"""
return now() + timedelta(days=30 * 3)
THRESHOLD_WARNING_ADMIN_WEEKS = 2
THRESHOLD_WARNING_USER_WEEKS = 4
THRESHOLD_WARNING_EXPIRY_WEEKS = 2
THRESHOLD_READ_ONLY_WEEKS = 6
class License(SerializerModel):
"""An authentik enterprise license"""
@ -50,7 +39,7 @@ class License(SerializerModel):
"""Get parsed license status"""
from authentik.enterprise.license import LicenseKey
return LicenseKey.validate(self.key, check_expiry=False)
return LicenseKey.validate(self.key)
class Meta:
indexes = (HashIndex(fields=("key",)),)
@ -58,23 +47,9 @@ class License(SerializerModel):
verbose_name_plural = _("Licenses")
class LicenseUsageStatus(models.TextChoices):
"""License states an instance/tenant can be in"""
UNLICENSED = "unlicensed"
VALID = "valid"
EXPIRED = "expired"
EXPIRY_SOON = "expiry_soon"
# User limit exceeded, 2 week threshold, show message in admin interface
LIMIT_EXCEEDED_ADMIN = "limit_exceeded_admin"
# User limit exceeded, 4 week threshold, show message in user interface
LIMIT_EXCEEDED_USER = "limit_exceeded_user"
READ_ONLY = "read_only"
@property
def is_valid(self) -> bool:
"""Quickly check if a license is valid"""
return self in [LicenseUsageStatus.VALID, LicenseUsageStatus.EXPIRY_SOON]
def usage_expiry():
"""Keep license usage records for 3 months"""
return now() + timedelta(days=30 * 3)
class LicenseUsage(ExpiringModel):
@ -84,9 +59,9 @@ class LicenseUsage(ExpiringModel):
usage_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
internal_user_count = models.BigIntegerField()
user_count = models.BigIntegerField()
external_user_count = models.BigIntegerField()
status = models.TextField(choices=LicenseUsageStatus.choices)
within_limits = models.BooleanField()
record_date = models.DateTimeField(auto_now_add=True)

View File

@ -13,7 +13,7 @@ class EnterprisePolicyAccessView(PolicyAccessView):
def check_license(self):
"""Check license"""
if not LicenseKey.get_total().status().is_valid:
if not LicenseKey.get_total().is_valid():
return PolicyResult(False, _("Enterprise required to access this feature."))
if self.request.user.type != UserTypes.INTERNAL:
return PolicyResult(False, _("Feature only accessible for internal users."))

View File

@ -9,26 +9,10 @@ from django.utils.timezone import now
from rest_framework.exceptions import ValidationError
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import (
THRESHOLD_READ_ONLY_WEEKS,
THRESHOLD_WARNING_ADMIN_WEEKS,
THRESHOLD_WARNING_USER_WEEKS,
License,
LicenseUsage,
LicenseUsageStatus,
)
from authentik.enterprise.models import License
from authentik.lib.generators import generate_id
# Valid license expiry
expiry_valid = int(mktime((now() + timedelta(days=3000)).timetuple()))
# Valid license expiry, expires soon
expiry_soon = int(mktime((now() + timedelta(hours=10)).timetuple()))
# Invalid license expiry, recently expired
expiry_expired = int(mktime((now() - timedelta(hours=10)).timetuple()))
# Invalid license expiry, expired longer ago
expiry_expired_read_only = int(
mktime((now() - timedelta(weeks=THRESHOLD_READ_ONLY_WEEKS + 1)).timetuple())
)
_exp = int(mktime((now() + timedelta(days=3000)).timetuple()))
class TestEnterpriseLicense(TestCase):
@ -39,7 +23,7 @@ class TestEnterpriseLicense(TestCase):
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
exp=_exp,
name=generate_id(),
internal_users=100,
external_users=100,
@ -49,7 +33,7 @@ class TestEnterpriseLicense(TestCase):
def test_valid(self):
"""Check license verification"""
lic = License.objects.create(key=generate_id())
self.assertTrue(lic.status.status().is_valid)
self.assertTrue(lic.status.is_valid())
self.assertEqual(lic.internal_users, 100)
def test_invalid(self):
@ -62,7 +46,7 @@ class TestEnterpriseLicense(TestCase):
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
exp=_exp,
name=generate_id(),
internal_users=100,
external_users=100,
@ -72,186 +56,11 @@ class TestEnterpriseLicense(TestCase):
def test_valid_multiple(self):
"""Check license verification"""
lic = License.objects.create(key=generate_id())
self.assertTrue(lic.status.status().is_valid)
self.assertTrue(lic.status.is_valid())
lic2 = License.objects.create(key=generate_id())
self.assertTrue(lic2.status.status().is_valid)
self.assertTrue(lic2.status.is_valid())
total = LicenseKey.get_total()
self.assertEqual(total.internal_users, 200)
self.assertEqual(total.external_users, 200)
self.assertEqual(total.exp, expiry_valid)
self.assertTrue(total.status().is_valid)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_internal_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_external_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.record_usage",
MagicMock(),
)
def test_limit_exceeded_read_only(self):
"""Check license verification"""
License.objects.create(key=generate_id())
usage = LicenseUsage.objects.create(
internal_user_count=100,
external_user_count=100,
status=LicenseUsageStatus.VALID,
)
usage.record_date = now() - timedelta(weeks=THRESHOLD_READ_ONLY_WEEKS + 1)
usage.save(update_fields=["record_date"])
self.assertEqual(LicenseKey.get_total().summary().status, LicenseUsageStatus.READ_ONLY)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_internal_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_external_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.record_usage",
MagicMock(),
)
def test_limit_exceeded_user_warning(self):
"""Check license verification"""
License.objects.create(key=generate_id())
usage = LicenseUsage.objects.create(
internal_user_count=100,
external_user_count=100,
status=LicenseUsageStatus.VALID,
)
usage.record_date = now() - timedelta(weeks=THRESHOLD_WARNING_USER_WEEKS + 1)
usage.save(update_fields=["record_date"])
self.assertEqual(
LicenseKey.get_total().summary().status, LicenseUsageStatus.LIMIT_EXCEEDED_USER
)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_internal_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_external_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.record_usage",
MagicMock(),
)
def test_limit_exceeded_admin_warning(self):
"""Check license verification"""
License.objects.create(key=generate_id())
usage = LicenseUsage.objects.create(
internal_user_count=100,
external_user_count=100,
status=LicenseUsageStatus.VALID,
)
usage.record_date = now() - timedelta(weeks=THRESHOLD_WARNING_ADMIN_WEEKS + 1)
usage.save(update_fields=["record_date"])
self.assertEqual(
LicenseKey.get_total().summary().status, LicenseUsageStatus.LIMIT_EXCEEDED_ADMIN
)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_expired_read_only,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@patch(
"authentik.enterprise.license.LicenseKey.record_usage",
MagicMock(),
)
def test_expiry_read_only(self):
"""Check license verification"""
License.objects.create(key=generate_id())
self.assertEqual(LicenseKey.get_total().summary().status, LicenseUsageStatus.READ_ONLY)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_expired,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@patch(
"authentik.enterprise.license.LicenseKey.record_usage",
MagicMock(),
)
def test_expiry_expired(self):
"""Check license verification"""
License.objects.create(key=generate_id())
self.assertEqual(LicenseKey.get_total().summary().status, LicenseUsageStatus.EXPIRED)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_soon,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@patch(
"authentik.enterprise.license.LicenseKey.record_usage",
MagicMock(),
)
def test_expiry_soon(self):
"""Check license verification"""
License.objects.create(key=generate_id())
self.assertEqual(LicenseKey.get_total().summary().status, LicenseUsageStatus.EXPIRY_SOON)
self.assertEqual(total.exp, _exp)
self.assertTrue(total.is_valid())

View File

@ -1,217 +0,0 @@
"""read only tests"""
from datetime import timedelta
from unittest.mock import MagicMock, patch
from django.urls import reverse
from django.utils.timezone import now
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_user
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import (
THRESHOLD_READ_ONLY_WEEKS,
License,
LicenseUsage,
LicenseUsageStatus,
)
from authentik.enterprise.tests.test_license import expiry_valid
from authentik.flows.models import (
FlowDesignation,
FlowStageBinding,
)
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_id
from authentik.stages.identification.models import IdentificationStage, UserFields
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.models import PasswordStage
from authentik.stages.user_login.models import UserLoginStage
class TestReadOnly(FlowTestCase):
"""Test read_only"""
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_internal_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_external_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.record_usage",
MagicMock(),
)
def test_login(self):
"""Test flow, ensure login is still possible with read only mode"""
License.objects.create(key=generate_id())
usage = LicenseUsage.objects.create(
internal_user_count=100,
external_user_count=100,
status=LicenseUsageStatus.VALID,
)
usage.record_date = now() - timedelta(weeks=THRESHOLD_READ_ONLY_WEEKS + 1)
usage.save(update_fields=["record_date"])
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
ident_stage = IdentificationStage.objects.create(
name=generate_id(),
user_fields=[UserFields.E_MAIL],
pretend_user_exists=False,
)
FlowStageBinding.objects.create(
target=flow,
stage=ident_stage,
order=0,
)
password_stage = PasswordStage.objects.create(
name=generate_id(), backends=[BACKEND_INBUILT]
)
FlowStageBinding.objects.create(
target=flow,
stage=password_stage,
order=1,
)
login_stage = UserLoginStage.objects.create(
name=generate_id(),
)
FlowStageBinding.objects.create(
target=flow,
stage=login_stage,
order=2,
)
user = create_test_user()
exec_url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
response = self.client.get(exec_url)
self.assertStageResponse(
response,
flow,
component="ak-stage-identification",
password_fields=False,
primary_action="Log in",
sources=[],
show_source_labels=False,
user_fields=[UserFields.E_MAIL],
)
response = self.client.post(exec_url, {"uid_field": user.email}, follow=True)
self.assertStageResponse(response, flow, component="ak-stage-password")
response = self.client.post(exec_url, {"password": user.username}, follow=True)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_internal_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_external_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.record_usage",
MagicMock(),
)
def test_manage_licenses(self):
"""Test that managing licenses is still possible"""
license = License.objects.create(key=generate_id())
usage = LicenseUsage.objects.create(
internal_user_count=100,
external_user_count=100,
status=LicenseUsageStatus.VALID,
)
usage.record_date = now() - timedelta(weeks=THRESHOLD_READ_ONLY_WEEKS + 1)
usage.save(update_fields=["record_date"])
admin = create_test_admin_user()
self.client.force_login(admin)
# Reading is always allowed
response = self.client.get(reverse("authentik_api:license-list"))
self.assertEqual(response.status_code, 200)
# Writing should also be allowed
response = self.client.patch(
reverse("authentik_api:license-detail", kwargs={"pk": license.pk})
)
self.assertEqual(response.status_code, 200)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_internal_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_external_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.record_usage",
MagicMock(),
)
def test_manage_flows(self):
"""Test flow"""
License.objects.create(key=generate_id())
usage = LicenseUsage.objects.create(
internal_user_count=100,
external_user_count=100,
status=LicenseUsageStatus.VALID,
)
usage.record_date = now() - timedelta(weeks=THRESHOLD_READ_ONLY_WEEKS + 1)
usage.save(update_fields=["record_date"])
admin = create_test_admin_user()
self.client.force_login(admin)
# Read only is still allowed
response = self.client.get(reverse("authentik_api:flow-list"))
self.assertEqual(response.status_code, 200)
flow = create_test_flow()
# Writing is not
response = self.client.patch(
reverse("authentik_api:flow-detail", kwargs={"slug": flow.slug})
)
self.assertJSONEqual(
response.content,
{"detail": "Request denied due to expired/invalid license.", "code": "denied_license"},
)
self.assertEqual(response.status_code, 400)

View File

@ -140,7 +140,7 @@ class OutpostHealthSerializer(PassiveSerializer):
def get_fips_enabled(self, obj: dict) -> bool | None:
"""Get FIPS enabled"""
if not LicenseKey.get_total().status().is_valid:
if not LicenseKey.get_total().is_valid():
return None
return obj["fips_enabled"]

View File

@ -6321,6 +6321,22 @@
"authentik_rbac.edit_system_settings",
"authentik_rbac.view_system_info",
"authentik_rbac.view_system_settings",
"authentik_sources_kerberos.add_groupkerberossourceconnection",
"authentik_sources_kerberos.change_groupkerberossourceconnection",
"authentik_sources_kerberos.delete_groupkerberossourceconnection",
"authentik_sources_kerberos.view_groupkerberossourceconnection",
"authentik_sources_kerberos.add_kerberospropertymapping",
"authentik_sources_kerberos.change_kerberospropertymapping",
"authentik_sources_kerberos.delete_kerberospropertymapping",
"authentik_sources_kerberos.view_kerberospropertymapping",
"authentik_sources_kerberos.add_kerberossource",
"authentik_sources_kerberos.change_kerberossource",
"authentik_sources_kerberos.delete_kerberossource",
"authentik_sources_kerberos.view_kerberossource",
"authentik_sources_kerberos.add_userkerberossourceconnection",
"authentik_sources_kerberos.change_userkerberossourceconnection",
"authentik_sources_kerberos.delete_userkerberossourceconnection",
"authentik_sources_kerberos.view_userkerberossourceconnection",
"authentik_sources_ldap.add_ldapsource",
"authentik_sources_ldap.change_ldapsource",
"authentik_sources_ldap.delete_ldapsource",
@ -6345,26 +6361,14 @@
"authentik_sources_oauth.change_useroauthsourceconnection",
"authentik_sources_oauth.delete_useroauthsourceconnection",
"authentik_sources_oauth.view_useroauthsourceconnection",
"authentik_sources_plex.add_groupplexsourceconnection",
"authentik_sources_plex.change_groupplexsourceconnection",
"authentik_sources_plex.delete_groupplexsourceconnection",
"authentik_sources_plex.view_groupplexsourceconnection",
"authentik_sources_plex.add_plexsource",
"authentik_sources_plex.change_plexsource",
"authentik_sources_plex.delete_plexsource",
"authentik_sources_plex.view_plexsource",
"authentik_sources_plex.add_plexsourcepropertymapping",
"authentik_sources_plex.change_plexsourcepropertymapping",
"authentik_sources_plex.delete_plexsourcepropertymapping",
"authentik_sources_plex.view_plexsourcepropertymapping",
"authentik_sources_plex.add_plexsourceconnection",
"authentik_sources_plex.add_userplexsourceconnection",
"authentik_sources_plex.change_plexsourceconnection",
"authentik_sources_plex.change_userplexsourceconnection",
"authentik_sources_plex.delete_plexsourceconnection",
"authentik_sources_plex.delete_userplexsourceconnection",
"authentik_sources_plex.view_plexsourceconnection",
"authentik_sources_plex.view_userplexsourceconnection",
"authentik_sources_saml.add_groupsamlsourceconnection",
"authentik_sources_saml.change_groupsamlsourceconnection",
"authentik_sources_saml.delete_groupsamlsourceconnection",
@ -11980,6 +11984,22 @@
"authentik_rbac.edit_system_settings",
"authentik_rbac.view_system_info",
"authentik_rbac.view_system_settings",
"authentik_sources_kerberos.add_groupkerberossourceconnection",
"authentik_sources_kerberos.change_groupkerberossourceconnection",
"authentik_sources_kerberos.delete_groupkerberossourceconnection",
"authentik_sources_kerberos.view_groupkerberossourceconnection",
"authentik_sources_kerberos.add_kerberospropertymapping",
"authentik_sources_kerberos.change_kerberospropertymapping",
"authentik_sources_kerberos.delete_kerberospropertymapping",
"authentik_sources_kerberos.view_kerberospropertymapping",
"authentik_sources_kerberos.add_kerberossource",
"authentik_sources_kerberos.change_kerberossource",
"authentik_sources_kerberos.delete_kerberossource",
"authentik_sources_kerberos.view_kerberossource",
"authentik_sources_kerberos.add_userkerberossourceconnection",
"authentik_sources_kerberos.change_userkerberossourceconnection",
"authentik_sources_kerberos.delete_userkerberossourceconnection",
"authentik_sources_kerberos.view_userkerberossourceconnection",
"authentik_sources_ldap.add_ldapsource",
"authentik_sources_ldap.change_ldapsource",
"authentik_sources_ldap.delete_ldapsource",
@ -12004,26 +12024,14 @@
"authentik_sources_oauth.change_useroauthsourceconnection",
"authentik_sources_oauth.delete_useroauthsourceconnection",
"authentik_sources_oauth.view_useroauthsourceconnection",
"authentik_sources_plex.add_groupplexsourceconnection",
"authentik_sources_plex.change_groupplexsourceconnection",
"authentik_sources_plex.delete_groupplexsourceconnection",
"authentik_sources_plex.view_groupplexsourceconnection",
"authentik_sources_plex.add_plexsource",
"authentik_sources_plex.change_plexsource",
"authentik_sources_plex.delete_plexsource",
"authentik_sources_plex.view_plexsource",
"authentik_sources_plex.add_plexsourcepropertymapping",
"authentik_sources_plex.change_plexsourcepropertymapping",
"authentik_sources_plex.delete_plexsourcepropertymapping",
"authentik_sources_plex.view_plexsourcepropertymapping",
"authentik_sources_plex.add_plexsourceconnection",
"authentik_sources_plex.add_userplexsourceconnection",
"authentik_sources_plex.change_plexsourceconnection",
"authentik_sources_plex.change_userplexsourceconnection",
"authentik_sources_plex.delete_plexsourceconnection",
"authentik_sources_plex.delete_userplexsourceconnection",
"authentik_sources_plex.view_plexsourceconnection",
"authentik_sources_plex.view_userplexsourceconnection",
"authentik_sources_saml.add_groupsamlsourceconnection",
"authentik_sources_saml.change_groupsamlsourceconnection",
"authentik_sources_saml.delete_groupsamlsourceconnection",

2
go.mod
View File

@ -28,7 +28,7 @@ require (
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2024063.6
goauthentik.io/api/v3 v3.2024063.5
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.22.0
golang.org/x/sync v0.8.0

4
go.sum
View File

@ -293,8 +293,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
goauthentik.io/api/v3 v3.2024063.6 h1:TloJKYEhdxej4PRPjQiA//SlaSByxc5XCYT3QmjErN8=
goauthentik.io/api/v3 v3.2024063.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2024063.5 h1:iCjsJDDGt9H8AkNk0cQdGV6PYErKJlFThPsmbZ4Vp6E=
goauthentik.io/api/v3 v3.2024063.5/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

40
poetry.lock generated
View File

@ -3098,8 +3098,6 @@ files = [
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"},
{file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"},
{file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"},
{file = "orjson-3.10.6-cp313-none-win32.whl", hash = "sha256:efdf2c5cde290ae6b83095f03119bdc00303d7a03b42b16c54517baa3c4ca3d0"},
{file = "orjson-3.10.6-cp313-none-win_amd64.whl", hash = "sha256:8e190fe7888e2e4392f52cafb9626113ba135ef53aacc65cd13109eb9746c43e"},
{file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"},
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"},
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"},
@ -4187,29 +4185,29 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.5.7"
version = "0.5.6"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"},
{file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"},
{file = "ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e"},
{file = "ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499"},
{file = "ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e"},
{file = "ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5"},
{file = "ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e"},
{file = "ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a"},
{file = "ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3"},
{file = "ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4"},
{file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"},
{file = "ruff-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:a0ef5930799a05522985b9cec8290b185952f3fcd86c1772c3bdbd732667fdcd"},
{file = "ruff-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b652dc14f6ef5d1552821e006f747802cc32d98d5509349e168f6bf0ee9f8f42"},
{file = "ruff-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80521b88d26a45e871f31e4b88938fd87db7011bb961d8afd2664982dfc3641a"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9bc8f328a9f1309ae80e4d392836e7dbc77303b38ed4a7112699e63d3b066ab"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d394940f61f7720ad371ddedf14722ee1d6250fd8d020f5ea5a86e7be217daf"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111a99cdb02f69ddb2571e2756e017a1496c2c3a2aeefe7b988ddab38b416d36"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e395daba77a79f6dc0d07311f94cc0560375ca20c06f354c7c99af3bf4560c5d"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c476acb43c3c51e3c614a2e878ee1589655fa02dab19fe2db0423a06d6a5b1b6"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2ff8003f5252fd68425fd53d27c1f08b201d7ed714bb31a55c9ac1d4c13e2eb"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f77c1c3aa0669fb230b06fb24ffa3e879391a3ba3f15e3d633a752da5a3e670"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f908148c93c02873210a52cad75a6eda856b2cbb72250370ce3afef6fb99b1ed"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:563a7ae61ad284187d3071d9041c08019975693ff655438d8d4be26e492760bd"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:94fe60869bfbf0521e04fd62b74cbca21cbc5beb67cbb75ab33fe8c174f54414"},
{file = "ruff-0.5.6-py3-none-win32.whl", hash = "sha256:e6a584c1de6f8591c2570e171cc7ce482bb983d49c70ddf014393cd39e9dfaed"},
{file = "ruff-0.5.6-py3-none-win_amd64.whl", hash = "sha256:d7fe7dccb1a89dc66785d7aa0ac283b2269712d8ed19c63af908fdccca5ccc1a"},
{file = "ruff-0.5.6-py3-none-win_arm64.whl", hash = "sha256:57c6c0dd997b31b536bff49b9eee5ed3194d60605a4427f735eeb1f9c1b8d264"},
{file = "ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642"},
]
[[package]]

View File

@ -41386,26 +41386,28 @@ components:
type: integer
external_users:
type: integer
status:
$ref: '#/components/schemas/LicenseSummaryStatusEnum'
valid:
type: boolean
show_admin_warning:
type: boolean
show_user_warning:
type: boolean
read_only:
type: boolean
latest_valid:
type: string
format: date-time
has_license:
type: boolean
required:
- external_users
- has_license
- internal_users
- latest_valid
- status
LicenseSummaryStatusEnum:
enum:
- unlicensed
- valid
- expired
- expiry_soon
- limit_exceeded_admin
- limit_exceeded_user
- read_only
type: string
- show_admin_warning
- show_user_warning
- valid
Link:
type: object
description: Returns a single link

View File

@ -43,7 +43,7 @@ const otherFiles = [
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"],
["src/custom.css", "."],
["src/common/styles/**", "."],
["packages/common/src/styles/**", "."],
["src/assets/images/**", "./assets/images"],
["./icons/*", "./assets/icons"],
];

View File

@ -12,6 +12,8 @@ export default [
{
ignores: [
"dist/",
".wireit/",
"packages/",
// don't ever lint node_modules
"node_modules/",
".storybook/*",

767
web/package-lock.json generated
View File

@ -11,7 +11,8 @@
"license": "MIT",
"workspaces": [
".",
"./packages/*"
"./packages/sfe",
"./packages/common"
],
"dependencies": {
"@codemirror/lang-html": "^6.4.9",
@ -23,7 +24,7 @@
"@floating-ui/dom": "^1.6.9",
"@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2024.6.3-1723206419",
"@goauthentik/api": "^2024.6.3-1723109801",
"@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2",
"@lit/reactive-element": "^2.0.4",
@ -3372,9 +3373,13 @@
"license": "MIT"
},
"node_modules/@goauthentik/api": {
"version": "2024.6.3-1723206419",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.6.3-1723206419.tgz",
"integrity": "sha512-hxDk3SvCVsUKdfFccvrTCAGOn+qV307PeZ556/GcxWtpH9gcwMv48oTthQu29pwim6qxlVVVHlCeqD48kDRM2g=="
"version": "2024.6.3-1723109801",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.6.3-1723109801.tgz",
"integrity": "sha512-liqFlqaAqmcCQyfrfkmJC3W+6tgzglf5hpONNpyV6qCxY81xVKzL4qhW9gk1CMbViVCrnKCeFBdYEyLHus7izg=="
},
"node_modules/@goauthentik/common": {
"resolved": "packages/common",
"link": true
},
"node_modules/@goauthentik/web": {
"resolved": "",
@ -4085,6 +4090,61 @@
"node": ">= 8"
}
},
"node_modules/@npmcli/agent": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz",
"integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==",
"dev": true,
"dependencies": {
"agent-base": "^7.1.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.1",
"lru-cache": "^10.0.1",
"socks-proxy-agent": "^8.0.3"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/@npmcli/agent/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
},
"node_modules/@npmcli/fs": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz",
"integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==",
"dev": true,
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/@npmcli/fs/node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@npmcli/redact": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz",
"integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==",
"dev": true,
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/@open-wc/lit-helpers": {
"version": "0.7.0",
"license": "MIT",
@ -8756,6 +8816,11 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/webappsec-credential-management": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/@types/webappsec-credential-management/-/webappsec-credential-management-0.6.8.tgz",
"integrity": "sha512-DES/SkK54U7AG8hmMkGCJkOSlywM3R+TzaWT+rBnX3lQTJ3K57jWr+UccWY8ImkuKekC9BjB+AH4zLJB4JKpvQ=="
},
"node_modules/@types/which": {
"version": "2.0.2",
"dev": true,
@ -10135,6 +10200,19 @@
"node": ">= 14"
}
},
"node_modules/aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
"dev": true,
"dependencies": {
"clean-stack": "^2.0.0",
"indent-string": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"dev": true,
@ -10556,6 +10634,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/awilix": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/awilix/-/awilix-10.0.2.tgz",
"integrity": "sha512-hFatb7eZFdtiWjjmGRSm/K/uxZpmcBlM+YoeMB3VpOPXk3xa6+7zctg3LRbUzoimom5bwGrePF0jXReO6b4zNQ==",
"dev": true,
"dependencies": {
"camel-case": "^4.1.2",
"fast-glob": "^3.3.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/axios": {
"version": "1.7.3",
"license": "MIT",
@ -11201,6 +11292,113 @@
"node": ">= 0.8"
}
},
"node_modules/cacache": {
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz",
"integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==",
"dev": true,
"dependencies": {
"@npmcli/fs": "^3.1.0",
"fs-minipass": "^3.0.0",
"glob": "^10.2.2",
"lru-cache": "^10.0.1",
"minipass": "^7.0.3",
"minipass-collect": "^2.0.1",
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.4",
"p-map": "^4.0.0",
"ssri": "^10.0.0",
"tar": "^6.1.11",
"unique-filename": "^3.0.0"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/cacache/node_modules/fs-minipass": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
"integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
"dev": true,
"dependencies": {
"minipass": "^7.0.3"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/cacache/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/cacache/node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/cacache/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
},
"node_modules/cacache/node_modules/p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
"dev": true,
"dependencies": {
"aggregate-error": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cacache/node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/cacheable-lookup": {
"version": "7.0.0",
"dev": true,
@ -11273,6 +11471,16 @@
"node": ">=6"
}
},
"node_modules/camel-case": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
"integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
"dev": true,
"dependencies": {
"pascal-case": "^3.1.2",
"tslib": "^2.0.3"
}
},
"node_modules/camelcase": {
"version": "5.3.1",
"dev": true,
@ -11434,6 +11642,15 @@
"consola": "^3.2.3"
}
},
"node_modules/clean-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/cli-cursor": {
"version": "3.1.0",
"dev": true,
@ -12936,7 +13153,6 @@
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
@ -12946,7 +13162,6 @@
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
@ -13015,6 +13230,12 @@
"node": ">=4"
}
},
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"dev": true
},
"node_modules/error-ex": {
"version": "1.3.2",
"dev": true,
@ -13959,6 +14180,24 @@
"eslint": ">=5.16.0"
}
},
"node_modules/eslint-config-nightmare-mode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/eslint-config-nightmare-mode/-/eslint-config-nightmare-mode-2.3.0.tgz",
"integrity": "sha512-oDstNzzG6wwOUupvQniUpV641RLlP6NFkltQVBdHx67CjaOsIXkPbDWWYANvx1BwxscPQW+Mzh5NFPATvgDBEQ==",
"dev": true,
"dependencies": {
"object-assign": "^2.0.0"
}
},
"node_modules/eslint-plugin-custom-elements": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/eslint-plugin-custom-elements/-/eslint-plugin-custom-elements-0.0.8.tgz",
"integrity": "sha512-726XMAabRLKKm6/yjvYfvY4MKBwX9C4x8yPjj/ap470KhSIBHm+xHbm3P7cKlsFz/4cxq6YrBeSwKmwlacF1jg==",
"dev": true,
"peerDependencies": {
"eslint": ">=4.19.0"
}
},
"node_modules/eslint-plugin-lit": {
"version": "1.14.0",
"dev": true,
@ -15914,6 +16153,15 @@
"node": ">=0.8.19"
}
},
"node_modules/indent-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"dev": true,
@ -16255,6 +16503,12 @@
"node": ">=8"
}
},
"node_modules/is-lambda": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
"integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
"dev": true
},
"node_modules/is-module": {
"version": "1.0.0",
"dev": true,
@ -17148,6 +17402,15 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonparse": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
"integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
"dev": true,
"engines": [
"node >= 0.2.0"
]
},
"node_modules/jsonschema": {
"version": "1.4.1",
"dev": true,
@ -17733,6 +17996,15 @@
"loose-envify": "cli.js"
}
},
"node_modules/lower-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
"dev": true,
"dependencies": {
"tslib": "^2.0.3"
}
},
"node_modules/lowercase-keys": {
"version": "3.0.0",
"dev": true,
@ -17779,6 +18051,29 @@
"dev": true,
"license": "ISC"
},
"node_modules/make-fetch-happen": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz",
"integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==",
"dev": true,
"dependencies": {
"@npmcli/agent": "^2.0.0",
"cacache": "^18.0.0",
"http-cache-semantics": "^4.1.1",
"is-lambda": "^1.0.1",
"minipass": "^7.0.2",
"minipass-fetch": "^3.0.0",
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.4",
"negotiator": "^0.6.3",
"proc-log": "^4.2.0",
"promise-retry": "^2.0.1",
"ssri": "^10.0.0"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/map-or-similar": {
"version": "1.5.0",
"dev": true,
@ -18457,6 +18752,125 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minipass-collect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz",
"integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==",
"dev": true,
"dependencies": {
"minipass": "^7.0.3"
},
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minipass-fetch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz",
"integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==",
"dev": true,
"dependencies": {
"minipass": "^7.0.3",
"minipass-sized": "^1.0.3",
"minizlib": "^2.1.2"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
},
"optionalDependencies": {
"encoding": "^0.1.13"
}
},
"node_modules/minipass-flush": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
"integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
"dev": true,
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minipass-flush/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minipass-flush/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/minipass-pipeline": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
"integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
"dev": true,
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minipass-pipeline/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minipass-pipeline/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/minipass-sized": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
"integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
"dev": true,
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minipass-sized/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minipass-sized/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/minizlib": {
"version": "2.1.2",
"dev": true,
@ -18957,6 +19371,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
"dev": true,
"dependencies": {
"lower-case": "^2.0.2",
"tslib": "^2.0.3"
}
},
"node_modules/node-abi": {
"version": "3.65.0",
"license": "MIT",
@ -19182,6 +19606,25 @@
"node": ">=10"
}
},
"node_modules/npm-registry-fetch": {
"version": "17.1.0",
"resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz",
"integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==",
"dev": true,
"dependencies": {
"@npmcli/redact": "^2.0.0",
"jsonparse": "^1.3.1",
"make-fetch-happen": "^13.0.0",
"minipass": "^7.0.2",
"minipass-fetch": "^3.0.0",
"minizlib": "^2.1.2",
"npm-package-arg": "^11.0.0",
"proc-log": "^4.0.0"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/npm-run-all": {
"version": "4.1.5",
"dev": true,
@ -19342,6 +19785,15 @@
"node": "^14.16.0 || >=16.10.0"
}
},
"node_modules/object-assign": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
"integrity": "sha512-CdsOUYIh5wIiozhJ3rLQgmUTgcyzFwZZrqhkKhODMoGtPKM+wt0h0CNIoauJWMsS9822EdzPsF/6mb4nLvPN5g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-hash": {
"version": "3.0.0",
"dev": true,
@ -19801,6 +20253,16 @@
"node": ">= 0.8"
}
},
"node_modules/pascal-case": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
"integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
"dev": true,
"dependencies": {
"no-case": "^3.0.4",
"tslib": "^2.0.3"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"dev": true,
@ -20245,6 +20707,19 @@
"node": ">=0.4.0"
}
},
"node_modules/promise-retry": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
"dev": true,
"dependencies": {
"err-code": "^2.0.2",
"retry": "^0.12.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prompts": {
"version": "2.4.2",
"dev": true,
@ -21402,6 +21877,25 @@
"dev": true,
"license": "MIT"
},
"node_modules/rimraf": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
"dev": true,
"dependencies": {
"glob": "^11.0.0",
"package-json-from-dist": "^1.0.0"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/robust-predicates": {
"version": "3.0.2",
"license": "Unlicense"
@ -22243,6 +22737,18 @@
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/ssri": {
"version": "10.0.6",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz",
"integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==",
"dev": true,
"dependencies": {
"minipass": "^7.0.3"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/stack-utils": {
"version": "2.0.6",
"dev": true,
@ -24091,6 +24597,194 @@
"dev": true,
"license": "MIT"
},
"node_modules/typesync": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/typesync/-/typesync-0.13.0.tgz",
"integrity": "sha512-t5+DHmXqNHJyX9PSocEEB6c5gQlO0j0LLxEiZ/HMz0lWJWBf+bKEXTORkquAuUgjMZ7U5Hx8w63Qmebx7bK2FA==",
"dev": true,
"dependencies": {
"awilix": "^10.0.2",
"chalk": "^4.1.2",
"cosmiconfig": "^9.0.0",
"detect-indent": "^6.0.0",
"glob": "^10.4.2",
"js-yaml": "^4.1.0",
"npm-registry-fetch": "^17.1.0",
"ora": "^5.1.0",
"semver": "^7.6.2"
},
"bin": {
"typesync": "bin/typesync"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/typesync/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/typesync/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"node_modules/typesync/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/typesync/node_modules/cosmiconfig": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
"integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
"dev": true,
"dependencies": {
"env-paths": "^2.2.1",
"import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
"parse-json": "^5.2.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/d-fischer"
},
"peerDependencies": {
"typescript": ">=4.9.5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/typesync/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/typesync/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/typesync/node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/typesync/node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/typesync/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
},
"node_modules/typesync/node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/typesync/node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/typesync/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/typical": {
"version": "4.0.0",
"dev": true,
@ -24179,6 +24873,30 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/unique-filename": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz",
"integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==",
"dev": true,
"dependencies": {
"unique-slug": "^4.0.0"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/unique-slug": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz",
"integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==",
"dev": true,
"dependencies": {
"imurmurhash": "^0.1.4"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/unique-string": {
"version": "3.0.0",
"dev": true,
@ -25431,6 +26149,41 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"packages/common": {
"name": "@goauthentik/common",
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@sentry/browser": "^8.23.0",
"@types/webappsec-credential-management": "^0.6.8",
"base64-js": "^1.5.1"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
"@types/eslint__js": "^8.42.3",
"esbuild": "^0.23.0",
"eslint": "^9.8.0",
"eslint-config-google": "^0.14.0",
"eslint-config-nightmare-mode": "^2.3.0",
"eslint-plugin-custom-elements": "^0.0.8",
"eslint-plugin-lit": "^1.14.0",
"eslint-plugin-sonarjs": "^1.0.4",
"glob": "^11.0.0",
"lit-analyzer": "^2.0.3",
"lockfile-lint": "^4.14.0",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"syncpack": "^12.4.0",
"typescript": "^5.5.4",
"typescript-eslint": "^8.0.1",
"typesync": "^0.13.0",
"wireit": "^0.14.4"
},
"peerDependencies": {
"@lit/localize": "^0.12.2",
"lit": "^3.2.0"
}
},
"packages/sfe": {
"name": "@goauthentik/web-sfe",
"version": "0.0.0",

View File

@ -11,7 +11,7 @@
"@floating-ui/dom": "^1.6.9",
"@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2024.6.3-1723206419",
"@goauthentik/api": "^2024.6.3-1723109801",
"@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2",
"@lit/reactive-element": "^2.0.4",
@ -138,6 +138,8 @@
"lint": "wireit",
"lint:lockfile": "wireit",
"lint:package": "wireit",
"lint:precommit": "wireit",
"lint:nightmare": "wireit",
"lit-analyse": "wireit",
"postinstall": "bash scripts/patch-spotlight.sh",
"precommit": "wireit",
@ -186,6 +188,7 @@
],
"dependencies": [
"build-locales",
"./packages/common:build",
"./packages/sfe:build"
],
"env": {
@ -241,6 +244,10 @@
},
"lint": {
"command": "eslint --max-warnings 0 --fix",
"dependencies": [
"./packages/common:lint",
"./packages/common:build:types"
],
"env": {
"NODE_OPTIONS": "--max_old_space_size=65536"
}
@ -251,6 +258,7 @@
"lint:types": {
"command": "tsc --noEmit -p .",
"dependencies": [
"./packages/common:build:types",
"build-locales"
]
},
@ -322,13 +330,19 @@
"command": "node scripts/build-storybook-import-maps.mjs"
},
"test": {
"command": "wdio run ./wdio.conf.ts --logLevel=warn --autoCompileOpts.tsNodeOpts.project=tsconfig.test.json",
"command": "wdio run ./wdio.conf.mts --logLevel=warn --autoCompileOpts.tsNodeOpts.project=tsconfig.test.json",
"dependencies": [
"./packages/common:build"
],
"env": {
"CI": "true"
}
},
"test-view": {
"command": "wdio run ./wdio.conf.ts --autoCompileOpts.tsNodeOpts.project=tsconfig.test.json"
"command": "wdio run ./wdio.conf.mts --autoCompileOpts.tsNodeOpts.project=tsconfig.test.json",
"dependencies": [
"./packages/common:build"
]
},
"tsc": {
"dependencies": [
@ -338,6 +352,7 @@
},
"workspaces": [
".",
"./packages/*"
"./packages/sfe",
"./packages/common"
]
}

View File

@ -0,0 +1,16 @@
# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
# don't lint nyc coverage output
coverage
# Import order matters
poly.ts
src/locale-codes.ts
src/locales/
storybook-static/
# Prettier breaks the tsconfig file
tsconfig.json
.storybook/css-import-maps*
package.json
packages/**/package.json

View File

@ -0,0 +1,23 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 100,
"proseWrap": "preserve",
"quoteProps": "consistent",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 4,
"trailingComma": "all",
"useTabs": false,
"vueIndentScriptAndStyle": false,
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrder": ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"importOrderParserPlugins": ["typescript", "classProperties", "decorators-legacy"]
}

View File

@ -0,0 +1,93 @@
# @goauthentik/common
The `common` package is a bit of a grab-bag of tools, utilities, and configuration details used
throughout the Authentik front-end suite. Here, we'll try (emphasis on the _try_) to document what
each part does.
- `./api`
The `./api` folder contains helpers and plug-ins for communicating with the Authentik API. Its
primary purpose is to provide the default configuration details for establishing a channel to the
API, as well as figuring out the default locale, branding information, and even the favicon. (See
what I said about it being a grab-bag?) It has its own list of todos.
- `/helpers/plex`
Contains configuration tools and access for the Plex TV client. Used by all three primary
interfaces, but again, not exactly a foundational tool.
- `/helpers/webauthn`
Used entirely by the WebAuthn tools in the Flow interface.
- `/styles`:
authentik's overrides for patternfly and dark mode.
TODO: Move this into its own package.
- `/ui`:
Describes the schema of the UIConfig Attributes Object, which dictates certain details about UI
behavior, such as the preliminary state of drawers, editors, and layouts. It also has an API call
to fetch that UIConfig object from the server.
- `/constants.ts`
Another grab-bag of configuration details: event names, default classnames for setting some visual
details, web socket message type tokens, and the localstorage key.
- `/enums.ts`
Contains one thing: a mapping of generic UI sizing terms to specific classnames in the CSS.
- `./errors.ts`
An error handling toolkit related to the `./api` above.
- `./events.ts`
An extension of the API's "Event" types to assist in reporting server-side events to the user. Has
nothing to do with the browser's internal Event type. Used entirely within `./admin`, may be
suitable to being moved there.
- `./global.ts`
A single function that retrieves any global information for the UI from the `index.html` file in
which it was invoked. Used by our Django application to preload configuration information.
- `./labels.ts`,
Maps a variety of API tokens to human-readable labels, including those for:
- Events
- Severities
- User Types
- Stage Intent
It might make more sense to move these closer to where they're used, if their use is local to a
single interface or component.
- `./messages.ts`
Contains one thing: a mapping of generic UI alert-level terms to specific classnames in the CSS.
- `./sentry.ts`
Sentry is an application monitoring package for finding code breakage. The Sentry configuration for
all of our interfaces is kept here.
- `./users.ts`
Despite the plural name, this is entirely about getting the current user's configuration from the
server. Used by all three major interfaces. Could probably be replaced by a context. (Possibly
already has been.)
- `./utils.ts`
The classic junk drawer of UI development. A few string functions, a few utilities from
YouMightNotNeedLodash, a slugifier, some date handling utilities, that sort of thing.
- `./ws.ts`
Sets up our web socket for receiving server-side events. Used by all three major interfaces.

View File

@ -0,0 +1,53 @@
import * as esbuild from "esbuild";
import fs from "fs";
import { globSync } from "glob";
import path from "path";
import { cwd } from "process";
import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const isProdBuild = process.env.NODE_ENV === "production";
const apiBasePath = process.env.AK_API_BASE_PATH || "";
const definitions = {
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
"process.env.CWD": JSON.stringify(cwd()),
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
};
const otherFiles = [["src/styles/**", "styles"]];
const isFile = (filePath) => fs.statSync(filePath).isFile();
function nameCopyTarget(src, dest, strip) {
const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base);
return [src, target];
}
for (const [source, rawdest, strip] of otherFiles) {
const matchedPaths = globSync(source);
const dest = path.join("dist", rawdest);
const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip));
for (const [src, dest] of copyTargets) {
if (isFile(src)) {
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.copyFileSync(src, dest);
}
}
}
const tsfiles = globSync("src/**/*.ts");
esbuild
.build({
entryPoints: tsfiles,
sourcemap: true,
bundle: false,
tsconfig: "./tsconfig.build.json",
outdir: "dist/",
format: "esm",
define: definitions,
loader: { ".css": "text" },
})
.catch(() => process.exit(1));

View File

@ -0,0 +1,82 @@
import eslint from "@eslint/js";
import tsparser from "@typescript-eslint/parser";
import litconf from "eslint-plugin-lit";
import wcconf from "eslint-plugin-wc";
import globals from "globals";
import tseslint from "typescript-eslint";
export default [
// You would not believe how much this change has frustrated users: ["if an ignores key is used
// without any other keys in the configuration object, then the patterns act as global
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
{
ignores: [
"dist/",
".wireit/",
"packages/common/.wireit/",
// don't ever lint node_modules
"node_modules/",
".storybook/*",
// don't lint build output (make sure it's set to your correct build folder name)
// don't lint nyc coverage output
"coverage/",
"src/locale-codes.ts",
"storybook-static/",
"src/locales/",
],
},
eslint.configs.recommended,
wcconf.configs["flat/recommended"],
litconf.configs["flat/recommended"],
...tseslint.configs.recommended,
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
},
files: ["src/**"],
rules: {
"no-unused-vars": "off",
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.nodeBuiltin,
},
},
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
rules: {
"no-unused-vars": "off",
// We WANT our scripts to output to the console!
"no-console": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
];

5199
web/packages/common/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,156 @@
{
"name": "@goauthentik/common",
"version": "0.0.0",
"dependencies": {
"@sentry/browser": "^8.23.0",
"@types/webappsec-credential-management": "^0.6.8",
"base64-js": "^1.5.1"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
"@types/eslint__js": "^8.42.3",
"esbuild": "^0.23.0",
"eslint": "^9.8.0",
"eslint-config-google": "^0.14.0",
"eslint-config-nightmare-mode": "^2.3.0",
"eslint-plugin-custom-elements": "^0.0.8",
"eslint-plugin-lit": "^1.14.0",
"eslint-plugin-sonarjs": "^1.0.4",
"glob": "^11.0.0",
"lit-analyzer": "^2.0.3",
"lockfile-lint": "^4.14.0",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"syncpack": "^12.4.0",
"typescript": "^5.5.4",
"typescript-eslint": "^8.0.1",
"typesync": "^0.13.0",
"wireit": "^0.14.4"
},
"exports": {
"./*": "./dist/*"
},
"files": [
"./dist/**/*"
],
"license": "MIT",
"peerDependencies": {
"@lit/localize": "^0.12.2",
"lit": "^3.2.0"
},
"private": true,
"scripts": {
"build": "wireit",
"build:types": "wireit",
"format": "wireit",
"lint": "wireit",
"lint:lockfile": "wireit",
"lint:nightmare": "wireit",
"lint:package": "wireit",
"lint:spelling": "wireit",
"lint:types": "wireit",
"precommit": "wireit",
"prettier": "wireit"
},
"type": "module",
"wireit": {
"build": {
"command": "${NODE_RUNNER} build.mjs",
"dependencies": [
"build:types"
],
"files": [
"src/**/*.{css,jpg,png,ts,js,json}",
"!src/**/*.stories.ts",
"!src/**/*.tests.ts",
"!src/locales/*.ts",
"!src/locale-codes.ts"
],
"output": [
"./dist/**/*.js",
"./dist/**/*.css"
],
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
}
},
"build:types": {
"command": "tsc --declaration -p .",
"files": [
"src/**/*.ts",
"!src/**/*.stories.ts",
"!src/**/*.tests.ts"
],
"output": [
"./dist/**/*.d.ts"
]
},
"lint": {
"command": "eslint --max-warnings 0 --fix --config ./eslint.config.mjs",
"env": {
"NODE_OPTIONS": "--max_old_space_size=65536"
}
},
"lint:types": {
"command": "tsc --noEmit -p ."
},
"lint:lockfile": {
"command": "lockfile-lint --path package.json --type npm --allowed-hosts npm --validate-https"
},
"lint:package": {
"command": "syncpack format -i ' '"
},
"lint:precommit": {
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --precommit",
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
}
},
"lint:nightmare": {
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --nightmare",
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
}
},
"prettier": {
"command": "prettier --write .",
"dependencies": [
"lint:package"
]
},
"format": {
"command": "prettier --write .",
"dependencies": [
"lint:package"
]
},
"precommit": {
"command": "prettier --write .",
"dependencies": [
"lint:types",
"lint:spelling",
"lint:lockfile",
"lint:package",
"lint:precommit"
],
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
}
},
"lint:spelling": {
"command": "node scripts/check-spelling.mjs"
}
}
}

View File

@ -0,0 +1,15 @@
import { execSync } from "child_process";
import path from "path";
const projectRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).replace(
"\n",
"",
);
const cmd = [
"codespell -D -",
`-D ${path.join(projectRoot, ".github/codespell-dictionary.txt")}`,
`-I ${path.join(projectRoot, ".github/codespell-words.txt")}`,
"-S './src/locales/**' ./src -s",
].join(" ");
console.log(execSync(cmd, { encoding: "utf8" }));

View File

@ -0,0 +1,56 @@
import { execFileSync } from "child_process";
import { ESLint } from "eslint";
import fs from "fs";
import path from "path";
import process from "process";
import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const projectRoot = path.join(__dirname, "..");
process.chdir(projectRoot);
function changedFiles() {
const gitStatus = execFileSync("git", ["diff", "--name-only", "HEAD"], { encoding: "utf8" });
const gitUntracked = execFileSync("git", ["ls-files", "--others", "--exclude-standard"], {
encoding: "utf8",
});
const changed = gitStatus
.split("\n")
.filter((line) => line.trim().substring(0, 4) === "web/")
.filter((line) => /\.(m|c)?(t|j)s$/.test(line))
.map((line) => line.substring(4))
.filter((line) => fs.existsSync(line));
const untracked = gitUntracked
.split("\n")
.filter((line) => /\.(m|c)?(t|j)s$/.test(line))
.filter((line) => fs.existsSync(line));
const sourceFiles = [...changed, ...untracked].filter((line) => /^src\//.test(line));
const scriptFiles = [...changed, ...untracked].filter(
(line) => /^scripts\//.test(line) || !/^src\//.test(line),
);
return [...sourceFiles, ...scriptFiles];
}
const hasFlag = (flags) => process.argv.length > 1 && flags.includes(process.argv[2]);
const [configFile, files] = hasFlag(["-n", "--nightmare"])
? [path.join(__dirname, "eslint.nightmare.mjs"), changedFiles()]
: hasFlag(["-p", "--precommit"])
? [path.join(__dirname, "eslint.precommit.mjs"), changedFiles()]
: [path.join(projectRoot, "eslint.config.mjs"), ["."]];
const eslint = new ESLint({
overrideConfigFile: configFile,
warnIgnored: false,
});
const results = await eslint.lintFiles(files);
const formatter = await eslint.loadFormatter("stylish");
const resultText = formatter.format(results);
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
console.log(resultText);
process.exit(errors > 1 ? 1 : 0);

View File

@ -0,0 +1,203 @@
import eslint from "@eslint/js";
import tsparser from "@typescript-eslint/parser";
import litconf from "eslint-plugin-lit";
import sonar from "eslint-plugin-sonarjs";
import wcconf from "eslint-plugin-wc";
import globals from "globals";
import tseslint from "typescript-eslint";
export default [
// You would not believe how much this change has frustrated users: ["if an ignores key is used
// without any other keys in the configuration object, then the patterns act as global
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
{
ignores: [
"dist/",
// don't ever lint node_modules
"node_modules/",
".storybook/",
".wireit/",
// don't lint build output (make sure it's set to your correct build folder name)
// don't lint nyc coverage output
"coverage/",
"src/locale-codes.ts",
"storybook-static/",
"src/locales/",
],
},
eslint.configs.recommended,
wcconf.configs["flat/recommended"],
litconf.configs["flat/recommended"],
...tseslint.configs.recommended,
sonar.configs.recommended,
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.browser,
process: "readonly",
},
},
files: ["src/**"],
rules: {
"accessor-pairs": "error",
"array-callback-return": "error",
"block-scoped-var": "error",
"consistent-return": "error",
"consistent-this": ["error", "that"],
"curly": ["error", "all"],
"dot-notation": [
"error",
{
allowKeywords: true,
},
],
"eqeqeq": "error",
"func-names": "error",
"guard-for-in": "error",
"max-depth": ["error", 4],
"max-nested-callbacks": ["error", 4],
"max-params": ["error", 5],
"new-cap": "error",
"no-alert": "error",
"no-array-constructor": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-div-regex": "error",
"no-dupe-args": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-else-return": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-function": "error",
"no-labels": "error",
"no-eq-null": "error",
"no-eval": "error",
"no-ex-assign": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-boolean-cast": "error",
"no-extra-label": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-implied-eval": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-inner-declarations": ["error", "functions"],
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-iterator": "error",
"no-invalid-this": "error",
"no-label-var": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": ["error", { ignore: [0, 1, -1] }],
"no-multi-str": "error",
"no-negated-condition": "error",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-obj-calls": "error",
"no-octal": "error",
"no-octal-escape": "error",
"no-param-reassign": "error",
"no-proto": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-restricted-syntax": ["error", "WithStatement"],
"no-script-url": "error",
"no-self-assign": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "warn",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef": "error",
"no-undef-init": "error",
"no-unexpected-multiline": "error",
"no-useless-constructor": "error",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unreachable": "error",
"no-unused-expressions": "error",
"no-unused-labels": "error",
"no-use-before-define": "error",
"no-useless-call": "error",
"no-dupe-class-members": "error",
"no-var": "error",
"no-void": "error",
"no-with": "error",
"prefer-arrow-callback": "error",
"prefer-const": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"radix": "error",
"require-yield": "error",
"strict": ["error", "global"],
"use-isnan": "error",
"valid-typeof": "error",
"vars-on-top": "error",
"yoda": ["error", "never"],
"no-unused-vars": "off",
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
"sonarjs/cognitive-complexity": ["off", 9],
"sonarjs/no-duplicate-string": "off",
"sonarjs/no-nested-template-literals": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.nodeBuiltin,
},
},
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
rules: {
"no-unused-vars": "off",
"no-console": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
];

View File

@ -0,0 +1,85 @@
import eslint from "@eslint/js";
import tsparser from "@typescript-eslint/parser";
import litconf from "eslint-plugin-lit";
import sonar from "eslint-plugin-sonarjs";
import wcconf from "eslint-plugin-wc";
import globals from "globals";
import tseslint from "typescript-eslint";
export default [
// You would not believe how much this change has frustrated users: ["if an ignores key is used
// without any other keys in the configuration object, then the patterns act as global
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
{
ignores: [
"dist/",
".wireit/",
// don't ever lint node_modules
"node_modules/",
".storybook/*",
// don't lint build output (make sure it's set to your correct build folder name)
// don't lint nyc coverage output
"coverage/",
"src/locale-codes.ts",
"storybook-static/",
"src/locales/",
],
},
eslint.configs.recommended,
wcconf.configs["flat/recommended"],
litconf.configs["flat/recommended"],
...tseslint.configs.recommended,
sonar.configs.recommended,
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
},
files: ["src/**"],
rules: {
"no-unused-vars": "off",
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
"sonarjs/cognitive-complexity": ["off", 9],
"sonarjs/no-duplicate-string": "off",
"sonarjs/no-nested-template-literals": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.nodeBuiltin,
},
},
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
rules: {
"no-unused-vars": "off",
"no-console": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
];

View File

@ -2,12 +2,32 @@ import {
CSRFMiddleware,
EventMiddleware,
LoggingMiddleware,
} from "@goauthentik/common/api/middleware";
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global";
} from "@goauthentik/common/api/middleware.js";
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants.js";
import { globalAK } from "@goauthentik/common/global.js";
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
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({
basePath: `${process.env.AK_API_BASE_PATH || window.location.origin}/api/v3`,
headers: {
"sentry-trace": getMetaContent("sentry-trace"),
},
middleware: [
new CSRFMiddleware(),
new EventMiddleware(),
new LoggingMiddleware(globalAK().brand),
],
});
let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config);
export function config(): Promise<Config> {
if (!globalConfigPromise) {
@ -61,24 +81,6 @@ export function brand(): Promise<CurrentBrand> {
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({
basePath: (process.env.AK_API_BASE_PATH || window.location.origin) + "/api/v3",
headers: {
"sentry-trace": getMetaContent("sentry-trace"),
},
middleware: [
new CSRFMiddleware(),
new EventMiddleware(),
new LoggingMiddleware(globalAK().brand),
],
});
// This is just a function so eslint doesn't complain about
// missing-whitespace-between-attributes or
// unexpected-character-in-attribute-name

View File

@ -1,5 +1,5 @@
import { EVENT_REQUEST_POST } from "@goauthentik/common/constants";
import { getCookie } from "@goauthentik/common/utils";
import { EVENT_REQUEST_POST } from "@goauthentik/common/constants.js";
import { getCookie } from "@goauthentik/common/utils.js";
import {
CurrentBrand,
@ -17,6 +17,8 @@ export interface RequestInfo {
status: number;
}
const HTTP_BAD_REQUEST = 400;
export class LoggingMiddleware implements Middleware {
brand: CurrentBrand;
constructor(brand: CurrentBrand) {
@ -28,7 +30,7 @@ export class LoggingMiddleware implements Middleware {
// https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output
msg += `%c${context.response.status}%c ${context.init.method} ${context.url}`;
let style = "";
if (context.response.status >= 400) {
if (context.response.status >= HTTP_BAD_REQUEST) {
style = "color: red; font-weight: bold;";
}
console.debug(msg, style, "");
@ -38,7 +40,7 @@ export class LoggingMiddleware implements Middleware {
export class CSRFMiddleware implements Middleware {
pre?(context: RequestContext): Promise<FetchParams | void> {
// @ts-ignore
// @ts-expect-error Headers collection type does not recognize 'X-' headers.
context.init.headers[CSRFHeaderName] = getCookie("authentik_csrf");
return Promise.resolve(context);
}

View File

@ -12,18 +12,25 @@ export class RequestError extends Error {}
export type APIErrorTypes = ValidationError | GenericError;
const HTTP_BAD_REQUEST = 400;
const HTTP_FORBIDDEN = 403;
const HTTP_INTERNAL_SERVICE_ERROR = 500;
export async function parseAPIError(error: Error): Promise<APIErrorTypes> {
if (!(error instanceof ResponseError)) {
return error;
}
if (error.response.status < 400 || error.response.status > 499) {
if (
error.response.status < HTTP_BAD_REQUEST ||
error.response.status >= HTTP_INTERNAL_SERVICE_ERROR
) {
return error;
}
const body = await error.response.json();
if (error.response.status === 400) {
if (error.response.status === HTTP_BAD_REQUEST) {
return ValidationErrorFromJSON(body);
}
if (error.response.status === 403) {
if (error.response.status === HTTP_FORBIDDEN) {
return GenericErrorFromJSON(body);
}
return body;

View File

@ -8,6 +8,13 @@ export interface EventUser {
is_anonymous?: boolean;
}
export interface EventModel {
pk: string;
name: string;
app: string;
model_name: string;
}
export interface EventContext {
[key: string]: EventContext | EventModel | string | number | string[];
}
@ -17,13 +24,6 @@ export interface EventWithContext extends Event {
context: EventContext;
}
export interface EventModel {
pk: string;
name: string;
app: string;
model_name: string;
}
export interface EventRequest {
path: string;
method: string;

View File

@ -1,5 +1,5 @@
import { VERSION } from "@goauthentik/common/constants";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { VERSION } from "@goauthentik/common/constants.js";
import { SentryIgnoredError } from "@goauthentik/common/errors.js";
export interface PlexPinResponse {
// Only has the fields we care about
@ -23,14 +23,16 @@ export const DEFAULT_HEADERS = {
"X-Plex-Device-Vendor": "goauthentik.io",
};
const HTTP_OK = 200;
const POLL_TIMEOUT = 500; // milliseconds
export async function popupCenterScreen(
url: string,
title: string,
w: number,
h: number,
): Promise<Window | null> {
const top = (screen.height - h) / 4,
left = (screen.width - w) / 2;
const [top, left] = [(screen.height - h) / 4, (screen.width - w) / 2];
return new Promise((resolve) => {
setTimeout(() => {
const popup = window.open(
@ -78,7 +80,7 @@ export class PlexAPIClient {
const pinResponse = await fetch(`https://plex.tv/api/v2/pins/${id}`, {
headers: headers,
});
if (pinResponse.status > 200) {
if (pinResponse.status > HTTP_OK) {
throw new SentryIgnoredError("Invalid response code");
}
const pin: PlexPinResponse = await pinResponse.json();
@ -97,7 +99,7 @@ export class PlexAPIClient {
if (response) {
resolve(response);
} else {
setTimeout(executePoll, 500, resolve, reject);
setTimeout(executePoll, POLL_TIMEOUT, resolve, reject);
}
} catch (e) {
reject(e as Error);

View File

@ -3,7 +3,7 @@ import * as base64js from "base64-js";
import { msg } from "@lit/localize";
export function b64enc(buf: Uint8Array): string {
return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/[=]/g, "");
}
export function b64RawEnc(buf: Uint8Array): string {

View File

@ -1,7 +1,7 @@
import { config } from "@goauthentik/common/api/config";
import { VERSION } from "@goauthentik/common/constants";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { me } from "@goauthentik/common/users";
import { config } from "@goauthentik/common/api/config.js";
import { VERSION } from "@goauthentik/common/constants.js";
import { SentryIgnoredError } from "@goauthentik/common/errors.js";
import { me } from "@goauthentik/common/users.js";
import {
ErrorEvent,
EventHint,
@ -15,6 +15,17 @@ import { CapabilitiesEnum, Config, ResponseError } from "@goauthentik/api";
export const TAG_SENTRY_COMPONENT = "authentik.component";
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
const MIN_PATH_LENGTH = 2;
// Get the interface name from URL
export function currentInterface(): string {
const pathMatches = window.location.pathname.match(/.+if\/(\w+)\//);
let knownInterface = "unknown";
if (pathMatches && pathMatches.length >= MIN_PATH_LENGTH) {
knownInterface = pathMatches[1];
}
return knownInterface.toLowerCase();
}
export async function configureSentry(canDoPpi = false): Promise<Config> {
const cfg = await config();
@ -81,13 +92,3 @@ export async function configureSentry(canDoPpi = false): Promise<Config> {
}
return cfg;
}
// Get the interface name from URL
export function currentInterface(): string {
const pathMatches = window.location.pathname.match(/.+if\/(\w+)\//);
let currentInterface = "unknown";
if (pathMatches && pathMatches.length >= 2) {
currentInterface = pathMatches[1];
}
return currentInterface.toLowerCase();
}

View File

@ -1,5 +1,5 @@
import { currentInterface } from "@goauthentik/common/sentry";
import { me } from "@goauthentik/common/users";
import { currentInterface } from "@goauthentik/common/sentry.js";
import { me } from "@goauthentik/common/users.js";
import { UiThemeEnum, UserSelf } from "@goauthentik/api";

View File

@ -1,14 +1,12 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants.js";
import { CoreApi, ResponseError, SessionUser } from "@goauthentik/api";
let globalMePromise: Promise<SessionUser> | undefined;
export function refreshMe(): Promise<SessionUser> {
globalMePromise = undefined;
return me();
}
const HTTP_FORBIDDEN = 403;
const HTTP_UNAUTHORIZED = 401;
export function me(): Promise<SessionUser> {
if (!globalMePromise) {
@ -48,7 +46,10 @@ export function me(): Promise<SessionUser> {
systemPermissions: [],
},
};
if (ex.response?.status === 401 || ex.response?.status === 403) {
if (
ex.response?.status === HTTP_UNAUTHORIZED ||
ex.response?.status === HTTP_FORBIDDEN
) {
const relativeUrl = window.location
.toString()
.substring(window.location.origin.length);
@ -61,3 +62,8 @@ export function me(): Promise<SessionUser> {
}
return globalMePromise;
}
export function refreshMe(): Promise<SessionUser> {
globalMePromise = undefined;
return me();
}

View File

@ -1,4 +1,4 @@
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { SentryIgnoredError } from "@goauthentik/common/errors.js";
import { CSSResult, css } from "lit";
@ -9,7 +9,7 @@ export function getCookie(name: string): string {
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === name + "=") {
if (cookie.substring(0, name.length + 1) === `${name}=`) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
@ -25,21 +25,21 @@ export function convertToSlug(text: string): string {
.replace(/[^\w-]+/g, "");
}
const WORD_COUNT_TRUNCATION_DEFAULT = 10;
/**
* Truncate a string based on maximum word count
*/
export function truncateWords(string: string, length = 10): string {
string = string || "";
const array = string.trim().split(" ");
const ellipsis = array.length > length ? "..." : "";
return array.slice(0, length).join(" ") + ellipsis;
export function truncateWords(input: string, length = WORD_COUNT_TRUNCATION_DEFAULT): string {
const words = (input ?? "").trim().split(" ");
const ellipsis = words.length > length ? "..." : "";
return words.slice(0, length).join(" ") + ellipsis;
}
const CHAR_COUNT_TRUNCATION_DEFAULT = 10;
/**
* Truncate a string based on character count
*/
export function truncate(string: string, length = 10): string {
export function truncate(string: string, length = CHAR_COUNT_TRUNCATION_DEFAULT): string {
return string.length > length ? `${string.substring(0, length)}...` : string;
}
@ -83,20 +83,24 @@ export const ascii_lowercase = "abcdefghijklmnopqrstuvwxyz";
export const ascii_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
export const ascii_letters = ascii_lowercase + ascii_uppercase;
export const digits = "0123456789";
export const hexdigits = digits + "abcdef" + "ABCDEF";
export const hexdigits = `${digits}abcdefABCDEF}`;
export const octdigits = "01234567";
export const punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
const BYTE_SIZE = 256;
export function randomString(len: number, charset: string): string {
const chars = [];
const array = new Uint8Array(len);
self.crypto.getRandomValues(array);
for (let index = 0; index < len; index++) {
chars.push(charset[Math.floor(charset.length * (array[index] / Math.pow(2, 8)))]);
chars.push(charset[Math.floor(charset.length * (array[index] / BYTE_SIZE))]);
}
return chars.join("");
}
const TIMEZONE_OFFSET = 60000; // milliseconds
export function dateTimeLocal(date: Date): string {
// So for some reason, the datetime-local input field requires ISO Datetime as value
// But the standard javascript date.toISOString() returns everything with seconds and
@ -105,7 +109,7 @@ export function dateTimeLocal(date: Date): string {
// figure.
// Additionally, toISOString always returns the date without timezone, which we would like
// to include for better usability
const tzOffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
const tzOffset = new Date().getTimezoneOffset() * TIMEZONE_OFFSET;
const localISOTime = new Date(date.getTime() - tzOffset).toISOString().slice(0, -1);
const parts = localISOTime.split(":");
return `${parts[0]}:${parts[1]}`;
@ -122,7 +126,7 @@ export function dateToUTC(date: Date): Date {
// then subtract the timezone offset to create an "invalid" date (correct time&date)
// but it still "thinks" it's in local TZ
const timestamp = date.getTime();
const offset = -1 * (new Date().getTimezoneOffset() * 60000);
const offset = -1 * (new Date().getTimezoneOffset() * TIMEZONE_OFFSET);
return new Date(timestamp - offset);
}
@ -151,13 +155,26 @@ export function adaptCSS(sheet: AdaptableStylesheet | AdaptableStylesheet[]): Ad
return Array.isArray(sheet) ? sheet.map(_adaptCSS) : _adaptCSS(sheet);
}
const SECONDS_IN_A_MINUTE = 60;
const MINUTES_IN_AN_HOUR = 60;
const HOURS_IN_A_DAY = 24;
const DAYS_IN_A_YEAR = 365;
const MONTHS_IN_A_YEAR = 12;
const MILLISECONDS_IN_A_SECOND = 1000;
const MILLISECONDS_IN_A_MINUTE = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE;
const MILLISECONDS_IN_AN_HOUR = MILLISECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR;
const MILLISECONDS_IN_A_DAY = MILLISECONDS_IN_AN_HOUR * HOURS_IN_A_DAY;
const MILLISECONDS_IN_A_YEAR = MILLISECONDS_IN_A_DAY * DAYS_IN_A_YEAR;
const MILLISECONDS_IN_A_MONTH = MILLISECONDS_IN_A_YEAR / MONTHS_IN_A_YEAR;
const _timeUnits = new Map<Intl.RelativeTimeFormatUnit, number>([
["year", 24 * 60 * 60 * 1000 * 365],
["month", (24 * 60 * 60 * 1000 * 365) / 12],
["day", 24 * 60 * 60 * 1000],
["hour", 60 * 60 * 1000],
["minute", 60 * 1000],
["second", 1000],
["year", MILLISECONDS_IN_A_YEAR],
["month", MILLISECONDS_IN_A_MONTH],
["day", MILLISECONDS_IN_A_DAY],
["hour", MILLISECONDS_IN_AN_HOUR],
["minute", MILLISECONDS_IN_A_MINUTE],
["second", MILLISECONDS_IN_A_SECOND],
]);
export function getRelativeTime(d1: Date, d2: Date = new Date()): string {
@ -166,9 +183,9 @@ export function getRelativeTime(d1: Date, d2: Date = new Date()): string {
// "Math.abs" accounts for both "past" & "future" scenarios
for (const [key, value] of _timeUnits) {
if (Math.abs(elapsed) > value || key == "second") {
if (Math.abs(elapsed) > value || key === "second") {
return rtf.format(Math.round(elapsed / value), key);
}
}
return rtf.format(Math.round(elapsed / 1000), "second");
return rtf.format(Math.round(elapsed / MILLISECONDS_IN_A_SECOND), "second");
}

View File

@ -1,5 +1,5 @@
import { EVENT_MESSAGE, EVENT_WS_MESSAGE } from "@goauthentik/common/constants";
import { MessageLevel } from "@goauthentik/common/messages";
import { EVENT_MESSAGE, EVENT_WS_MESSAGE } from "@goauthentik/common/constants.js";
import { MessageLevel } from "@goauthentik/common/messages.js";
import { msg } from "@lit/localize";
@ -7,9 +7,14 @@ export interface WSMessage {
message_type: string;
}
const MESSAGE_RETRY_DELAY = 200; // milliseconds
const CLOSE_RETRY_DELAY = 6000; // milliseconds
const OPEN_RETRY_DELAY = 200; // milliseconds
const RETRY_BACKOFF = 2;
export class WebsocketClient {
messageSocket?: WebSocket;
retryDelay = 200;
retryDelay = MESSAGE_RETRY_DELAY;
constructor() {
try {
@ -20,18 +25,20 @@ export class WebsocketClient {
}
connect(): void {
if (navigator.webdriver) return;
if (navigator.webdriver) {
return;
}
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
window.location.host
}/ws/client/`;
this.messageSocket = new WebSocket(wsUrl);
this.messageSocket.addEventListener("open", () => {
console.debug(`authentik/ws: connected to ${wsUrl}`);
this.retryDelay = 200;
this.retryDelay = OPEN_RETRY_DELAY;
});
this.messageSocket.addEventListener("close", (e) => {
console.debug("authentik/ws: closed ws connection", e);
if (this.retryDelay > 6000) {
if (this.retryDelay > CLOSE_RETRY_DELAY) {
window.dispatchEvent(
new CustomEvent(EVENT_MESSAGE, {
bubbles: true,
@ -47,7 +54,7 @@ export class WebsocketClient {
console.debug(`authentik/ws: reconnecting ws in ${this.retryDelay}ms`);
this.connect();
}, this.retryDelay);
this.retryDelay = this.retryDelay * 2;
this.retryDelay = this.retryDelay * RETRY_BACKOFF;
});
this.messageSocket.addEventListener("message", (e) => {
const data = JSON.parse(e.data);
@ -60,7 +67,7 @@ export class WebsocketClient {
);
});
this.messageSocket.addEventListener("error", () => {
this.retryDelay = this.retryDelay * 2;
this.retryDelay = this.retryDelay * RETRY_BACKOFF;
});
}
}

View File

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "./dist/",
"types": ["webauthn"],
"paths": {
"@goauthentik/elements/*": ["./src/*"],
"@goauthentik/locales/*": ["src/locales/*"]
}
}
}

View File

@ -0,0 +1,44 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"outDir": "./dist/",
"esModuleInterop": true,
"paths": {
"@goauthentik/common/*": ["./src/*"],
"@goauthentik/locales/*": ["src/locales/*"]
},
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"sourceMap": true,
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"lib": [
"ES5",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020",
"ESNext",
"DOM",
"DOM.Iterable",
"WebWorker"
],
"noUnusedLocals": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"useDefineForClassFields": false,
"alwaysStrict": true,
"noImplicitAny": true,
},
"exclude": ["node_modules", "dist"]
}

View File

@ -48,6 +48,11 @@ const eslint = new ESLint({
warnIgnored: false,
});
if (files.length < 1) {
console.log("eslint: change set contains no lintable files");
process.exit(0);
}
const results = await eslint.lintFiles(files);
const formatter = await eslint.loadFormatter("stylish");
const resultText = formatter.format(results);

View File

@ -13,6 +13,8 @@ export default [
{
ignores: [
"dist/",
".wireit/",
"packages/",
// don't ever lint node_modules
"node_modules/",
".storybook/*",

View File

@ -13,6 +13,8 @@ export default [
{
ignores: [
"dist/",
".wireit/",
"packages/",
// don't ever lint node_modules
"node_modules/",
".storybook/*",

3057
web/sfe/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +0,0 @@
{
"name": "@goauthentik/web-sfe",
"version": "0.0.0",
"private": true,
"license": "MIT",
"dependencies": {
"@goauthentik/api": "^2024.6.3-1723109801",
"base64-js": "^1.5.1",
"bootstrap": "^4.6.1",
"formdata-polyfill": "^4.0.10",
"jquery": "^3.7.1",
"weakmap-polyfill": "^2.0.4"
},
"scripts": {
"build": "rollup -c rollup.config.js --bundleConfigAsCjs",
"watch": "rollup -w -c rollup.config.js --bundleConfigAsCjs"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-swc": "^0.3.1",
"@swc/cli": "^0.4.0",
"@swc/core": "^1.7.6",
"@types/jquery": "^3.5.30",
"rollup": "^4.20.0",
"rollup-plugin-copy": "^3.5.0"
}
}

View File

@ -1,12 +1,12 @@
import { ROUTES } from "@goauthentik/admin/Routes";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import {
EVENT_API_DRAWER_TOGGLE,
EVENT_NOTIFICATION_DRAWER_TOGGLE,
} from "@goauthentik/common/constants";
import { configureSentry } from "@goauthentik/common/sentry";
import { me } from "@goauthentik/common/users";
import { WebsocketClient } from "@goauthentik/common/ws";
} from "@goauthentik/common/constants.js";
import { configureSentry } from "@goauthentik/common/sentry.js";
import { me } from "@goauthentik/common/users.js";
import { WebsocketClient } from "@goauthentik/common/ws.js";
import { EnterpriseAwareInterface } from "@goauthentik/elements/Interface";
import "@goauthentik/elements/ak-locale-context";
import "@goauthentik/elements/enterprise/EnterpriseStatusBanner";
@ -71,12 +71,6 @@ export class AdminInterface extends EnterpriseAwareInterface {
:host([theme="dark"]) .pf-c-page {
--pf-c-page--BackgroundColor: var(--ak-dark-background);
}
ak-enterprise-status {
grid-area: header;
}
ak-admin-sidebar {
grid-area: nav;
}
`,
];
}
@ -124,7 +118,6 @@ export class AdminInterface extends EnterpriseAwareInterface {
return html` <ak-locale-context>
<div class="pf-c-page">
<ak-enterprise-status interface="admin"></ak-enterprise-status>
<ak-admin-sidebar
class="pf-c-page__sidebar ${classMap(sidebarClasses)}"
></ak-admin-sidebar>

View File

@ -1,6 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants";
import { me } from "@goauthentik/common/users";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants.js";
import { me } from "@goauthentik/common/users.js";
import { AKElement } from "@goauthentik/elements/Base";
import {
CapabilitiesEnum,

View File

@ -1,5 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { MessageLevel } from "@goauthentik/common/messages";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { MessageLevel } from "@goauthentik/common/messages.js";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/PageHeader";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";

View File

@ -8,8 +8,8 @@ import "@goauthentik/admin/admin-overview/cards/WorkerStatusCard";
import "@goauthentik/admin/admin-overview/charts/AdminLoginAuthorizeChart";
import "@goauthentik/admin/admin-overview/charts/OutpostStatusChart";
import "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
import { VERSION } from "@goauthentik/common/constants";
import { me } from "@goauthentik/common/users";
import { VERSION } from "@goauthentik/common/constants.js";
import { me } from "@goauthentik/common/users.js";
import { AKElement } from "@goauthentik/elements/Base";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
import "@goauthentik/elements/PageHeader";

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/Spinner";

View File

@ -1,4 +1,4 @@
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { EVENT_REFRESH } from "@goauthentik/common/constants.js";
import { PFSize } from "@goauthentik/common/enums.js";
import { AggregateCard } from "@goauthentik/elements/cards/AggregateCard";

View File

@ -2,7 +2,7 @@ import {
AdminStatus,
AdminStatusCard,
} from "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";

View File

@ -1,8 +1,8 @@
import { EventGeo, EventUser } from "@goauthentik/admin/events/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EventWithContext } from "@goauthentik/common/events";
import { actionToLabel } from "@goauthentik/common/labels";
import { getRelativeTime } from "@goauthentik/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { EventWithContext } from "@goauthentik/common/events.js";
import { actionToLabel } from "@goauthentik/common/labels.js";
import { getRelativeTime } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-event-info";
import "@goauthentik/elements/Tabs";
import "@goauthentik/elements/buttons/Dropdown";

View File

@ -2,7 +2,7 @@ import {
AdminStatus,
AdminStatusCard,
} from "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";

View File

@ -2,7 +2,7 @@ import {
AdminStatus,
AdminStatusCard,
} from "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";

View File

@ -2,7 +2,7 @@ import {
AdminStatus,
AdminStatusCard,
} from "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { AKChart, RGBAColor } from "@goauthentik/elements/charts/Chart";
import { ChartData } from "chart.js";

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { AKChart } from "@goauthentik/elements/charts/Chart";
import { ChartData, Tick } from "chart.js";

View File

@ -1,5 +1,5 @@
import { SummarizedSyncStatus } from "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { AKChart } from "@goauthentik/elements/charts/Chart";
import "@goauthentik/elements/forms/ConfirmationForm";
import { ChartData, ChartOptions } from "chart.js";

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { AKChart } from "@goauthentik/elements/charts/Chart";
import "@goauthentik/elements/forms/ConfirmationForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";

View File

@ -1,5 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { first } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";

View File

@ -1,6 +1,6 @@
import "@goauthentik/admin/admin-settings/AdminSettingsForm";
import { AdminSettingsForm } from "@goauthentik/admin/admin-settings/AdminSettingsForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import "@goauthentik/components/events/ObjectChangelog";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/CodeMirror";

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { AKChart } from "@goauthentik/elements/charts/Chart";
import { ChartData, Tick } from "chart.js";

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/events/LogViewer";
import { Form } from "@goauthentik/elements/forms/Form";

View File

@ -1,7 +1,7 @@
import "@goauthentik/admin/applications/ProviderSelectModal";
import { iconHelperText } from "@goauthentik/admin/helperText";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { first } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-file-input";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";

View File

@ -1,5 +1,5 @@
import "@goauthentik/admin/applications/ApplicationForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { PFSize } from "@goauthentik/common/enums.js";
import "@goauthentik/components/ak-app-icon";
import MDApplication from "@goauthentik/docs/applications/index.md";

View File

@ -3,7 +3,7 @@ import "@goauthentik/admin/applications/ApplicationCheckAccessForm";
import "@goauthentik/admin/applications/ApplicationForm";
import "@goauthentik/admin/policies/BoundPoliciesList";
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { PFSize } from "@goauthentik/common/enums.js";
import "@goauthentik/components/ak-app-icon";
import "@goauthentik/components/events/ObjectChangelog";

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import "@goauthentik/elements/buttons/SpinnerButton";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { TableColumn } from "@goauthentik/elements/table/Table";

View File

@ -1,5 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { groupBy } from "@goauthentik/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { groupBy } from "@goauthentik/common/utils.js";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/SearchSelect";

View File

@ -1,5 +1,5 @@
import { policyOptions } from "@goauthentik/admin/applications/ApplicationForm";
import { first } from "@goauthentik/common/utils";
import { first } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-slug-input";
import "@goauthentik/components/ak-switch-input";

View File

@ -1,5 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { EVENT_REFRESH } from "@goauthentik/common/constants.js";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";

View File

@ -2,7 +2,7 @@ import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { first } from "@goauthentik/common/utils";
import { first } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";

View File

@ -11,8 +11,8 @@ import {
makeOAuth2PropertyMappingsSelector,
oauth2PropertyMappingsProvider,
} from "@goauthentik/admin/providers/oauth2/Oauth2PropertyMappings.js";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";

View File

@ -3,8 +3,8 @@ import {
makeProxyPropertyMappingsSelector,
proxyPropertyMappingsProvider,
} from "@goauthentik/admin/providers/proxy/ProxyProviderPropertyMappings.js";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { first } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input";

View File

@ -1,4 +1,4 @@
import { first } from "@goauthentik/common/utils";
import { first } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";

View File

@ -1,7 +1,7 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-text-input";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import "@goauthentik/elements/forms/FormGroup";

View File

@ -3,7 +3,7 @@ import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import "@goauthentik/components/ak-multi-select";
import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-radio-input";

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { AKElement } from "@goauthentik/elements/Base";
import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";

View File

@ -2,8 +2,8 @@ import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { first } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-multi-select";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";

View File

@ -1,6 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { docLink } from "@goauthentik/common/global";
import { first } from "@goauthentik/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { docLink } from "@goauthentik/common/global.js";
import { first } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-toggle-group";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";

View File

@ -1,8 +1,8 @@
import "@goauthentik/admin/blueprints/BlueprintForm";
import "@goauthentik/admin/rbac/ObjectPermissionModal";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { getRelativeTime } from "@goauthentik/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { EVENT_REFRESH } from "@goauthentik/common/constants.js";
import { getRelativeTime } from "@goauthentik/common/utils.js";
import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/buttons/ActionButton";
import "@goauthentik/elements/buttons/SpinnerButton";

View File

@ -1,7 +1,7 @@
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { first } from "@goauthentik/common/utils.js";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";

View File

@ -1,6 +1,6 @@
import "@goauthentik/admin/brands/BrandForm";
import "@goauthentik/admin/rbac/ObjectPermissionModal";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import "@goauthentik/components/ak-status-label";
import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/buttons/SpinnerButton";

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { AKElement } from "@goauthentik/elements/Base";
import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { AKElement } from "@goauthentik/elements/Base";
import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
import "@goauthentik/elements/forms/SearchSelect";

View File

@ -1,5 +1,5 @@
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config.js";
import { AKElement } from "@goauthentik/elements/Base";
import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
import "@goauthentik/elements/forms/SearchSelect";

Some files were not shown because too many files have changed in this diff Show More