Merge branch 'main' into web/lage

* main: (21 commits)
  web: bump API Client version (#7964)
  release: 2023.10.5
  web: bump API Client version (#7962)
  providers/scim: use lock for sync (#7948)
  stages/email: prevent authentik emails from being marked as spam (also add text template support) (#7949)
  website/docs: prepare 2023.10.5 (#7947)
  web: bump the sentry group in /web with 2 updates (#7955)
  web: bump the wdio group in /tests/wdio with 4 updates (#7957)
  core: bump goauthentik.io/api/v3 from 3.2023104.4 to 3.2023104.5 (#7958)
  web: bump API Client version (#7953)
  events: add ASN Database reader (#7793)
  translate: Updates for file web/xliff/en.xlf in fr (#7951)
  website/docs: add expression example for geoip (#7739)
  website: fix hosted API browser (#7946)
  core: bump github.com/redis/go-redis/v9 from 9.3.0 to 9.3.1 (#7942)
  web: bump the sentry group in /web with 2 updates (#7944)
  web: bump the storybook group in /web with 7 updates (#7945)
  core: bump goauthentik.io/api/v3 from 3.2023104.3 to 3.2023104.4 (#7943)
  providers/scim: set timeout based on page and page count (#7941)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7928)
  ...
This commit is contained in:
Ken Sternberg 2023-12-21 12:11:25 -08:00
commit 12559f518a
566 changed files with 1041 additions and 9398 deletions

View file

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 2023.10.4 current_version = 2023.10.5
tag = True tag = True
commit = True commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)

View file

@ -71,7 +71,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
# Stage 4: MaxMind GeoIP # Stage 4: MaxMind GeoIP
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v6.0 as geoip FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v6.0 as geoip
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City" ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
ENV GEOIPUPDATE_VERBOSE="true" ENV GEOIPUPDATE_VERBOSE="true"
ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID" ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID"
ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY" ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY"

View file

@ -2,7 +2,7 @@
from os import environ from os import environ
from typing import Optional from typing import Optional
__version__ = "2023.10.4" __version__ = "2023.10.5"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View file

@ -19,7 +19,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.events.geo import GEOIP_READER from authentik.events.context_processors.base import get_context_processors
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
capabilities = Signal() capabilities = Signal()
@ -30,6 +30,7 @@ class Capabilities(models.TextChoices):
CAN_SAVE_MEDIA = "can_save_media" CAN_SAVE_MEDIA = "can_save_media"
CAN_GEO_IP = "can_geo_ip" CAN_GEO_IP = "can_geo_ip"
CAN_ASN = "can_asn"
CAN_IMPERSONATE = "can_impersonate" CAN_IMPERSONATE = "can_impersonate"
CAN_DEBUG = "can_debug" CAN_DEBUG = "can_debug"
IS_ENTERPRISE = "is_enterprise" IS_ENTERPRISE = "is_enterprise"
@ -68,8 +69,9 @@ class ConfigView(APIView):
deb_test = settings.DEBUG or settings.TEST deb_test = settings.DEBUG or settings.TEST
if Path(settings.MEDIA_ROOT).is_mount() or deb_test: if Path(settings.MEDIA_ROOT).is_mount() or deb_test:
caps.append(Capabilities.CAN_SAVE_MEDIA) caps.append(Capabilities.CAN_SAVE_MEDIA)
if GEOIP_READER.enabled: for processor in get_context_processors():
caps.append(Capabilities.CAN_GEO_IP) if cap := processor.capability():
caps.append(cap)
if CONFIG.get_bool("impersonation"): if CONFIG.get_bool("impersonation"):
caps.append(Capabilities.CAN_IMPERSONATE) caps.append(Capabilities.CAN_IMPERSONATE)
if settings.DEBUG: # pragma: no cover if settings.DEBUG: # pragma: no cover

View file

@ -14,7 +14,8 @@ from ua_parser import user_agent_parser
from authentik.api.authorization import OwnerSuperuserPermissions from authentik.api.authorization import OwnerSuperuserPermissions
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import AuthenticatedSession from authentik.core.models import AuthenticatedSession
from authentik.events.geo import GEOIP_READER, GeoIPDict from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR, ASNDict
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR, GeoIPDict
class UserAgentDeviceDict(TypedDict): class UserAgentDeviceDict(TypedDict):
@ -59,6 +60,7 @@ class AuthenticatedSessionSerializer(ModelSerializer):
current = SerializerMethodField() current = SerializerMethodField()
user_agent = SerializerMethodField() user_agent = SerializerMethodField()
geo_ip = SerializerMethodField() geo_ip = SerializerMethodField()
asn = SerializerMethodField()
def get_current(self, instance: AuthenticatedSession) -> bool: def get_current(self, instance: AuthenticatedSession) -> bool:
"""Check if session is currently active session""" """Check if session is currently active session"""
@ -70,8 +72,12 @@ class AuthenticatedSessionSerializer(ModelSerializer):
return user_agent_parser.Parse(instance.last_user_agent) return user_agent_parser.Parse(instance.last_user_agent)
def get_geo_ip(self, instance: AuthenticatedSession) -> Optional[GeoIPDict]: # pragma: no cover def get_geo_ip(self, instance: AuthenticatedSession) -> Optional[GeoIPDict]: # pragma: no cover
"""Get parsed user agent""" """Get GeoIP Data"""
return GEOIP_READER.city_dict(instance.last_ip) return GEOIP_CONTEXT_PROCESSOR.city_dict(instance.last_ip)
def get_asn(self, instance: AuthenticatedSession) -> Optional[ASNDict]: # pragma: no cover
"""Get ASN Data"""
return ASN_CONTEXT_PROCESSOR.asn_dict(instance.last_ip)
class Meta: class Meta:
model = AuthenticatedSession model = AuthenticatedSession
@ -80,6 +86,7 @@ class AuthenticatedSessionSerializer(ModelSerializer):
"current", "current",
"user_agent", "user_agent",
"geo_ip", "geo_ip",
"asn",
"user", "user",
"last_ip", "last_ip",
"last_user_agent", "last_user_agent",

View file

@ -0,0 +1,79 @@
"""ASN Enricher"""
from typing import TYPE_CHECKING, Optional, TypedDict
from django.http import HttpRequest
from geoip2.errors import GeoIP2Error
from geoip2.models import ASN
from sentry_sdk import Hub
from authentik.events.context_processors.mmdb import MMDBContextProcessor
from authentik.lib.config import CONFIG
from authentik.root.middleware import ClientIPMiddleware
if TYPE_CHECKING:
from authentik.api.v3.config import Capabilities
from authentik.events.models import Event
class ASNDict(TypedDict):
"""ASN Details"""
asn: int
as_org: str | None
network: str | None
class ASNContextProcessor(MMDBContextProcessor):
"""ASN Database reader wrapper"""
def capability(self) -> Optional["Capabilities"]:
from authentik.api.v3.config import Capabilities
return Capabilities.CAN_ASN
def path(self) -> str | None:
return CONFIG.get("events.context_processors.asn")
def enrich_event(self, event: "Event"):
asn = self.asn_dict(event.client_ip)
if not asn:
return
event.context["asn"] = asn
def enrich_context(self, request: HttpRequest) -> dict:
return {
"asn": self.asn_dict(ClientIPMiddleware.get_client_ip(request)),
}
def asn(self, ip_address: str) -> Optional[ASN]:
"""Wrapper for Reader.asn"""
with Hub.current.start_span(
op="authentik.events.asn.asn",
description=ip_address,
):
if not self.enabled:
return None
self.check_expired()
try:
return self.reader.asn(ip_address)
except (GeoIP2Error, ValueError):
return None
def asn_to_dict(self, asn: ASN) -> ASNDict:
"""Convert ASN to dict"""
asn_dict: ASNDict = {
"asn": asn.autonomous_system_number,
"as_org": asn.autonomous_system_organization,
"network": str(asn.network) if asn.network else None,
}
return asn_dict
def asn_dict(self, ip_address: str) -> Optional[ASNDict]:
"""Wrapper for self.asn that returns a dict"""
asn = self.asn(ip_address)
if not asn:
return None
return self.asn_to_dict(asn)
ASN_CONTEXT_PROCESSOR = ASNContextProcessor()

View file

@ -0,0 +1,43 @@
"""Base event enricher"""
from functools import cache
from typing import TYPE_CHECKING, Optional
from django.http import HttpRequest
if TYPE_CHECKING:
from authentik.api.v3.config import Capabilities
from authentik.events.models import Event
class EventContextProcessor:
"""Base event enricher"""
def capability(self) -> Optional["Capabilities"]:
"""Return the capability this context processor provides"""
return None
def configured(self) -> bool:
"""Return true if this context processor is configured"""
return False
def enrich_event(self, event: "Event"):
"""Modify event"""
raise NotImplementedError
def enrich_context(self, request: HttpRequest) -> dict:
"""Modify context"""
raise NotImplementedError
@cache
def get_context_processors() -> list[EventContextProcessor]:
"""Get a list of all configured context processors"""
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
processors_types = [ASN_CONTEXT_PROCESSOR, GEOIP_CONTEXT_PROCESSOR]
processors = []
for _type in processors_types:
if _type.configured():
processors.append(_type)
return processors

View file

@ -0,0 +1,84 @@
"""events GeoIP Reader"""
from typing import TYPE_CHECKING, Optional, TypedDict
from django.http import HttpRequest
from geoip2.errors import GeoIP2Error
from geoip2.models import City
from sentry_sdk.hub import Hub
from authentik.events.context_processors.mmdb import MMDBContextProcessor
from authentik.lib.config import CONFIG
from authentik.root.middleware import ClientIPMiddleware
if TYPE_CHECKING:
from authentik.api.v3.config import Capabilities
from authentik.events.models import Event
class GeoIPDict(TypedDict):
"""GeoIP Details"""
continent: str
country: str
lat: float
long: float
city: str
class GeoIPContextProcessor(MMDBContextProcessor):
"""Slim wrapper around GeoIP API"""
def capability(self) -> Optional["Capabilities"]:
from authentik.api.v3.config import Capabilities
return Capabilities.CAN_GEO_IP
def path(self) -> str | None:
return CONFIG.get("events.context_processors.geoip")
def enrich_event(self, event: "Event"):
city = self.city_dict(event.client_ip)
if not city:
return
event.context["geo"] = city
def enrich_context(self, request: HttpRequest) -> dict:
# Different key `geoip` vs `geo` for legacy reasons
return {"geoip": self.city(ClientIPMiddleware.get_client_ip(request))}
def city(self, ip_address: str) -> Optional[City]:
"""Wrapper for Reader.city"""
with Hub.current.start_span(
op="authentik.events.geo.city",
description=ip_address,
):
if not self.enabled:
return None
self.check_expired()
try:
return self.reader.city(ip_address)
except (GeoIP2Error, ValueError):
return None
def city_to_dict(self, city: City) -> GeoIPDict:
"""Convert City to dict"""
city_dict: GeoIPDict = {
"continent": city.continent.code,
"country": city.country.iso_code,
"lat": city.location.latitude,
"long": city.location.longitude,
"city": "",
}
if city.city.name:
city_dict["city"] = city.city.name
return city_dict
def city_dict(self, ip_address: str) -> Optional[GeoIPDict]:
"""Wrapper for self.city that returns a dict"""
city = self.city(ip_address)
if not city:
return None
return self.city_to_dict(city)
GEOIP_CONTEXT_PROCESSOR = GeoIPContextProcessor()

View file

@ -0,0 +1,54 @@
"""Common logic for reading MMDB files"""
from pathlib import Path
from typing import Optional
from geoip2.database import Reader
from structlog.stdlib import get_logger
from authentik.events.context_processors.base import EventContextProcessor
class MMDBContextProcessor(EventContextProcessor):
"""Common logic for reading MaxMind DB files, including re-loading if the file has changed"""
def __init__(self):
self.reader: Optional[Reader] = None
self._last_mtime: float = 0.0
self.logger = get_logger()
self.open()
def path(self) -> str | None:
"""Get the path to the MMDB file to load"""
raise NotImplementedError
def open(self):
"""Get GeoIP Reader, if configured, otherwise none"""
path = self.path()
if path == "" or not path:
return
try:
self.reader = Reader(path)
self._last_mtime = Path(path).stat().st_mtime
self.logger.info("Loaded MMDB database", last_write=self._last_mtime, file=path)
except OSError as exc:
self.logger.warning("Failed to load MMDB database", exc=exc)
def check_expired(self):
"""Check if the modification date of the MMDB database has
changed, and reload it if so"""
path = self.path()
if path == "" or not path:
return
try:
mtime = Path(path).stat().st_mtime
diff = self._last_mtime < mtime
if diff > 0:
self.logger.info("Found new MMDB Database, reopening", diff=diff, path=path)
self.open()
except OSError as exc:
self.logger.warning("Failed to check MMDB age", exc=exc)
@property
def enabled(self) -> bool:
"""Check if MMDB is enabled"""
return bool(self.reader)

View file

@ -1,100 +0,0 @@
"""events GeoIP Reader"""
from os import stat
from typing import Optional, TypedDict
from geoip2.database import Reader
from geoip2.errors import GeoIP2Error
from geoip2.models import City
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger
from authentik.lib.config import CONFIG
LOGGER = get_logger()
class GeoIPDict(TypedDict):
"""GeoIP Details"""
continent: str
country: str
lat: float
long: float
city: str
class GeoIPReader:
"""Slim wrapper around GeoIP API"""
def __init__(self):
self.__reader: Optional[Reader] = None
self.__last_mtime: float = 0.0
self.__open()
def __open(self):
"""Get GeoIP Reader, if configured, otherwise none"""
path = CONFIG.get("geoip")
if path == "" or not path:
return
try:
self.__reader = Reader(path)
self.__last_mtime = stat(path).st_mtime
LOGGER.info("Loaded GeoIP database", last_write=self.__last_mtime)
except OSError as exc:
LOGGER.warning("Failed to load GeoIP database", exc=exc)
def __check_expired(self):
"""Check if the modification date of the GeoIP database has
changed, and reload it if so"""
path = CONFIG.get("geoip")
try:
mtime = stat(path).st_mtime
diff = self.__last_mtime < mtime
if diff > 0:
LOGGER.info("Found new GeoIP Database, reopening", diff=diff)
self.__open()
except OSError as exc:
LOGGER.warning("Failed to check GeoIP age", exc=exc)
return
@property
def enabled(self) -> bool:
"""Check if GeoIP is enabled"""
return bool(self.__reader)
def city(self, ip_address: str) -> Optional[City]:
"""Wrapper for Reader.city"""
with Hub.current.start_span(
op="authentik.events.geo.city",
description=ip_address,
):
if not self.enabled:
return None
self.__check_expired()
try:
return self.__reader.city(ip_address)
except (GeoIP2Error, ValueError):
return None
def city_to_dict(self, city: City) -> GeoIPDict:
"""Convert City to dict"""
city_dict: GeoIPDict = {
"continent": city.continent.code,
"country": city.country.iso_code,
"lat": city.location.latitude,
"long": city.location.longitude,
"city": "",
}
if city.city.name:
city_dict["city"] = city.city.name
return city_dict
def city_dict(self, ip_address: str) -> Optional[GeoIPDict]:
"""Wrapper for self.city that returns a dict"""
city = self.city(ip_address)
if not city:
return None
return self.city_to_dict(city)
GEOIP_READER = GeoIPReader()

View file

@ -26,7 +26,7 @@ from authentik.core.middleware import (
SESSION_KEY_IMPERSONATE_USER, SESSION_KEY_IMPERSONATE_USER,
) )
from authentik.core.models import ExpiringModel, Group, PropertyMapping, User from authentik.core.models import ExpiringModel, Group, PropertyMapping, User
from authentik.events.geo import GEOIP_READER from authentik.events.context_processors.base import get_context_processors
from authentik.events.utils import ( from authentik.events.utils import (
cleanse_dict, cleanse_dict,
get_user, get_user,
@ -246,21 +246,15 @@ class Event(SerializerModel, ExpiringModel):
self.user["on_behalf_of"] = get_user(request.session[SESSION_KEY_IMPERSONATE_USER]) self.user["on_behalf_of"] = get_user(request.session[SESSION_KEY_IMPERSONATE_USER])
# User 255.255.255.255 as fallback if IP cannot be determined # User 255.255.255.255 as fallback if IP cannot be determined
self.client_ip = ClientIPMiddleware.get_client_ip(request) self.client_ip = ClientIPMiddleware.get_client_ip(request)
# Apply GeoIP Data, when enabled # Enrich event data
self.with_geoip() for processor in get_context_processors():
processor.enrich_event(self)
# If there's no app set, we get it from the requests too # If there's no app set, we get it from the requests too
if not self.app: if not self.app:
self.app = Event._get_app_from_request(request) self.app = Event._get_app_from_request(request)
self.save() self.save()
return self return self
def with_geoip(self): # pragma: no cover
"""Apply GeoIP Data, when enabled"""
city = GEOIP_READER.city_dict(self.client_ip)
if not city:
return
self.context["geo"] = city
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self._state.adding: if self._state.adding:
LOGGER.info( LOGGER.info(
@ -467,7 +461,7 @@ class NotificationTransport(SerializerModel):
} }
mail = TemplateEmailMessage( mail = TemplateEmailMessage(
subject=subject_prefix + context["title"], subject=subject_prefix + context["title"],
to=[notification.user.email], to=[f"{notification.user.name} <{notification.user.email}>"],
language=notification.user.locale(), language=notification.user.locale(),
template_name="email/event_notification.html", template_name="email/event_notification.html",
template_context=context, template_context=context,

View file

@ -0,0 +1,24 @@
"""Test ASN Wrapper"""
from django.test import TestCase
from authentik.events.context_processors.asn import ASNContextProcessor
class TestASN(TestCase):
"""Test ASN Wrapper"""
def setUp(self) -> None:
self.reader = ASNContextProcessor()
def test_simple(self):
"""Test simple asn wrapper"""
# IPs from
# https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-ASN-Test.json
self.assertEqual(
self.reader.asn_dict("1.0.0.1"),
{
"asn": 15169,
"as_org": "Google Inc.",
"network": "1.0.0.0/24",
},
)

View file

@ -1,14 +1,14 @@
"""Test GeoIP Wrapper""" """Test GeoIP Wrapper"""
from django.test import TestCase from django.test import TestCase
from authentik.events.geo import GeoIPReader from authentik.events.context_processors.geoip import GeoIPContextProcessor
class TestGeoIP(TestCase): class TestGeoIP(TestCase):
"""Test GeoIP Wrapper""" """Test GeoIP Wrapper"""
def setUp(self) -> None: def setUp(self) -> None:
self.reader = GeoIPReader() self.reader = GeoIPContextProcessor()
def test_simple(self): def test_simple(self):
"""Test simple city wrapper""" """Test simple city wrapper"""

View file

@ -17,12 +17,13 @@ from django.db.models.base import Model
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.utils import timezone from django.utils import timezone
from django.views.debug import SafeExceptionReporterFilter from django.views.debug import SafeExceptionReporterFilter
from geoip2.models import City from geoip2.models import ASN, City
from guardian.utils import get_anonymous_user from guardian.utils import get_anonymous_user
from authentik.blueprints.v1.common import YAMLTag from authentik.blueprints.v1.common import YAMLTag
from authentik.core.models import User from authentik.core.models import User
from authentik.events.geo import GEOIP_READER from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
from authentik.policies.types import PolicyRequest from authentik.policies.types import PolicyRequest
# Special keys which are *not* cleaned, even when the default filter # Special keys which are *not* cleaned, even when the default filter
@ -123,7 +124,9 @@ def sanitize_item(value: Any) -> Any:
if isinstance(value, (HttpRequest, WSGIRequest)): if isinstance(value, (HttpRequest, WSGIRequest)):
return ... return ...
if isinstance(value, City): if isinstance(value, City):
return GEOIP_READER.city_to_dict(value) return GEOIP_CONTEXT_PROCESSOR.city_to_dict(value)
if isinstance(value, ASN):
return ASN_CONTEXT_PROCESSOR.asn_to_dict(value)
if isinstance(value, Path): if isinstance(value, Path):
return str(value) return str(value)
if isinstance(value, Exception): if isinstance(value, Exception):

View file

@ -35,6 +35,7 @@ REDIS_ENV_KEYS = [
] ]
DEPRECATIONS = { DEPRECATIONS = {
"geoip": "events.context_processors.geoip",
"redis.broker_url": "broker.url", "redis.broker_url": "broker.url",
"redis.broker_transport_options": "broker.transport_options", "redis.broker_transport_options": "broker.transport_options",
"redis.cache_timeout": "cache.timeout", "redis.cache_timeout": "cache.timeout",

View file

@ -108,7 +108,10 @@ cookie_domain: null
disable_update_check: false disable_update_check: false
disable_startup_analytics: false disable_startup_analytics: false
avatars: env://AUTHENTIK_AUTHENTIK__AVATARS?gravatar,initials avatars: env://AUTHENTIK_AUTHENTIK__AVATARS?gravatar,initials
geoip: "/geoip/GeoLite2-City.mmdb" events:
context_processors:
geoip: "/geoip/GeoLite2-City.mmdb"
asn: "/geoip/GeoLite2-ASN.mmdb"
footer_links: [] footer_links: []

View file

@ -47,6 +47,7 @@ class ReputationSerializer(ModelSerializer):
"identifier", "identifier",
"ip", "ip",
"ip_geo_data", "ip_geo_data",
"ip_asn_data",
"score", "score",
"updated", "updated",
] ]

View file

@ -0,0 +1,17 @@
# Generated by Django 4.2.7 on 2023-12-05 22:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_reputation", "0005_reputation_expires_reputation_expiring"),
]
operations = [
migrations.AddField(
model_name="reputation",
name="ip_asn_data",
field=models.JSONField(default=dict),
),
]

View file

@ -76,6 +76,7 @@ class Reputation(ExpiringModel, SerializerModel):
identifier = models.TextField() identifier = models.TextField()
ip = models.GenericIPAddressField() ip = models.GenericIPAddressField()
ip_geo_data = models.JSONField(default=dict) ip_geo_data = models.JSONField(default=dict)
ip_asn_data = models.JSONField(default=dict)
score = models.BigIntegerField(default=0) score = models.BigIntegerField(default=0)
expires = models.DateTimeField(default=reputation_expiry) expires = models.DateTimeField(default=reputation_expiry)

View file

@ -2,7 +2,8 @@
from django.core.cache import cache from django.core.cache import cache
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.events.geo import GEOIP_READER from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
from authentik.events.monitored_tasks import ( from authentik.events.monitored_tasks import (
MonitoredTask, MonitoredTask,
TaskResult, TaskResult,
@ -26,7 +27,8 @@ def save_reputation(self: MonitoredTask):
ip=score["ip"], ip=score["ip"],
identifier=score["identifier"], identifier=score["identifier"],
) )
rep.ip_geo_data = GEOIP_READER.city_dict(score["ip"]) or {} rep.ip_geo_data = GEOIP_CONTEXT_PROCESSOR.city_dict(score["ip"]) or {}
rep.ip_asn_data = ASN_CONTEXT_PROCESSOR.asn_dict(score["ip"]) or {}
rep.score = score["score"] rep.score = score["score"]
objects_to_update.append(rep) objects_to_update.append(rep)
Reputation.objects.bulk_update(objects_to_update, ["score", "ip_geo_data"]) Reputation.objects.bulk_update(objects_to_update, ["score", "ip_geo_data"])

View file

@ -8,7 +8,7 @@ from django.db.models import Model
from django.http import HttpRequest from django.http import HttpRequest
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.events.geo import GEOIP_READER from authentik.events.context_processors.base import get_context_processors
if TYPE_CHECKING: if TYPE_CHECKING:
from authentik.core.models import User from authentik.core.models import User
@ -37,15 +37,9 @@ class PolicyRequest:
def set_http_request(self, request: HttpRequest): # pragma: no cover def set_http_request(self, request: HttpRequest): # pragma: no cover
"""Load data from HTTP request, including geoip when enabled""" """Load data from HTTP request, including geoip when enabled"""
from authentik.root.middleware import ClientIPMiddleware
self.http_request = request self.http_request = request
if not GEOIP_READER.enabled: for processor in get_context_processors():
return self.context.update(processor.enrich_context(request))
client_ip = ClientIPMiddleware.get_client_ip(request)
if not client_ip:
return
self.context["geoip"] = GEOIP_READER.city(client_ip)
@property @property
def should_cache(self) -> bool: def should_cache(self) -> bool:

View file

@ -2,6 +2,7 @@
from django.utils.text import slugify from django.utils.text import slugify
from drf_spectacular.utils import OpenApiResponse, extend_schema from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import BooleanField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
@ -9,6 +10,7 @@ from rest_framework.viewsets import ModelViewSet
from authentik.admin.api.tasks import TaskSerializer from authentik.admin.api.tasks import TaskSerializer
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.events.monitored_tasks import TaskInfo from authentik.events.monitored_tasks import TaskInfo
from authentik.providers.scim.models import SCIMProvider from authentik.providers.scim.models import SCIMProvider
@ -37,6 +39,13 @@ class SCIMProviderSerializer(ProviderSerializer):
extra_kwargs = {} extra_kwargs = {}
class SCIMSyncStatusSerializer(PassiveSerializer):
"""SCIM Provider sync status"""
is_running = BooleanField(read_only=True)
tasks = TaskSerializer(many=True, read_only=True)
class SCIMProviderViewSet(UsedByMixin, ModelViewSet): class SCIMProviderViewSet(UsedByMixin, ModelViewSet):
"""SCIMProvider Viewset""" """SCIMProvider Viewset"""
@ -48,15 +57,18 @@ class SCIMProviderViewSet(UsedByMixin, ModelViewSet):
@extend_schema( @extend_schema(
responses={ responses={
200: TaskSerializer(), 200: SCIMSyncStatusSerializer(),
404: OpenApiResponse(description="Task not found"), 404: OpenApiResponse(description="Task not found"),
} }
) )
@action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[]) @action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[])
def sync_status(self, request: Request, pk: int) -> Response: def sync_status(self, request: Request, pk: int) -> Response:
"""Get provider's sync status""" """Get provider's sync status"""
provider = self.get_object() provider: SCIMProvider = self.get_object()
task = TaskInfo.by_name(f"scim_sync:{slugify(provider.name)}") task = TaskInfo.by_name(f"scim_sync:{slugify(provider.name)}")
if not task: tasks = [task] if task else []
return Response(status=404) status = {
return Response(TaskSerializer(task).data) "tasks": tasks,
"is_running": provider.sync_lock.locked(),
}
return Response(SCIMSyncStatusSerializer(status).data)

View file

@ -1,2 +1,3 @@
"""SCIM constants""" """SCIM constants"""
PAGE_SIZE = 100 PAGE_SIZE = 100
PAGE_TIMEOUT = 60 * 60 * 0.5 # Half an hour

View file

@ -1,11 +1,14 @@
"""SCIM Provider models""" """SCIM Provider models"""
from django.core.cache import cache
from django.db import models from django.db import models
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from redis.lock import Lock
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from authentik.core.models import BackchannelProvider, Group, PropertyMapping, User, UserTypes from authentik.core.models import BackchannelProvider, Group, PropertyMapping, User, UserTypes
from authentik.providers.scim.clients import PAGE_TIMEOUT
class SCIMProvider(BackchannelProvider): class SCIMProvider(BackchannelProvider):
@ -27,6 +30,15 @@ class SCIMProvider(BackchannelProvider):
help_text=_("Property mappings used for group creation/updating."), help_text=_("Property mappings used for group creation/updating."),
) )
@property
def sync_lock(self) -> Lock:
"""Redis lock for syncing SCIM to prevent multiple parallel syncs happening"""
return Lock(
cache.client.get_client(),
name=f"goauthentik.io/providers/scim/sync-{str(self.pk)}",
timeout=(60 * 60 * PAGE_TIMEOUT) * 3,
)
def get_user_qs(self) -> QuerySet[User]: def get_user_qs(self) -> QuerySet[User]:
"""Get queryset of all users with consistent ordering """Get queryset of all users with consistent ordering
according to the provider's settings""" according to the provider's settings"""

View file

@ -12,7 +12,7 @@ from structlog.stdlib import get_logger
from authentik.core.models import Group, User from authentik.core.models import Group, User
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.lib.utils.reflection import path_to_class from authentik.lib.utils.reflection import path_to_class
from authentik.providers.scim.clients import PAGE_SIZE from authentik.providers.scim.clients import PAGE_SIZE, PAGE_TIMEOUT
from authentik.providers.scim.clients.base import SCIMClient from authentik.providers.scim.clients.base import SCIMClient
from authentik.providers.scim.clients.exceptions import SCIMRequestException, StopSync from authentik.providers.scim.clients.exceptions import SCIMRequestException, StopSync
from authentik.providers.scim.clients.group import SCIMGroupClient from authentik.providers.scim.clients.group import SCIMGroupClient
@ -47,12 +47,19 @@ def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
).first() ).first()
if not provider: if not provider:
return return
lock = provider.sync_lock
if lock.locked():
LOGGER.debug("SCIM sync locked, skipping task", source=provider.name)
return
self.set_uid(slugify(provider.name)) self.set_uid(slugify(provider.name))
result = TaskResult(TaskResultStatus.SUCCESSFUL, []) result = TaskResult(TaskResultStatus.SUCCESSFUL, [])
result.messages.append(_("Starting full SCIM sync")) result.messages.append(_("Starting full SCIM sync"))
LOGGER.debug("Starting SCIM sync") LOGGER.debug("Starting SCIM sync")
users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE) users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)
groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE) groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)
self.soft_time_limit = self.time_limit = (
users_paginator.count + groups_paginator.count
) * PAGE_TIMEOUT
with allow_join_result(): with allow_join_result():
try: try:
for page in users_paginator.page_range: for page in users_paginator.page_range:
@ -69,7 +76,10 @@ def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
self.set_status(result) self.set_status(result)
@CELERY_APP.task() @CELERY_APP.task(
soft_time_limit=PAGE_TIMEOUT,
task_time_limit=PAGE_TIMEOUT,
)
def scim_sync_users(page: int, provider_pk: int): def scim_sync_users(page: int, provider_pk: int):
"""Sync single or multiple users to SCIM""" """Sync single or multiple users to SCIM"""
messages = [] messages = []

View file

@ -32,7 +32,8 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
settings.TEST = True settings.TEST = True
settings.CELERY["task_always_eager"] = True settings.CELERY["task_always_eager"] = True
CONFIG.set("avatars", "none") CONFIG.set("avatars", "none")
CONFIG.set("geoip", "tests/GeoLite2-City-Test.mmdb") CONFIG.set("events.context_processors.geoip", "tests/GeoLite2-City-Test.mmdb")
CONFIG.set("events.context_processors.asn", "tests/GeoLite2-ASN-Test.mmdb")
CONFIG.set("blueprints_dir", "./blueprints") CONFIG.set("blueprints_dir", "./blueprints")
CONFIG.set( CONFIG.set(
"outposts.container_image_base", "outposts.container_image_base",

View file

@ -110,7 +110,7 @@ class EmailStageView(ChallengeStageView):
try: try:
message = TemplateEmailMessage( message = TemplateEmailMessage(
subject=_(current_stage.subject), subject=_(current_stage.subject),
to=[email], to=[f"{pending_user.name} <{email}>"],
language=pending_user.locale(self.request), language=pending_user.locale(self.request),
template_name=current_stage.template, template_name=current_stage.template,
template_context={ template_context={

View file

@ -0,0 +1,8 @@
{% load i18n %}{% translate "Welcome!" %}
{% translate "We're excited to have you get started. First, you need to confirm your account. Just open the link below." %}
{{ url }}
--
Powered by goauthentik.io.

View file

@ -44,7 +44,7 @@
<tr> <tr>
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center"> <td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
{% blocktranslate with name=source.from %} {% blocktranslate with name=source.from %}
This email was sent from the notification transport <code>{{name}}</code>. This email was sent from the notification transport <code>{{ name }}</code>.
{% endblocktranslate %} {% endblocktranslate %}
</td> </td>
</tr> </tr>

View file

@ -0,0 +1,18 @@
{% load authentik_stages_email %}{% load i18n %}{% translate "Dear authentik user," %}
{% translate "The following notification was created:" %}
{{ body|indent }}
{% if key_value %}
{% translate "Additional attributes:" %}
{% for key, value in key_value.items %}
{{ key }}: {{ value|indent }}{% endfor %}
{% endif %}
{% if source %}{% blocktranslate with name=source.from %}
This email was sent from the notification transport {{ name }}.
{% endblocktranslate %}{% endif %}
--
Powered by goauthentik.io.

View file

@ -0,0 +1,12 @@
{% load i18n %}{% load humanize %}{% blocktrans with username=user.username %}Hi {{ username }},{% endblocktrans %}
{% blocktrans %}
You recently requested to change your password for your authentik account. Use the link below to set a new password.
{% endblocktrans %}
{{ url }}
{% blocktrans with expires=expires|naturaltime %}
If you did not request a password change, please ignore this Email. The link above is valid for {{ expires }}.
{% endblocktrans %}
--
Powered by goauthentik.io.

View file

@ -0,0 +1,7 @@
{% load i18n %}authentik Test-Email
{% blocktrans %}
This is a test email to inform you, that you've successfully configured authentik emails.
{% endblocktrans %}
--
Powered by goauthentik.io.

View file

@ -29,3 +29,9 @@ def inline_static_binary(path: str) -> str:
b64content = b64encode(_file.read().encode()) b64content = b64encode(_file.read().encode())
return f"data:image/{result.suffix};base64,{b64content.decode('utf-8')}" return f"data:image/{result.suffix};base64,{b64content.decode('utf-8')}"
return path return path
@register.filter(name="indent")
def indent_string(val, num_spaces=4):
"""Intent text by a given amount of spaces"""
return val.replace("\n", "\n" + " " * num_spaces)

View file

@ -58,9 +58,11 @@ class TestEmailStageSending(FlowTestCase):
events = Event.objects.filter(action=EventAction.EMAIL_SENT) events = Event.objects.filter(action=EventAction.EMAIL_SENT)
self.assertEqual(len(events), 1) self.assertEqual(len(events), 1)
event = events.first() event = events.first()
self.assertEqual(event.context["message"], f"Email to {self.user.email} sent") self.assertEqual(
event.context["message"], f"Email to {self.user.name} <{self.user.email}> sent"
)
self.assertEqual(event.context["subject"], "authentik") self.assertEqual(event.context["subject"], "authentik")
self.assertEqual(event.context["to_email"], [self.user.email]) self.assertEqual(event.context["to_email"], [f"{self.user.name} <{self.user.email}>"])
self.assertEqual(event.context["from_email"], "system@authentik.local") self.assertEqual(event.context["from_email"], "system@authentik.local")
def test_pending_fake_user(self): def test_pending_fake_user(self):

View file

@ -94,7 +94,7 @@ class TestEmailStage(FlowTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "authentik") self.assertEqual(mail.outbox[0].subject, "authentik")
self.assertEqual(mail.outbox[0].to, [self.user.email]) self.assertEqual(mail.outbox[0].to, [f"{self.user.name} <{self.user.email}>"])
@patch( @patch(
"authentik.stages.email.models.EmailStage.backend_class", "authentik.stages.email.models.EmailStage.backend_class",
@ -114,7 +114,7 @@ class TestEmailStage(FlowTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "authentik") self.assertEqual(mail.outbox[0].subject, "authentik")
self.assertEqual(mail.outbox[0].to, ["foo@bar.baz"]) self.assertEqual(mail.outbox[0].to, [f"{self.user.name} <foo@bar.baz>"])
@patch( @patch(
"authentik.stages.email.models.EmailStage.backend_class", "authentik.stages.email.models.EmailStage.backend_class",

View file

@ -4,6 +4,7 @@ from functools import lru_cache
from pathlib import Path from pathlib import Path
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.template.exceptions import TemplateDoesNotExist
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import translation from django.utils import translation
@ -24,9 +25,15 @@ class TemplateEmailMessage(EmailMultiAlternatives):
"""Wrapper around EmailMultiAlternatives with integrated template rendering""" """Wrapper around EmailMultiAlternatives with integrated template rendering"""
def __init__(self, template_name=None, template_context=None, language="", **kwargs): def __init__(self, template_name=None, template_context=None, language="", **kwargs):
super().__init__(**kwargs)
with translation.override(language): with translation.override(language):
html_content = render_to_string(template_name, template_context) html_content = render_to_string(template_name, template_context)
super().__init__(**kwargs) try:
self.content_subtype = "html" text_content = render_to_string(
template_name.replace("html", "txt"), template_context
)
self.body = text_content
except TemplateDoesNotExist:
pass
self.mixed_subtype = "related" self.mixed_subtype = "related"
self.attach_alternative(html_content, "text/html") self.attach_alternative(html_content, "text/html")

File diff suppressed because it is too large Load diff

View file

@ -32,7 +32,7 @@ services:
volumes: volumes:
- redis:/data - redis:/data
server: server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.4} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.5}
restart: unless-stopped restart: unless-stopped
command: server command: server
environment: environment:
@ -53,7 +53,7 @@ services:
- postgresql - postgresql
- redis - redis
worker: worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.4} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.5}
restart: unless-stopped restart: unless-stopped
command: worker command: worker
environment: environment:

4
go.mod
View file

@ -23,11 +23,11 @@ require (
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/pires/go-proxyproto v0.7.0 github.com/pires/go-proxyproto v0.7.0
github.com/prometheus/client_golang v1.17.0 github.com/prometheus/client_golang v1.17.0
github.com/redis/go-redis/v9 v9.3.0 github.com/redis/go-redis/v9 v9.3.1
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
goauthentik.io/api/v3 v3.2023104.3 goauthentik.io/api/v3 v3.2023104.5
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.15.0 golang.org/x/oauth2 v0.15.0
golang.org/x/sync v0.5.0 golang.org/x/sync v0.5.0

8
go.sum
View file

@ -256,8 +256,8 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0= github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds=
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
@ -309,8 +309,8 @@ go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYO
go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
goauthentik.io/api/v3 v3.2023104.3 h1:MzwdB21Q+G+wACEZiX0T1iVV4l7PjopjaVv6muqJE1M= goauthentik.io/api/v3 v3.2023104.5 h1:CWaQq44DxElyqMvZVjqMhWIr1vXzobf4eqFUas0lMgs=
goauthentik.io/api/v3 v3.2023104.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= goauthentik.io/api/v3 v3.2023104.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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View file

@ -29,4 +29,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion()) return fmt.Sprintf("authentik@%s", FullVersion())
} }
const VERSION = "2023.10.4" const VERSION = "2023.10.5"

View file

@ -113,7 +113,7 @@ filterwarnings = [
[tool.poetry] [tool.poetry]
name = "authentik" name = "authentik"
version = "2023.10.4" version = "2023.10.5"
description = "" description = ""
authors = ["authentik Team <hello@goauthentik.io>"] authors = ["authentik Team <hello@goauthentik.io>"]

View file

@ -1,7 +1,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: authentik title: authentik
version: 2023.10.4 version: 2023.10.5
description: Making authentication simple. description: Making authentication simple.
contact: contact:
email: hello@goauthentik.io email: hello@goauthentik.io
@ -17079,7 +17079,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Task' $ref: '#/components/schemas/SCIMSyncStatus'
description: '' description: ''
'404': '404':
description: Task not found description: Task not found
@ -28280,7 +28280,7 @@ components:
readOnly: true readOnly: true
geo_ip: geo_ip:
type: object type: object
description: Get parsed user agent description: Get GeoIP Data
properties: properties:
continent: continent:
type: string type: string
@ -28302,6 +28302,24 @@ components:
- long - long
nullable: true nullable: true
readOnly: true readOnly: true
asn:
type: object
description: Get ASN Data
properties:
asn:
type: integer
as_org:
type: string
nullable: true
network:
type: string
nullable: true
required:
- as_org
- asn
- network
nullable: true
readOnly: true
user: user:
type: integer type: integer
last_ip: last_ip:
@ -28316,6 +28334,7 @@ components:
type: string type: string
format: date-time format: date-time
required: required:
- asn
- current - current
- geo_ip - geo_ip
- last_ip - last_ip
@ -29283,6 +29302,7 @@ components:
enum: enum:
- can_save_media - can_save_media
- can_geo_ip - can_geo_ip
- can_asn
- can_impersonate - can_impersonate
- can_debug - can_debug
- is_enterprise - is_enterprise
@ -29290,6 +29310,7 @@ components:
description: |- description: |-
* `can_save_media` - Can Save Media * `can_save_media` - Can Save Media
* `can_geo_ip` - Can Geo Ip * `can_geo_ip` - Can Geo Ip
* `can_asn` - Can Asn
* `can_impersonate` - Can Impersonate * `can_impersonate` - Can Impersonate
* `can_debug` - Can Debug * `can_debug` - Can Debug
* `is_enterprise` - Is Enterprise * `is_enterprise` - Is Enterprise
@ -39667,6 +39688,7 @@ components:
ip: ip:
type: string type: string
ip_geo_data: {} ip_geo_data: {}
ip_asn_data: {}
score: score:
type: integer type: integer
maximum: 9223372036854775807 maximum: 9223372036854775807
@ -40623,6 +40645,21 @@ components:
- name - name
- token - token
- url - url
SCIMSyncStatus:
type: object
description: SCIM Provider sync status
properties:
is_running:
type: boolean
readOnly: true
tasks:
type: array
items:
$ref: '#/components/schemas/Task'
readOnly: true
required:
- is_running
- tasks
SMSDevice: SMSDevice:
type: object type: object
description: Serializer for sms authenticator devices description: Serializer for sms authenticator devices

View file

@ -17,7 +17,12 @@ with open("local.env.yml", "w", encoding="utf-8") as _config:
}, },
"blueprints_dir": "./blueprints", "blueprints_dir": "./blueprints",
"cert_discovery_dir": "./certs", "cert_discovery_dir": "./certs",
"geoip": "tests/GeoLite2-City-Test.mmdb", "events": {
"processors": {
"geoip": "tests/GeoLite2-City-Test.mmdb",
"asn": "tests/GeoLite2-ASN-Test.mmdb",
}
},
}, },
_config, _config,
default_flow_style=False, default_flow_style=False,

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -9,10 +9,10 @@
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0", "@typescript-eslint/parser": "^6.15.0",
"@wdio/cli": "^8.26.3", "@wdio/cli": "^8.27.0",
"@wdio/local-runner": "^8.26.3", "@wdio/local-runner": "^8.27.0",
"@wdio/mocha-framework": "^8.26.3", "@wdio/mocha-framework": "^8.27.0",
"@wdio/spec-reporter": "^8.26.3", "@wdio/spec-reporter": "^8.27.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-sonarjs": "^0.23.0",
@ -1141,18 +1141,18 @@
"dev": true "dev": true
}, },
"node_modules/@wdio/cli": { "node_modules/@wdio/cli": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.26.3.tgz", "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.27.0.tgz",
"integrity": "sha512-wrq145sNBw4DrsF5GEK8TBxqVWln7GZpNpM5QeDqCcZzVHIqDud4f7nADgZGbR8dJ96NVfC3rby6wbeRQUA+eg==", "integrity": "sha512-wdNYNvu52XxOqNHqDMGAtexBz+MM0RE2Z5U5ljyllbP3ed5vcvvK9vswURtI4cFGoqobVeoC7wif3VeD3aN+aQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.1", "@types/node": "^20.1.1",
"@wdio/config": "8.26.3", "@wdio/config": "8.27.0",
"@wdio/globals": "8.26.3", "@wdio/globals": "8.27.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/protocols": "8.24.12", "@wdio/protocols": "8.24.12",
"@wdio/types": "8.26.3", "@wdio/types": "8.27.0",
"@wdio/utils": "8.26.3", "@wdio/utils": "8.27.0",
"async-exit-hook": "^2.0.1", "async-exit-hook": "^2.0.1",
"chalk": "^5.2.0", "chalk": "^5.2.0",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
@ -1167,7 +1167,7 @@
"lodash.union": "^4.6.0", "lodash.union": "^4.6.0",
"read-pkg-up": "^10.0.0", "read-pkg-up": "^10.0.0",
"recursive-readdir": "^2.2.3", "recursive-readdir": "^2.2.3",
"webdriverio": "8.26.3", "webdriverio": "8.27.0",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"bin": { "bin": {
@ -1190,14 +1190,14 @@
} }
}, },
"node_modules/@wdio/config": { "node_modules/@wdio/config": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.26.3.tgz", "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.27.0.tgz",
"integrity": "sha512-NWh2JXRSyP4gY+jeC79u0L3hSXW/s3rOWez4M6qAglT91fZTXFbIl1GM8lnZlCq03ye2qFPqYrZ+4tGNQj7YxQ==", "integrity": "sha512-zYM5daeiBVVAbQj0ASymAt0RUsocLVIwKiUHNa8gg/1GsZnztGjetXExSp1gXlxtMVM5xWUSKjh6ceFK79gWDQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/types": "8.26.3", "@wdio/types": "8.27.0",
"@wdio/utils": "8.26.3", "@wdio/utils": "8.27.0",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"deepmerge-ts": "^5.0.0", "deepmerge-ts": "^5.0.0",
"glob": "^10.2.2", "glob": "^10.2.2",
@ -1208,29 +1208,29 @@
} }
}, },
"node_modules/@wdio/globals": { "node_modules/@wdio/globals": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.26.3.tgz", "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.27.0.tgz",
"integrity": "sha512-RW3UsvnUb4DjxVOqIngXQMcDJlbH+QL/LeChznUF0FW+Mqg/mZWukBld5/dDwgQHk9F2TOzc8ctk5FM3s1AoWQ==", "integrity": "sha512-HUPOIsrmxfF0LhU68lVsNGQGZkW/bWOvcCd8WxeaggTAH9JyxasxxfwzeCceAuhAvwtlwoMXITOpjAXO2mj38Q==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"
}, },
"optionalDependencies": { "optionalDependencies": {
"expect-webdriverio": "^4.6.1", "expect-webdriverio": "^4.6.1",
"webdriverio": "8.26.3" "webdriverio": "8.27.0"
} }
}, },
"node_modules/@wdio/local-runner": { "node_modules/@wdio/local-runner": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.26.3.tgz", "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.27.0.tgz",
"integrity": "sha512-YWxTBp6tc8Dlz09rnRhV2GXV4b3w5G0WyYEf81D+kXDI3QxDvYn6QujByr6Q7fQ9yLwJU4ptnT2uL5IbAwVo2A==", "integrity": "sha512-nxS17mhoLkXP20eoPMkz7tbMFMOQejSw0hZfkEvuDCNhJokr8ugp6IjYXL9f7yV9IB9UDGHox8WGY4ArSrOeBA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/repl": "8.24.12", "@wdio/repl": "8.24.12",
"@wdio/runner": "8.26.3", "@wdio/runner": "8.27.0",
"@wdio/types": "8.26.3", "@wdio/types": "8.27.0",
"async-exit-hook": "^2.0.1", "async-exit-hook": "^2.0.1",
"split2": "^4.1.0", "split2": "^4.1.0",
"stream-buffers": "^3.0.2" "stream-buffers": "^3.0.2"
@ -1267,16 +1267,16 @@
} }
}, },
"node_modules/@wdio/mocha-framework": { "node_modules/@wdio/mocha-framework": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.26.3.tgz", "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.27.0.tgz",
"integrity": "sha512-r9uHUcXhh6TKFqTBCacnbtQx0nZjFsnV9Pony8CY9qcNCn2q666ucQbrtMfZdjuevhn5N0E710+El4eAvK3jyw==", "integrity": "sha512-NaFUPv90ks1XlZy0qdUaJ5/ilBtiCCgTIxaPexshJiaVDT5cV+Igjag/O80HIcvqknOZpdKAR0I1ArQzhJrmcA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/mocha": "^10.0.0", "@types/mocha": "^10.0.0",
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/types": "8.26.3", "@wdio/types": "8.27.0",
"@wdio/utils": "8.26.3", "@wdio/utils": "8.27.0",
"mocha": "^10.0.0" "mocha": "^10.0.0"
}, },
"engines": { "engines": {
@ -1302,14 +1302,14 @@
} }
}, },
"node_modules/@wdio/reporter": { "node_modules/@wdio/reporter": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.26.3.tgz", "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.27.0.tgz",
"integrity": "sha512-F/sF1Hwxp1osM2wto4JydONQGxqkbOhwbLM0o4Y8eHPgK7/m+Kn9uygBDqPVpgQnpf0kUfhpICe9gaZzG4Jt+g==", "integrity": "sha512-kBwsrHbsblmXfHSWlaOKXjPRPeT29WSKTUoCmzuTcCkhvbjY4TrEB0p04cpaM7uNqdIZTxHng54gZVaG/nZPiw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/types": "8.26.3", "@wdio/types": "8.27.0",
"diff": "^5.0.0", "diff": "^5.0.0",
"object-inspect": "^1.12.0" "object-inspect": "^1.12.0"
}, },
@ -1318,35 +1318,35 @@
} }
}, },
"node_modules/@wdio/runner": { "node_modules/@wdio/runner": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.26.3.tgz", "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.27.0.tgz",
"integrity": "sha512-mbZGkBbXTRtj1hL5QUbNxpJvhE4rkXvYlUuea1uOVk3e2/+k2dZeGeKPgh1Q7Dt07118dfujCB7pQCYldE/dGg==", "integrity": "sha512-da332r2d1QXdRhMhsDxMObcqLZS0l/u14pHICNTvEHp+72gOttbjUDvdMHPQY6Ae5ul7AVVQ05qpmz9CX7TzOg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/config": "8.26.3", "@wdio/config": "8.27.0",
"@wdio/globals": "8.26.3", "@wdio/globals": "8.27.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/types": "8.26.3", "@wdio/types": "8.27.0",
"@wdio/utils": "8.26.3", "@wdio/utils": "8.27.0",
"deepmerge-ts": "^5.0.0", "deepmerge-ts": "^5.0.0",
"expect-webdriverio": "^4.6.1", "expect-webdriverio": "^4.6.1",
"gaze": "^1.1.2", "gaze": "^1.1.2",
"webdriver": "8.26.3", "webdriver": "8.27.0",
"webdriverio": "8.26.3" "webdriverio": "8.27.0"
}, },
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"
} }
}, },
"node_modules/@wdio/spec-reporter": { "node_modules/@wdio/spec-reporter": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.26.3.tgz", "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.27.0.tgz",
"integrity": "sha512-YfKlBOmxGyJk08BpDnsjPsp85XyhG7Cu2qoAVxtJ8kkJOZaGfUg9TBV9DXDqvdZcxCMnPfDfQIda6LzfkZf58Q==", "integrity": "sha512-EOXLBIr4oLzSDp/BQ86IqCulSF0jwEAj2EiMeY6dh9WXzBBtoR8WnoX/27xFoZ8GU2zetWC3EVnLJ0Ex8Up1mA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@wdio/reporter": "8.26.3", "@wdio/reporter": "8.27.0",
"@wdio/types": "8.26.3", "@wdio/types": "8.27.0",
"chalk": "^5.1.2", "chalk": "^5.1.2",
"easy-table": "^1.2.0", "easy-table": "^1.2.0",
"pretty-ms": "^7.0.0" "pretty-ms": "^7.0.0"
@ -1368,9 +1368,9 @@
} }
}, },
"node_modules/@wdio/types": { "node_modules/@wdio/types": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.26.3.tgz", "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.27.0.tgz",
"integrity": "sha512-WOxvSV4sKJ5QCRNTJHeKCzgO2TZmcK1eDlJ+FObt9Pnt+4pCRy/881eVY/Aj2bozn2hhzq0AK/h6oPAUV/gjCg==", "integrity": "sha512-LbP9FKh8r0uW9/dKhTIUCC1Su8PsP9TmzGKXkWt6/IMacgJiB/zW3u1CgyaLw9lG0UiQORHGoeJX9zB2HZAh4w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0" "@types/node": "^20.1.0"
@ -1380,14 +1380,14 @@
} }
}, },
"node_modules/@wdio/utils": { "node_modules/@wdio/utils": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.26.3.tgz", "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.27.0.tgz",
"integrity": "sha512-LA/iCKgJQemAAXoN6vHyKBtngdkFUWEnjB8Yd1Xm3gUQTvY4GVlvcqOxC2RF5Th7/L2LNxc6TWuErYv/mm5H+w==", "integrity": "sha512-4BY+JBQssVn003P5lA289uDMie3LtGinHze5btkcW9timB6VaU+EeZS4eKTPC0pziizLhteVvXYxv3YTpeeRfA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@puppeteer/browsers": "^1.6.0", "@puppeteer/browsers": "^1.6.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/types": "8.26.3", "@wdio/types": "8.27.0",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"deepmerge-ts": "^5.1.0", "deepmerge-ts": "^5.1.0",
"edgedriver": "^5.3.5", "edgedriver": "^5.3.5",
@ -8495,18 +8495,18 @@
} }
}, },
"node_modules/webdriver": { "node_modules/webdriver": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.26.3.tgz", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.27.0.tgz",
"integrity": "sha512-vHbMj0BFXPMtKVmJsVIkFVbdOT8eXkjFeJ7LmJL8cMMe1S5Lt44DqRjSBBoGsqYoYgIBmKpqAQcDrHrv9m7smQ==", "integrity": "sha512-n1IA+rR3u84XxU9swiKUM06BkEC0GDimfZkBML57cny+utQOUbdM/mBpqCUnkWX/RBz/p2EfHdKNyOs3/REaog==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"@wdio/config": "8.26.3", "@wdio/config": "8.27.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/protocols": "8.24.12", "@wdio/protocols": "8.24.12",
"@wdio/types": "8.26.3", "@wdio/types": "8.27.0",
"@wdio/utils": "8.26.3", "@wdio/utils": "8.27.0",
"deepmerge-ts": "^5.1.0", "deepmerge-ts": "^5.1.0",
"got": "^12.6.1", "got": "^12.6.1",
"ky": "^0.33.0", "ky": "^0.33.0",
@ -8517,18 +8517,18 @@
} }
}, },
"node_modules/webdriverio": { "node_modules/webdriverio": {
"version": "8.26.3", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.26.3.tgz", "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.27.0.tgz",
"integrity": "sha512-5Ka8MOQoK866EI3whiCvzD1IiKFBq9niWF3lh92uMt6ZjbUZZoe5esWIHhFsHFxT6dOOU8uXR/Gr6qsBJFZReA==", "integrity": "sha512-Qh5VCiBjEmxnmXcL1QEFoDzFqTtaWKrXriuU5G0yHKCModGAt2G7IHTkAok3CpmkVJfZpEvY630aP1MvgDtFhw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/config": "8.26.3", "@wdio/config": "8.27.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/protocols": "8.24.12", "@wdio/protocols": "8.24.12",
"@wdio/repl": "8.24.12", "@wdio/repl": "8.24.12",
"@wdio/types": "8.26.3", "@wdio/types": "8.27.0",
"@wdio/utils": "8.26.3", "@wdio/utils": "8.27.0",
"archiver": "^6.0.0", "archiver": "^6.0.0",
"aria-query": "^5.0.0", "aria-query": "^5.0.0",
"css-shorthand-properties": "^1.1.1", "css-shorthand-properties": "^1.1.1",
@ -8545,7 +8545,7 @@
"resq": "^1.9.1", "resq": "^1.9.1",
"rgb2hex": "0.2.5", "rgb2hex": "0.2.5",
"serialize-error": "^11.0.1", "serialize-error": "^11.0.1",
"webdriver": "8.26.3" "webdriver": "8.27.0"
}, },
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"

View file

@ -6,10 +6,10 @@
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0", "@typescript-eslint/parser": "^6.15.0",
"@wdio/cli": "^8.26.3", "@wdio/cli": "^8.27.0",
"@wdio/local-runner": "^8.26.3", "@wdio/local-runner": "^8.27.0",
"@wdio/mocha-framework": "^8.26.3", "@wdio/mocha-framework": "^8.27.0",
"@wdio/spec-reporter": "^8.26.3", "@wdio/spec-reporter": "^8.27.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-sonarjs": "^0.23.0",

482
web/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@
"lage": "^2.7.9" "lage": "^2.7.9"
}, },
"workspaces": [ "workspaces": [
"packages/web" "packages/monolith"
], ],
"engines": { "engines": {
"node": ">=20" "node": ">=20"

View file

Before

Width:  |  Height:  |  Size: 663 B

After

Width:  |  Height:  |  Size: 663 B

View file

Before

Width:  |  Height:  |  Size: 254 B

After

Width:  |  Height:  |  Size: 254 B

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

Before

Width:  |  Height:  |  Size: 351 B

After

Width:  |  Height:  |  Size: 351 B

View file

Before

Width:  |  Height:  |  Size: 402 B

After

Width:  |  Height:  |  Size: 402 B

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 543 B

After

Width:  |  Height:  |  Size: 543 B

View file

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 368 B

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 878 B

After

Width:  |  Height:  |  Size: 878 B

View file

Before

Width:  |  Height:  |  Size: 587 B

After

Width:  |  Height:  |  Size: 587 B

View file

Before

Width:  |  Height:  |  Size: 175 B

After

Width:  |  Height:  |  Size: 175 B

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

Before

Width:  |  Height:  |  Size: 887 B

After

Width:  |  Height:  |  Size: 887 B

View file

Before

Width:  |  Height:  |  Size: 732 B

After

Width:  |  Height:  |  Size: 732 B

View file

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View file

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

View file

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 5 KiB

View file

@ -1,5 +1,5 @@
{ {
"name": "@goauthentik/web", "name": "@goauthentik/monolith",
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
@ -16,7 +16,7 @@
"watch": "run-s build-locales rollup:watch", "watch": "run-s build-locales rollup:watch",
"lint": "eslint . --max-warnings 0 --fix", "lint": "eslint . --max-warnings 0 --fix",
"lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain . | grep '^[M?][M?]' | cut -c8- | grep -E '\\.(ts|js|tsx|jsx)$') ", "lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain . | grep '^[M?][M?]' | cut -c8- | grep -E '\\.(ts|js|tsx|jsx)$') ",
"lint:spelling": "codespell -D - -D ../.github/codespell-dictionary.txt -I ../.github/codespell-words.txt -S './src/locales/**' ./src -s", "lint:spelling": "codespell -D - -D ../../../.github/codespell-dictionary.txt -I ../../../.github/codespell-words.txt -S './src/locales/**' ./src -s",
"lit-analyse": "lit-analyzer src", "lit-analyse": "lit-analyzer src",
"precommit": "run-s tsc lit-analyse lint:precommit lint:spelling prettier", "precommit": "run-s tsc lit-analyse lint:precommit lint:spelling prettier",
"prequick": "run-s tsc:execute lit-analyse lint:precommit lint:spelling", "prequick": "run-s tsc:execute lit-analyse lint:precommit lint:spelling",
@ -42,15 +42,15 @@
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.3", "@formatjs/intl-listformat": "^7.5.3",
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2023.10.4-1702989148", "@goauthentik/api": "^2023.10.5-1703167718",
"@lit-labs/context": "^0.4.0", "@lit-labs/context": "^0.4.0",
"@lit-labs/task": "^3.1.0", "@lit-labs/task": "^3.1.0",
"@lit/localize": "^0.11.4", "@lit/localize": "^0.11.4",
"@open-wc/lit-helpers": "^0.6.0", "@open-wc/lit-helpers": "^0.6.0",
"@patternfly/elements": "^2.4.0", "@patternfly/elements": "^2.4.0",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^7.88.0", "@sentry/browser": "^7.90.0",
"@sentry/tracing": "^7.88.0", "@sentry/tracing": "^7.90.0",
"@webcomponents/webcomponentsjs": "^2.8.0", "@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^4.4.1", "chart.js": "^4.4.1",
@ -86,13 +86,13 @@
"@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5", "@rollup/plugin-typescript": "^11.1.5",
"@storybook/addon-essentials": "^7.6.5", "@storybook/addon-essentials": "^7.6.6",
"@storybook/addon-links": "^7.6.5", "@storybook/addon-links": "^7.6.6",
"@storybook/api": "^7.6.5", "@storybook/api": "^7.6.6",
"@storybook/blocks": "^7.6.4", "@storybook/blocks": "^7.6.4",
"@storybook/manager-api": "^7.6.5", "@storybook/manager-api": "^7.6.6",
"@storybook/web-components": "^7.6.5", "@storybook/web-components": "^7.6.6",
"@storybook/web-components-vite": "^7.6.5", "@storybook/web-components-vite": "^7.6.6",
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41", "@types/chart.js": "^2.9.41",
"@types/codemirror": "5.60.15", "@types/codemirror": "5.60.15",
@ -120,7 +120,7 @@
"rollup-plugin-cssimport": "^1.0.3", "rollup-plugin-cssimport": "^1.0.3",
"rollup-plugin-modify": "^3.0.0", "rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0", "rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^7.6.5", "storybook": "^7.6.6",
"storybook-addon-mock": "^4.3.0", "storybook-addon-mock": "^4.3.0",
"ts-lit-plugin": "^2.0.1", "ts-lit-plugin": "^2.0.1",
"tslib": "^2.6.2", "tslib": "^2.6.2",

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