Compare commits
55 Commits
version/20
...
version/20
Author | SHA1 | Date | |
---|---|---|---|
9201fc1834 | |||
5385feb428 | |||
db557401aa | |||
c824af5bc3 | |||
1faba11a57 | |||
f0c72e8536 | |||
91f91b08e5 | |||
8faa909c32 | |||
49142fa80b | |||
2a6fccd22a | |||
1d10afa209 | |||
4b7c3c38cd | |||
440cacbafe | |||
b33bff92ee | |||
caed306346 | |||
d0eb6af7e9 | |||
ec5ed67f6c | |||
59b899ddff | |||
85784f796c | |||
4c0e19cbea | |||
b42eb9464f | |||
6559fdee15 | |||
3455bf3d27 | |||
ff2baf502b | |||
3b182ca223 | |||
8da8890a8e | |||
23023ec727 | |||
7d84a71a01 | |||
192001f193 | |||
63734682d2 | |||
a0cd2d55f8 | |||
a72c7adfc0 | |||
e88e02ec85 | |||
f7661c8bbd | |||
9add8479ca | |||
4c39e08dd4 | |||
44ce2ebece | |||
f5a8859d00 | |||
9ef0e8bc5f | |||
60eeafd111 | |||
6f3d6efa22 | |||
8d3275817b | |||
ca40d31dac | |||
438aac8879 | |||
2dfa6c2c82 | |||
c11435780d | |||
ee54328589 | |||
817d538b8f | |||
210775776f | |||
2a4ce75bc4 | |||
b26111fb42 | |||
e30103aa9f | |||
dc9203789e | |||
d70ce2776f | |||
ad7d65e903 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2022.6.1
|
||||
current_version = 2022.6.3
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
||||
|
2
.github/workflows/ci-outpost.yml
vendored
2
.github/workflows/ci-outpost.yml
vendored
@ -110,7 +110,7 @@ jobs:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "^1.17"
|
||||
- uses: actions/setup-node@v3.2.0
|
||||
- uses: actions/setup-node@v3.3.0
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
|
8
.github/workflows/ci-web.yml
vendored
8
.github/workflows/ci-web.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3.2.0
|
||||
- uses: actions/setup-node@v3.3.0
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3.2.0
|
||||
- uses: actions/setup-node@v3.3.0
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
@ -47,7 +47,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3.2.0
|
||||
- uses: actions/setup-node@v3.3.0
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
@ -73,7 +73,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3.2.0
|
||||
- uses: actions/setup-node@v3.3.0
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
|
2
.github/workflows/ci-website.yml
vendored
2
.github/workflows/ci-website.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3.2.0
|
||||
- uses: actions/setup-node@v3.3.0
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
|
12
.github/workflows/release-publish.yml
vendored
12
.github/workflows/release-publish.yml
vendored
@ -30,9 +30,9 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik:2022.6.1,
|
||||
beryju/authentik:2022.6.3,
|
||||
beryju/authentik:latest,
|
||||
ghcr.io/goauthentik/server:2022.6.1,
|
||||
ghcr.io/goauthentik/server:2022.6.3,
|
||||
ghcr.io/goauthentik/server:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
@ -69,9 +69,9 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-${{ matrix.type }}:2022.6.1,
|
||||
beryju/authentik-${{ matrix.type }}:2022.6.3,
|
||||
beryju/authentik-${{ matrix.type }}:latest,
|
||||
ghcr.io/goauthentik/${{ matrix.type }}:2022.6.1,
|
||||
ghcr.io/goauthentik/${{ matrix.type }}:2022.6.3,
|
||||
ghcr.io/goauthentik/${{ matrix.type }}:latest
|
||||
file: ${{ matrix.type }}.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@ -91,7 +91,7 @@ jobs:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "^1.17"
|
||||
- uses: actions/setup-node@v3.2.0
|
||||
- uses: actions/setup-node@v3.3.0
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
@ -152,7 +152,7 @@ jobs:
|
||||
SENTRY_PROJECT: authentik
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
with:
|
||||
version: authentik@2022.6.1
|
||||
version: authentik@2022.6.3
|
||||
environment: beryjuorg-prod
|
||||
sourcemaps: './web/dist'
|
||||
url_prefix: '~/static/dist'
|
||||
|
2
.github/workflows/web-api-publish.yml
vendored
2
.github/workflows/web-api-publish.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@v3.2.0
|
||||
- uses: actions/setup-node@v3.3.0
|
||||
with:
|
||||
node-version: '16'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
3
Makefile
3
Makefile
@ -103,6 +103,9 @@ run:
|
||||
## Web
|
||||
#########################
|
||||
|
||||
web-build: web-install
|
||||
cd web && npm run build
|
||||
|
||||
web: web-lint-fix web-lint web-extract
|
||||
|
||||
web-install:
|
||||
|
@ -2,7 +2,7 @@
|
||||
from os import environ
|
||||
from typing import Optional
|
||||
|
||||
__version__ = "2022.6.1"
|
||||
__version__ = "2022.6.3"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -63,6 +63,7 @@ class ApplicationSerializer(ModelSerializer):
|
||||
"provider",
|
||||
"provider_obj",
|
||||
"launch_url",
|
||||
"open_in_new_tab",
|
||||
"meta_launch_url",
|
||||
"meta_icon",
|
||||
"meta_description",
|
||||
|
@ -8,7 +8,7 @@ from rest_framework.decorators import action
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||
from rest_framework.serializers import ModelSerializer, ReadOnlyField, SerializerMethodField
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
@ -26,6 +26,7 @@ LOGGER = get_logger()
|
||||
class SourceSerializer(ModelSerializer, MetaNameSerializer):
|
||||
"""Source Serializer"""
|
||||
|
||||
managed = ReadOnlyField()
|
||||
component = SerializerMethodField()
|
||||
|
||||
def get_component(self, obj: Source) -> str:
|
||||
@ -51,6 +52,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
|
||||
"meta_model_name",
|
||||
"policy_engine_mode",
|
||||
"user_matching_mode",
|
||||
"managed",
|
||||
]
|
||||
|
||||
|
||||
@ -67,6 +69,7 @@ class SourceViewSet(
|
||||
serializer_class = SourceSerializer
|
||||
lookup_field = "slug"
|
||||
search_fields = ["slug", "name"]
|
||||
filterset_fields = ["slug", "name", "managed"]
|
||||
|
||||
def get_queryset(self): # pragma: no cover
|
||||
return Source.objects.select_subclasses()
|
||||
|
@ -12,5 +12,6 @@ class CoreManager(ObjectManager):
|
||||
Source,
|
||||
"goauthentik.io/sources/inbuilt",
|
||||
name="authentik Built-in",
|
||||
slug="authentik-built-in",
|
||||
),
|
||||
]
|
||||
|
13
authentik/core/management/commands/bootstrap_tasks.py
Normal file
13
authentik/core/management/commands/bootstrap_tasks.py
Normal file
@ -0,0 +1,13 @@
|
||||
"""Run bootstrap tasks"""
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from authentik.root.celery import _get_startup_tasks
|
||||
|
||||
|
||||
class Command(BaseCommand): # pragma: no cover
|
||||
"""Run bootstrap tasks to ensure certain objects are created"""
|
||||
|
||||
def handle(self, **options):
|
||||
tasks = _get_startup_tasks()
|
||||
for task in tasks:
|
||||
task()
|
@ -36,8 +36,10 @@ def fix_duplicates(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
|
||||
|
||||
def create_default_user_token(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
# We have to use a direct import here, otherwise we get an object manager error
|
||||
from authentik.core.models import Token, TokenIntents, User
|
||||
from authentik.core.models import TokenIntents
|
||||
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
Token = apps.get_model("authentik_core", "Token")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
|
@ -0,0 +1,20 @@
|
||||
# Generated by Django 4.0.5 on 2022-06-04 06:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0019_application_group"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="application",
|
||||
name="open_in_new_tab",
|
||||
field=models.BooleanField(
|
||||
default=False, help_text="Open launch URL in a new browser tab or window."
|
||||
),
|
||||
),
|
||||
]
|
@ -7,8 +7,10 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def create_default_user_token(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
# We have to use a direct import here, otherwise we get an object manager error
|
||||
from authentik.core.models import Token, TokenIntents, User
|
||||
from authentik.core.models import TokenIntents
|
||||
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
Token = apps.get_model("authentik_core", "Token")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
|
@ -278,6 +278,11 @@ class Application(PolicyBindingModel):
|
||||
meta_launch_url = models.TextField(
|
||||
default="", blank=True, validators=[DomainlessURLValidator()]
|
||||
)
|
||||
|
||||
open_in_new_tab = models.BooleanField(
|
||||
default=False, help_text=_("Open launch URL in a new browser tab or window.")
|
||||
)
|
||||
|
||||
# For template applications, this can be set to /static/authentik/applications/*
|
||||
meta_icon = models.FileField(
|
||||
upload_to="application-icons/",
|
||||
|
@ -29,6 +29,7 @@ class TestApplicationsAPI(APITestCase):
|
||||
name="allowed",
|
||||
slug="allowed",
|
||||
meta_launch_url="https://goauthentik.io/%(username)s",
|
||||
open_in_new_tab=True,
|
||||
provider=self.provider,
|
||||
)
|
||||
self.denied = Application.objects.create(name="denied", slug="denied")
|
||||
@ -100,6 +101,7 @@ class TestApplicationsAPI(APITestCase):
|
||||
},
|
||||
"launch_url": f"https://goauthentik.io/{self.user.username}",
|
||||
"meta_launch_url": "https://goauthentik.io/%(username)s",
|
||||
"open_in_new_tab": True,
|
||||
"meta_icon": None,
|
||||
"meta_description": "",
|
||||
"meta_publisher": "",
|
||||
@ -148,6 +150,7 @@ class TestApplicationsAPI(APITestCase):
|
||||
},
|
||||
"launch_url": f"https://goauthentik.io/{self.user.username}",
|
||||
"meta_launch_url": "https://goauthentik.io/%(username)s",
|
||||
"open_in_new_tab": True,
|
||||
"meta_icon": None,
|
||||
"meta_description": "",
|
||||
"meta_publisher": "",
|
||||
@ -158,6 +161,7 @@ class TestApplicationsAPI(APITestCase):
|
||||
"meta_description": "",
|
||||
"meta_icon": None,
|
||||
"meta_launch_url": "",
|
||||
"open_in_new_tab": False,
|
||||
"meta_publisher": "",
|
||||
"group": "",
|
||||
"name": "denied",
|
||||
|
@ -47,11 +47,11 @@ def create_test_tenant() -> Tenant:
|
||||
|
||||
def create_test_cert() -> CertificateKeyPair:
|
||||
"""Generate a certificate for testing"""
|
||||
CertificateKeyPair.objects.filter(name="goauthentik.io").delete()
|
||||
builder = CertificateBuilder()
|
||||
builder.common_name = "goauthentik.io"
|
||||
builder.build(
|
||||
subject_alt_names=["goauthentik.io"],
|
||||
validity_days=360,
|
||||
)
|
||||
builder.name = generate_id()
|
||||
return builder.save()
|
||||
|
@ -53,10 +53,7 @@ class CertificateBuilder:
|
||||
.subject_name(
|
||||
x509.Name(
|
||||
[
|
||||
x509.NameAttribute(
|
||||
NameOID.COMMON_NAME,
|
||||
self.common_name,
|
||||
),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, self.common_name),
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "authentik"),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Self-signed"),
|
||||
]
|
||||
@ -65,10 +62,7 @@ class CertificateBuilder:
|
||||
.issuer_name(
|
||||
x509.Name(
|
||||
[
|
||||
x509.NameAttribute(
|
||||
NameOID.COMMON_NAME,
|
||||
f"authentik {__version__}",
|
||||
),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, f"authentik {__version__}"),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
@ -76,11 +76,8 @@ class GeoIPReader:
|
||||
except (GeoIP2Error, ValueError):
|
||||
return None
|
||||
|
||||
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
|
||||
def city_to_dict(self, city: City) -> GeoIPDict:
|
||||
"""Convert City to dict"""
|
||||
city_dict: GeoIPDict = {
|
||||
"continent": city.continent.code,
|
||||
"country": city.country.iso_code,
|
||||
@ -92,5 +89,12 @@ class GeoIPReader:
|
||||
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()
|
||||
|
@ -10,9 +10,11 @@ from django.db import models
|
||||
from django.db.models.base import Model
|
||||
from django.http.request import HttpRequest
|
||||
from django.views.debug import SafeExceptionReporterFilter
|
||||
from geoip2.models import City
|
||||
from guardian.utils import get_anonymous_user
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.events.geo import GEOIP_READER
|
||||
from authentik.policies.types import PolicyRequest
|
||||
|
||||
# Special keys which are *not* cleaned, even when the default filter
|
||||
@ -93,6 +95,8 @@ def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]:
|
||||
final_dict[key] = value.hex
|
||||
elif isinstance(value, (HttpRequest, WSGIRequest)):
|
||||
continue
|
||||
elif isinstance(value, City):
|
||||
final_dict[key] = GEOIP_READER.city_to_dict(value)
|
||||
elif isinstance(value, type):
|
||||
final_dict[key] = {
|
||||
"type": value.__name__,
|
||||
|
@ -56,7 +56,6 @@ def sentry_init(**sentry_init_kwargs):
|
||||
"""Configure sentry SDK"""
|
||||
sentry_env = CONFIG.y("error_reporting.environment", "customer")
|
||||
kwargs = {
|
||||
"traces_sample_rate": float(CONFIG.y("error_reporting.sample_rate", 0.5)),
|
||||
"environment": sentry_env,
|
||||
"send_default_pii": CONFIG.y_bool("error_reporting.send_pii", False),
|
||||
}
|
||||
@ -71,6 +70,7 @@ def sentry_init(**sentry_init_kwargs):
|
||||
ThreadingIntegration(propagate_hub=True),
|
||||
],
|
||||
before_send=before_send,
|
||||
traces_sampler=traces_sampler,
|
||||
release=f"authentik@{__version__}",
|
||||
**kwargs,
|
||||
)
|
||||
@ -83,6 +83,15 @@ def sentry_init(**sentry_init_kwargs):
|
||||
)
|
||||
|
||||
|
||||
def traces_sampler(sampling_context: dict) -> float:
|
||||
"""Custom sampler to ignore certain routes"""
|
||||
path = sampling_context.get("asgi_scope", {}).get("path", "")
|
||||
# Ignore all healthcheck routes
|
||||
if path.startswith("/-/health") or path.startswith("/-/metrics"):
|
||||
return 0
|
||||
return float(CONFIG.y("error_reporting.sample_rate", 0.5))
|
||||
|
||||
|
||||
def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
"""Check if error is database error, and ignore if so"""
|
||||
# pylint: disable=no-name-in-module
|
||||
|
@ -88,7 +88,7 @@ class PolicyProcess(PROCESS_CLASS):
|
||||
LOGGER.debug(
|
||||
"P_ENG(proc): Running policy",
|
||||
policy=self.binding.policy,
|
||||
user=self.request.user,
|
||||
user=self.request.user.username,
|
||||
# this is used for filtering in access checking where logs are sent to the admin
|
||||
process="PolicyProcess",
|
||||
)
|
||||
@ -124,7 +124,7 @@ class PolicyProcess(PROCESS_CLASS):
|
||||
# this is used for filtering in access checking where logs are sent to the admin
|
||||
process="PolicyProcess",
|
||||
passing=policy_result.passing,
|
||||
user=self.request.user,
|
||||
user=self.request.user.username,
|
||||
)
|
||||
return policy_result
|
||||
|
||||
|
@ -117,8 +117,8 @@ class PolicyAccessView(AccessMixin, View):
|
||||
result = policy_engine.result
|
||||
LOGGER.debug(
|
||||
"PolicyAccessView user_has_access",
|
||||
user=user,
|
||||
app=self.application,
|
||||
user=user.username,
|
||||
app=self.application.slug,
|
||||
result=result,
|
||||
)
|
||||
if not result.passing:
|
||||
|
@ -48,6 +48,7 @@ class OAuth2ProviderSetupURLs(PassiveSerializer):
|
||||
user_info = CharField(read_only=True)
|
||||
provider_info = CharField(read_only=True)
|
||||
logout = CharField(read_only=True)
|
||||
jwks = CharField(read_only=True)
|
||||
|
||||
|
||||
class OAuth2ProviderViewSet(UsedByMixin, ModelViewSet):
|
||||
@ -119,6 +120,12 @@ class OAuth2ProviderViewSet(UsedByMixin, ModelViewSet):
|
||||
kwargs={"application_slug": provider.application.slug},
|
||||
)
|
||||
)
|
||||
data["jwks"] = request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_providers_oauth2:jwks",
|
||||
kwargs={"application_slug": provider.application.slug},
|
||||
)
|
||||
)
|
||||
except Provider.application.RelatedObjectDoesNotExist: # pylint: disable=no-member
|
||||
pass
|
||||
return Response(data)
|
||||
|
@ -3,7 +3,7 @@ from django.test import RequestFactory
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.flows.challenge import ChallengeTypes
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
from authentik.providers.oauth2.errors import AuthorizeError, ClientIdError, RedirectUriError
|
||||
@ -39,7 +39,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
def test_request(self):
|
||||
"""test request param"""
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid/Foo",
|
||||
@ -59,7 +59,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
def test_invalid_redirect_uri(self):
|
||||
"""test missing/invalid redirect URI"""
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
@ -78,10 +78,55 @@ class TestAuthorize(OAuthTestCase):
|
||||
)
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
|
||||
def test_invalid_redirect_uri_empty(self):
|
||||
"""test missing/invalid redirect URI"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name=generate_id(),
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="",
|
||||
)
|
||||
with self.assertRaises(RedirectUriError):
|
||||
request = self.factory.get("/", data={"response_type": "code", "client_id": "test"})
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "code",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "+",
|
||||
},
|
||||
)
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
provider.refresh_from_db()
|
||||
self.assertEqual(provider.redirect_uris, "+")
|
||||
|
||||
def test_invalid_redirect_uri_regex(self):
|
||||
"""test missing/invalid redirect URI"""
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid?",
|
||||
)
|
||||
with self.assertRaises(RedirectUriError):
|
||||
request = self.factory.get("/", data={"response_type": "code", "client_id": "test"})
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
with self.assertRaises(RedirectUriError):
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "code",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://localhost",
|
||||
},
|
||||
)
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
|
||||
def test_redirect_uri_invalid_regex(self):
|
||||
"""test missing/invalid redirect URI (invalid regex)"""
|
||||
OAuth2Provider.objects.create(
|
||||
name=generate_id(),
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="+",
|
||||
@ -103,7 +148,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
def test_empty_redirect_uri(self):
|
||||
"""test empty redirect URI (configure in provider)"""
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
)
|
||||
@ -123,7 +168,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
def test_response_type(self):
|
||||
"""test response_type"""
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid/Foo",
|
||||
@ -201,7 +246,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
"""Test full authorization"""
|
||||
flow = create_test_flow()
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id="test",
|
||||
authorization_flow=flow,
|
||||
redirect_uris="foo://localhost",
|
||||
@ -237,12 +282,12 @@ class TestAuthorize(OAuthTestCase):
|
||||
"""Test full authorization"""
|
||||
flow = create_test_flow()
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id="test",
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=flow,
|
||||
redirect_uris="http://localhost",
|
||||
signing_key=create_test_cert(),
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
Application.objects.create(name="app", slug="app", provider=provider)
|
||||
state = generate_id()
|
||||
@ -281,12 +326,12 @@ class TestAuthorize(OAuthTestCase):
|
||||
"""Test full authorization (form_post response)"""
|
||||
flow = create_test_flow()
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id="test",
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=flow,
|
||||
redirect_uris="http://localhost",
|
||||
signing_key=create_test_cert(),
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
Application.objects.create(name="app", slug="app", provider=provider)
|
||||
state = generate_id()
|
||||
|
@ -5,7 +5,7 @@ from django.test import RequestFactory
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
from authentik.providers.oauth2.constants import (
|
||||
@ -24,17 +24,17 @@ class TestToken(OAuthTestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.factory = RequestFactory()
|
||||
self.app = Application.objects.create(name="test", slug="test")
|
||||
self.app = Application.objects.create(name=generate_id(), slug="test")
|
||||
|
||||
def test_request_auth_code(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://testserver",
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://TestServer",
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = create_test_admin_user()
|
||||
@ -44,7 +44,7 @@ class TestToken(OAuthTestCase):
|
||||
data={
|
||||
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
"code": code.code,
|
||||
"redirect_uri": "http://testserver",
|
||||
"redirect_uri": "http://TestServer",
|
||||
},
|
||||
HTTP_AUTHORIZATION=f"Basic {header}",
|
||||
)
|
||||
@ -56,12 +56,12 @@ class TestToken(OAuthTestCase):
|
||||
def test_request_auth_code_invalid(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://testserver",
|
||||
signing_key=create_test_cert(),
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
request = self.factory.post(
|
||||
@ -79,12 +79,12 @@ class TestToken(OAuthTestCase):
|
||||
def test_request_refresh_token(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
signing_key=create_test_cert(),
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = create_test_admin_user()
|
||||
@ -108,12 +108,12 @@ class TestToken(OAuthTestCase):
|
||||
def test_auth_code_view(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
signing_key=create_test_cert(),
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
self.app.provider = provider
|
||||
@ -150,12 +150,12 @@ class TestToken(OAuthTestCase):
|
||||
def test_refresh_token_view(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
signing_key=create_test_cert(),
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
self.app.provider = provider
|
||||
@ -199,12 +199,12 @@ class TestToken(OAuthTestCase):
|
||||
def test_refresh_token_view_invalid_origin(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
signing_key=create_test_cert(),
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = create_test_admin_user()
|
||||
@ -244,12 +244,12 @@ class TestToken(OAuthTestCase):
|
||||
def test_refresh_token_revoke(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
name=generate_id(),
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://testserver",
|
||||
signing_key=create_test_cert(),
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
self.app.provider = provider
|
||||
|
@ -2,12 +2,15 @@
|
||||
from django.test import TestCase
|
||||
from jwt import decode
|
||||
|
||||
from authentik.core.tests.utils import create_test_cert
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider, RefreshToken
|
||||
|
||||
|
||||
class OAuthTestCase(TestCase):
|
||||
"""OAuth test helpers"""
|
||||
|
||||
keypair: CertificateKeyPair
|
||||
required_jwt_keys = [
|
||||
"exp",
|
||||
"iat",
|
||||
@ -17,6 +20,11 @@ class OAuthTestCase(TestCase):
|
||||
"iss",
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.keypair = create_test_cert()
|
||||
super().setUpClass()
|
||||
|
||||
def validate_jwt(self, token: RefreshToken, provider: OAuth2Provider):
|
||||
"""Validate that all required fields are set"""
|
||||
key, alg = provider.get_jwt_key()
|
||||
|
@ -2,7 +2,7 @@
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
from re import error as RegexError
|
||||
from re import escape, fullmatch
|
||||
from re import fullmatch
|
||||
from typing import Optional
|
||||
from urllib.parse import parse_qs, urlencode, urlparse, urlsplit, urlunsplit
|
||||
from uuid import uuid4
|
||||
@ -181,7 +181,7 @@ class OAuthAuthorizationParams:
|
||||
|
||||
if self.provider.redirect_uris == "":
|
||||
LOGGER.info("Setting redirect for blank redirect_uris", redirect=self.redirect_uri)
|
||||
self.provider.redirect_uris = escape(self.redirect_uri)
|
||||
self.provider.redirect_uris = self.redirect_uri
|
||||
self.provider.save()
|
||||
allowed_redirect_urls = self.provider.redirect_uris.split()
|
||||
|
||||
@ -194,14 +194,20 @@ class OAuthAuthorizationParams:
|
||||
try:
|
||||
if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls):
|
||||
LOGGER.warning(
|
||||
"Invalid redirect uri",
|
||||
"Invalid redirect uri (regex comparison)",
|
||||
redirect_uri=self.redirect_uri,
|
||||
expected=allowed_redirect_urls,
|
||||
)
|
||||
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
|
||||
except RegexError as exc:
|
||||
LOGGER.warning("Invalid regular expression configured", exc=exc)
|
||||
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
|
||||
LOGGER.info("Failed to parse regular expression, checking directly", exc=exc)
|
||||
if not any(x == self.redirect_uri for x in allowed_redirect_urls):
|
||||
LOGGER.warning(
|
||||
"Invalid redirect uri (strict comparison)",
|
||||
redirect_uri=self.redirect_uri,
|
||||
expected=allowed_redirect_urls,
|
||||
)
|
||||
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
|
||||
if self.request:
|
||||
raise AuthorizeError(
|
||||
self.redirect_uri, "request_not_supported", self.grant_type, self.state
|
||||
|
@ -89,7 +89,7 @@ class TokenParams:
|
||||
provider=provider,
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
redirect_uri=request.POST.get("redirect_uri", "").lower(),
|
||||
redirect_uri=request.POST.get("redirect_uri", ""),
|
||||
grant_type=request.POST.get("grant_type", ""),
|
||||
state=request.POST.get("state", ""),
|
||||
scope=request.POST.get("scope", "").split(),
|
||||
@ -154,7 +154,7 @@ class TokenParams:
|
||||
try:
|
||||
if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls):
|
||||
LOGGER.warning(
|
||||
"Invalid redirect uri",
|
||||
"Invalid redirect uri (regex comparison)",
|
||||
redirect_uri=self.redirect_uri,
|
||||
expected=allowed_redirect_urls,
|
||||
)
|
||||
@ -167,13 +167,19 @@ class TokenParams:
|
||||
).from_http(request)
|
||||
raise TokenError("invalid_client")
|
||||
except RegexError as exc:
|
||||
LOGGER.warning("Invalid regular expression configured", exc=exc)
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message="Invalid redirect_uri RegEx configured",
|
||||
provider=self.provider,
|
||||
).from_http(request)
|
||||
raise TokenError("invalid_client")
|
||||
LOGGER.info("Failed to parse regular expression, checking directly", exc=exc)
|
||||
if not any(x == self.redirect_uri for x in allowed_redirect_urls):
|
||||
LOGGER.warning(
|
||||
"Invalid redirect uri (strict comparison)",
|
||||
redirect_uri=self.redirect_uri,
|
||||
expected=allowed_redirect_urls,
|
||||
)
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message="Invalid redirect_uri configured",
|
||||
provider=self.provider,
|
||||
).from_http(request)
|
||||
raise TokenError("invalid_client")
|
||||
|
||||
try:
|
||||
self.authorization_code = AuthorizationCode.objects.get(code=raw_code)
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""authentik core celery"""
|
||||
import os
|
||||
from logging.config import dictConfig
|
||||
from typing import Callable
|
||||
|
||||
from celery import Celery
|
||||
from celery.signals import (
|
||||
@ -76,23 +77,28 @@ def task_error_hook(task_id, exception: Exception, traceback, *args, **kwargs):
|
||||
Event.new(EventAction.SYSTEM_EXCEPTION, message=exception_to_string(exception)).save()
|
||||
|
||||
|
||||
@worker_ready.connect
|
||||
def worker_ready_hook(*args, **kwargs):
|
||||
"""Run certain tasks on worker start"""
|
||||
def _get_startup_tasks() -> list[Callable]:
|
||||
"""Get all tasks to be run on startup"""
|
||||
from authentik.admin.tasks import clear_update_notifications
|
||||
from authentik.managed.tasks import managed_reconcile
|
||||
from authentik.outposts.tasks import outpost_controller_all, outpost_local_connection
|
||||
from authentik.providers.proxy.tasks import proxy_set_defaults
|
||||
|
||||
tasks = [
|
||||
return [
|
||||
clear_update_notifications,
|
||||
outpost_local_connection,
|
||||
outpost_controller_all,
|
||||
proxy_set_defaults,
|
||||
managed_reconcile,
|
||||
]
|
||||
|
||||
|
||||
@worker_ready.connect
|
||||
def worker_ready_hook(*args, **kwargs):
|
||||
"""Run certain tasks on worker start"""
|
||||
|
||||
LOGGER.info("Dispatching startup tasks...")
|
||||
for task in tasks:
|
||||
for task in _get_startup_tasks():
|
||||
try:
|
||||
task.delay()
|
||||
except ProgrammingError as exc:
|
||||
|
@ -33,8 +33,8 @@ class PytestTestRunner: # pragma: no cover
|
||||
"outposts.container_image_base",
|
||||
f"ghcr.io/goauthentik/dev-%(type)s:{get_docker_tag()}",
|
||||
)
|
||||
CONFIG.y_set("error_reporting.sample_rate", 1.0)
|
||||
sentry_init(
|
||||
sample_rate=1.0,
|
||||
environment="testing",
|
||||
send_default_pii=True,
|
||||
)
|
||||
@ -43,6 +43,7 @@ class PytestTestRunner: # pragma: no cover
|
||||
def add_arguments(cls, parser: ArgumentParser):
|
||||
"""Add more pytest-specific arguments"""
|
||||
parser.add_argument("--randomly-seed", type=int)
|
||||
parser.add_argument("--keepdb", action="store_true")
|
||||
|
||||
def run_tests(self, test_labels):
|
||||
"""Run pytest and return the exitcode.
|
||||
|
@ -44,7 +44,7 @@ for _authentik_app in get_apps():
|
||||
)
|
||||
|
||||
urlpatterns += [
|
||||
path("metrics/", MetricsView.as_view(), name="metrics"),
|
||||
path("-/metrics/", MetricsView.as_view(), name="metrics"),
|
||||
path("-/health/live/", LiveView.as_view(), name="health-live"),
|
||||
path("-/health/ready/", ReadyView.as_view(), name="health-ready"),
|
||||
]
|
||||
|
@ -77,10 +77,7 @@ class OAuth2Client(BaseOAuthClient):
|
||||
if self.source.type.urls_customizable and self.source.access_token_url:
|
||||
access_token_url = self.source.access_token_url
|
||||
response = self.session.request(
|
||||
"post",
|
||||
access_token_url,
|
||||
data=args,
|
||||
headers=self._default_headers,
|
||||
"post", access_token_url, data=args, headers=self._default_headers, **request_kwargs
|
||||
)
|
||||
response.raise_for_status()
|
||||
except RequestException as exc:
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""Twitter OAuth Views"""
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.sources.oauth.clients.oauth2 import SESSION_KEY_OAUTH_PKCE
|
||||
@ -9,6 +9,23 @@ from authentik.sources.oauth.views.callback import OAuthCallback
|
||||
from authentik.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
|
||||
class TwitterClient(AzureADClient):
|
||||
"""Twitter has similar quirks to Azure AD, and additionally requires Basic auth on
|
||||
the access token endpoint for some reason."""
|
||||
|
||||
# Twitter has the same quirk as azure and throws an error if the access token
|
||||
# is set via query parameter, so we re-use the azure client
|
||||
# see https://github.com/goauthentik/authentik/issues/1910
|
||||
|
||||
def get_access_token(self, **request_kwargs) -> Optional[dict[str, Any]]:
|
||||
return super().get_access_token(
|
||||
auth=(
|
||||
self.source.consumer_key,
|
||||
self.source.consumer_secret,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TwitterOAuthRedirect(OAuthRedirect):
|
||||
"""Twitter OAuth2 Redirect"""
|
||||
|
||||
@ -24,10 +41,7 @@ class TwitterOAuthRedirect(OAuthRedirect):
|
||||
class TwitterOAuthCallback(OAuthCallback):
|
||||
"""Twitter OAuth2 Callback"""
|
||||
|
||||
# Twitter has the same quirk as azure and throws an error if the access token
|
||||
# is set via query parameter, so we re-use the azure client
|
||||
# see https://github.com/goauthentik/authentik/issues/1910
|
||||
client_class = AzureADClient
|
||||
client_class = TwitterClient
|
||||
|
||||
def get_user_id(self, info: dict[str, str]) -> str:
|
||||
return info.get("data", {}).get("id", "")
|
||||
|
@ -120,7 +120,7 @@ def validate_challenge_webauthn(data: dict, stage_view: StageView, user: User) -
|
||||
|
||||
device = WebAuthnDevice.objects.filter(credential_id=credential_id).first()
|
||||
if not device:
|
||||
raise Http404()
|
||||
raise ValidationError("Invalid device")
|
||||
|
||||
try:
|
||||
authentication_verification = verify_authentication_response(
|
||||
|
@ -374,9 +374,9 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||
# All validation is done by the serializer
|
||||
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
||||
if not user:
|
||||
webauthn_device: WebAuthnDevice = response.data.get("webauthn", None)
|
||||
if not webauthn_device:
|
||||
return self.executor.stage_ok()
|
||||
if "webauthn" not in response.data:
|
||||
return self.executor.stage_invalid()
|
||||
webauthn_device: WebAuthnDevice = response.device
|
||||
self.logger.debug("Set user from user-less flow", user=webauthn_device.user)
|
||||
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = webauthn_device.user
|
||||
self.executor.plan.context[PLAN_CONTEXT_METHOD] = "auth_webauthn_pwl"
|
||||
|
@ -1,12 +1,12 @@
|
||||
"""Test validator stage"""
|
||||
from time import sleep
|
||||
|
||||
from django.http import Http404
|
||||
from django.test.client import RequestFactory
|
||||
from django.urls.base import reverse
|
||||
from webauthn.helpers import bytes_to_base64url
|
||||
from rest_framework.serializers import ValidationError
|
||||
from webauthn.helpers import base64url_to_bytes, bytes_to_base64url
|
||||
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
|
||||
from authentik.flows.stage import StageView
|
||||
from authentik.flows.tests import FlowTestCase
|
||||
@ -15,10 +15,13 @@ from authentik.lib.generators import generate_id
|
||||
from authentik.lib.tests.utils import get_request
|
||||
from authentik.stages.authenticator_validate.challenge import (
|
||||
get_challenge_for_device,
|
||||
get_webauthn_challenge_without_user,
|
||||
validate_challenge_webauthn,
|
||||
)
|
||||
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
|
||||
from authentik.stages.authenticator_validate.stage import AuthenticatorValidateStageView
|
||||
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
||||
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
|
||||
from authentik.stages.identification.models import IdentificationStage, UserFields
|
||||
|
||||
|
||||
@ -104,7 +107,199 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
||||
},
|
||||
)
|
||||
|
||||
with self.assertRaises(Http404):
|
||||
with self.assertRaises(ValidationError):
|
||||
validate_challenge_webauthn(
|
||||
{}, StageView(FlowExecutorView(current_stage=stage), request=request), self.user
|
||||
)
|
||||
|
||||
def test_get_challenge(self):
|
||||
"""Test webauthn"""
|
||||
request = get_request("/")
|
||||
request.user = self.user
|
||||
|
||||
webauthn_device = WebAuthnDevice.objects.create(
|
||||
user=self.user,
|
||||
public_key=(
|
||||
"pQECAyYgASFYIGsBLkklToCQkT7qJT_bJYN1sEc1oJdbnmoOc43i0J"
|
||||
"H6IlggLTXytuhzFVYYAK4PQNj8_coGrbbzSfUxdiPAcZTQCyU"
|
||||
),
|
||||
credential_id="QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
sign_count=0,
|
||||
rp_id=generate_id(),
|
||||
)
|
||||
challenge = get_challenge_for_device(request, webauthn_device)
|
||||
webauthn_challenge = request.session[SESSION_KEY_WEBAUTHN_CHALLENGE]
|
||||
self.assertEqual(
|
||||
challenge,
|
||||
{
|
||||
"allowCredentials": [
|
||||
{
|
||||
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
"type": "public-key",
|
||||
}
|
||||
],
|
||||
"challenge": bytes_to_base64url(webauthn_challenge),
|
||||
"rpId": "testserver",
|
||||
"timeout": 60000,
|
||||
"userVerification": "preferred",
|
||||
},
|
||||
)
|
||||
|
||||
def test_get_challenge_userless(self):
|
||||
"""Test webauthn (userless)"""
|
||||
request = get_request("/")
|
||||
|
||||
WebAuthnDevice.objects.create(
|
||||
user=self.user,
|
||||
public_key=(
|
||||
"pQECAyYgASFYIGsBLkklToCQkT7qJT_bJYN1sEc1oJdbnmoOc43i0J"
|
||||
"H6IlggLTXytuhzFVYYAK4PQNj8_coGrbbzSfUxdiPAcZTQCyU"
|
||||
),
|
||||
credential_id="QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
sign_count=0,
|
||||
rp_id=generate_id(),
|
||||
)
|
||||
challenge = get_webauthn_challenge_without_user(request)
|
||||
webauthn_challenge = request.session[SESSION_KEY_WEBAUTHN_CHALLENGE]
|
||||
self.assertEqual(
|
||||
challenge,
|
||||
{
|
||||
"allowCredentials": [],
|
||||
"challenge": bytes_to_base64url(webauthn_challenge),
|
||||
"rpId": "testserver",
|
||||
"timeout": 60000,
|
||||
"userVerification": "preferred",
|
||||
},
|
||||
)
|
||||
|
||||
def test_validate_challenge(self):
|
||||
"""Test webauthn"""
|
||||
request = get_request("/")
|
||||
request.user = self.user
|
||||
|
||||
WebAuthnDevice.objects.create(
|
||||
user=self.user,
|
||||
public_key=(
|
||||
"pQECAyYgASFYIGsBLkklToCQkT7qJT_bJYN1sEc1oJdbnmoOc43i0J"
|
||||
"H6IlggLTXytuhzFVYYAK4PQNj8_coGrbbzSfUxdiPAcZTQCyU"
|
||||
),
|
||||
credential_id="QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
sign_count=4,
|
||||
rp_id=generate_id(),
|
||||
)
|
||||
flow = create_test_flow()
|
||||
stage = AuthenticatorValidateStage.objects.create(
|
||||
name=generate_id(),
|
||||
not_configured_action=NotConfiguredAction.CONFIGURE,
|
||||
device_classes=[DeviceClasses.WEBAUTHN],
|
||||
)
|
||||
stage_view = AuthenticatorValidateStageView(
|
||||
FlowExecutorView(flow=flow, current_stage=stage), request=request
|
||||
)
|
||||
request = get_request("/")
|
||||
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
(
|
||||
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1"
|
||||
"jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
||||
)
|
||||
)
|
||||
request.session.save()
|
||||
|
||||
stage_view = AuthenticatorValidateStageView(
|
||||
FlowExecutorView(flow=flow, current_stage=stage), request=request
|
||||
)
|
||||
request.META["SERVER_NAME"] = "localhost"
|
||||
request.META["SERVER_PORT"] = "9000"
|
||||
validate_challenge_webauthn(
|
||||
{
|
||||
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
"rawId": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
"type": "public-key",
|
||||
"assertionClientExtensions": "{}",
|
||||
"response": {
|
||||
"clientDataJSON": (
|
||||
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2WlhvNWx4TGZo"
|
||||
"ckQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGczQWdPNFdoLUJxYTU1"
|
||||
"NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJjcm9z"
|
||||
"c09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hlcmUiOiJkbyBub3Qg"
|
||||
"Y29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczov"
|
||||
"L2dvby5nbC95YWJQZXgifQ==",
|
||||
),
|
||||
"signature": (
|
||||
"MEQCIFNlrHf9ablJAalXLWkrqvHB8oIu8kwvRpH3X3rbJVpI"
|
||||
"AiAqtOK6mIZPk62kZN0OzFsHfuvu_RlOl7zlqSNzDdz_Ag=="
|
||||
),
|
||||
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAABQ==",
|
||||
"userHandle": None,
|
||||
},
|
||||
},
|
||||
stage_view,
|
||||
self.user,
|
||||
)
|
||||
|
||||
def test_validate_challenge_invalid(self):
|
||||
"""Test webauthn"""
|
||||
request = get_request("/")
|
||||
request.user = self.user
|
||||
|
||||
WebAuthnDevice.objects.create(
|
||||
user=self.user,
|
||||
public_key=(
|
||||
"pQECAyYgASFYIGsBLkklToCQkT7qJT_bJYN1sEc1oJdbnmoOc4"
|
||||
"3i0JH6IlggLTXytuhzFVYYAK4PQNj8_coGrbbzSfUxdiPAcZTQCyU"
|
||||
),
|
||||
credential_id="QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
# One more sign count than above, make it invalid
|
||||
sign_count=5,
|
||||
rp_id=generate_id(),
|
||||
)
|
||||
flow = create_test_flow()
|
||||
stage = AuthenticatorValidateStage.objects.create(
|
||||
name=generate_id(),
|
||||
not_configured_action=NotConfiguredAction.CONFIGURE,
|
||||
device_classes=[DeviceClasses.WEBAUTHN],
|
||||
)
|
||||
stage_view = AuthenticatorValidateStageView(
|
||||
FlowExecutorView(flow=flow, current_stage=stage), request=request
|
||||
)
|
||||
request = get_request("/")
|
||||
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||
(
|
||||
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1j"
|
||||
"wSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
||||
)
|
||||
)
|
||||
request.session.save()
|
||||
|
||||
stage_view = AuthenticatorValidateStageView(
|
||||
FlowExecutorView(flow=flow, current_stage=stage), request=request
|
||||
)
|
||||
request.META["SERVER_NAME"] = "localhost"
|
||||
request.META["SERVER_PORT"] = "9000"
|
||||
with self.assertRaises(ValidationError):
|
||||
validate_challenge_webauthn(
|
||||
{
|
||||
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
"rawId": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||
"type": "public-key",
|
||||
"assertionClientExtensions": "{}",
|
||||
"response": {
|
||||
"clientDataJSON": (
|
||||
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2WlhvNWx4"
|
||||
"TGZockQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGczQWdPNFdo"
|
||||
"LUJxYTU1NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0Ojkw"
|
||||
"MDAiLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hl"
|
||||
"cmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxh"
|
||||
"dGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ=="
|
||||
),
|
||||
"signature": (
|
||||
"MEQCIFNlrHf9ablJAalXLWkrqvHB8oIu8kwvRpH3X3rbJVpI"
|
||||
"AiAqtOK6mIZPk62kZN0OzFsHfuvu_RlOl7zlqSNzDdz_Ag=="
|
||||
),
|
||||
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAABQ==",
|
||||
"userHandle": None,
|
||||
},
|
||||
},
|
||||
stage_view,
|
||||
self.user,
|
||||
)
|
||||
|
@ -144,4 +144,4 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||
return self.executor.stage_ok()
|
||||
|
||||
def cleanup(self):
|
||||
self.request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE)
|
||||
self.request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE, None)
|
||||
|
@ -1,20 +1,93 @@
|
||||
"""Test WebAuthn API"""
|
||||
from base64 import b64decode
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
from webauthn.helpers import bytes_to_base64url
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.flows.markers import StageMarker
|
||||
from authentik.flows.models import FlowStageBinding
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
||||
from authentik.flows.tests import FlowTestCase
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage, WebAuthnDevice
|
||||
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
|
||||
|
||||
|
||||
class AuthenticatorWebAuthnStage(APITestCase):
|
||||
class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
||||
"""Test WebAuthn API"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.stage = AuthenticateWebAuthnStage.objects.create(
|
||||
name=generate_id(),
|
||||
)
|
||||
self.flow = create_test_flow()
|
||||
self.binding = FlowStageBinding.objects.create(
|
||||
target=self.flow,
|
||||
stage=self.stage,
|
||||
order=0,
|
||||
)
|
||||
self.user = create_test_admin_user()
|
||||
|
||||
def test_api_delete(self):
|
||||
"""Test api delete"""
|
||||
user = User.objects.create(username="foo")
|
||||
self.client.force_login(user)
|
||||
dev = WebAuthnDevice.objects.create(user=user)
|
||||
self.client.force_login(self.user)
|
||||
dev = WebAuthnDevice.objects.create(user=self.user)
|
||||
response = self.client.delete(
|
||||
reverse("authentik_api:webauthndevice-detail", kwargs={"pk": dev.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
|
||||
def test_registration_options(self):
|
||||
"""Test registration options"""
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = b64decode(
|
||||
(
|
||||
"o90Yh1osqW3mjGift+6WclWOya5lcdff/G0mqueN3hChacMUz"
|
||||
"V4mxiDafuQ0x0e1d/fcPai0fx/jMBZ8/nG2qQ=="
|
||||
).encode()
|
||||
)
|
||||
session.save()
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
session = self.client.session
|
||||
self.assertStageResponse(
|
||||
response,
|
||||
self.flow,
|
||||
self.user,
|
||||
registration={
|
||||
"rp": {"name": "authentik", "id": "testserver"},
|
||||
"user": {
|
||||
"id": bytes_to_base64url(self.user.uid.encode("utf-8")),
|
||||
"name": self.user.username,
|
||||
"displayName": self.user.name,
|
||||
},
|
||||
"challenge": bytes_to_base64url(session[SESSION_KEY_WEBAUTHN_CHALLENGE]),
|
||||
"pubKeyCredParams": [
|
||||
{"type": "public-key", "alg": -7},
|
||||
{"type": "public-key", "alg": -8},
|
||||
{"type": "public-key", "alg": -36},
|
||||
{"type": "public-key", "alg": -37},
|
||||
{"type": "public-key", "alg": -38},
|
||||
{"type": "public-key", "alg": -39},
|
||||
{"type": "public-key", "alg": -257},
|
||||
{"type": "public-key", "alg": -258},
|
||||
{"type": "public-key", "alg": -259},
|
||||
],
|
||||
"timeout": 60000,
|
||||
"excludeCredentials": [],
|
||||
"authenticatorSelection": {
|
||||
"residentKey": "preferred",
|
||||
"requireResidentKey": False,
|
||||
"userVerification": "preferred",
|
||||
},
|
||||
"attestation": "none",
|
||||
},
|
||||
)
|
||||
|
@ -162,10 +162,10 @@ class IdentificationStageView(ChallengeStageView):
|
||||
if not query:
|
||||
self.logger.debug("Empty user query", query=query)
|
||||
return None
|
||||
users = User.objects.filter(query, is_active=True)
|
||||
if users.exists():
|
||||
self.logger.debug("Found user", user=users.first(), query=query)
|
||||
return users.first()
|
||||
user = User.objects.filter(query, is_active=True).first()
|
||||
if user:
|
||||
self.logger.debug("Found user", user=user.username, query=query)
|
||||
return user
|
||||
return None
|
||||
|
||||
def get_challenge(self) -> Challenge:
|
||||
|
@ -56,7 +56,7 @@ def authenticate(
|
||||
continue
|
||||
# Annotate the user object with the path of the backend.
|
||||
user.backend = backend_path
|
||||
LOGGER.debug("Successful authentication", user=user, backend=backend_path)
|
||||
LOGGER.debug("Successful authentication", user=user.username, backend=backend_path)
|
||||
return user
|
||||
|
||||
# The credentials supplied are invalid to all backends, fire signal
|
||||
|
@ -47,7 +47,7 @@ class UserLoginStageView(StageView):
|
||||
self.logger.debug(
|
||||
"Logged in",
|
||||
backend=backend,
|
||||
user=user,
|
||||
user=user.username,
|
||||
flow_slug=self.executor.flow.slug,
|
||||
session_duration=self.executor.current_stage.session_duration,
|
||||
)
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"goauthentik.io/internal/gounicorn"
|
||||
"goauthentik.io/internal/outpost/ak"
|
||||
"goauthentik.io/internal/outpost/proxyv2"
|
||||
sentryutils "goauthentik.io/internal/utils/sentry"
|
||||
"goauthentik.io/internal/web"
|
||||
"goauthentik.io/internal/web/tenant_tls"
|
||||
)
|
||||
@ -51,7 +52,7 @@ func main() {
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: config.G.ErrorReporting.DSN,
|
||||
AttachStacktrace: true,
|
||||
TracesSampleRate: config.G.ErrorReporting.SampleRate,
|
||||
TracesSampler: sentryutils.SamplerFunc(config.G.ErrorReporting.SampleRate),
|
||||
Release: fmt.Sprintf("authentik@%s", constants.VERSION),
|
||||
Environment: config.G.ErrorReporting.Environment,
|
||||
IgnoreErrors: []string{
|
||||
|
@ -29,7 +29,7 @@ services:
|
||||
retries: 5
|
||||
timeout: 3s
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.6.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.6.3}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -50,7 +50,7 @@ services:
|
||||
- "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000"
|
||||
- "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.6.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.6.3}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
6
go.mod
6
go.mod
@ -25,8 +25,8 @@ require (
|
||||
github.com/prometheus/client_golang v1.12.2
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.1
|
||||
goauthentik.io/api/v3 v3.2022053.4
|
||||
github.com/stretchr/testify v1.7.2
|
||||
goauthentik.io/api/v3 v3.2022061.3
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b
|
||||
@ -73,5 +73,5 @@ require (
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
11
go.sum
11
go.sum
@ -337,8 +337,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
@ -358,8 +358,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
|
||||
goauthentik.io/api/v3 v3.2022053.4 h1:MzWwr6YXpnJhCnjf74/vpC5QlEQ71WAGsAPrJMFnLwo=
|
||||
goauthentik.io/api/v3 v3.2022053.4/go.mod h1:QM9J32HgYE4gL71lWAfAoXSPdSmLVLW08itfLI3Mo10=
|
||||
goauthentik.io/api/v3 v3.2022061.3 h1:1vW4j8sk6pOqPPYquGcYWBA3zfa8pmCdceMNbKRBJiA=
|
||||
goauthentik.io/api/v3 v3.2022061.3/go.mod h1:QM9J32HgYE4gL71lWAfAoXSPdSmLVLW08itfLI3Mo10=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
@ -668,8 +668,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@ -25,4 +25,4 @@ func OutpostUserAgent() string {
|
||||
return fmt.Sprintf("goauthentik.io/outpost/%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2022.6.1"
|
||||
const VERSION = "2022.6.3"
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/api/v3"
|
||||
"goauthentik.io/internal/constants"
|
||||
sentryutils "goauthentik.io/internal/utils/sentry"
|
||||
)
|
||||
|
||||
var initialSetup = false
|
||||
@ -47,10 +48,10 @@ func doGlobalSetup(outpost api.Outpost, globalConfig *api.Config) {
|
||||
l.WithField("env", globalConfig.ErrorReporting.Environment).Debug("Error reporting enabled")
|
||||
}
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: dsn,
|
||||
Environment: globalConfig.ErrorReporting.Environment,
|
||||
TracesSampleRate: float64(globalConfig.ErrorReporting.TracesSampleRate),
|
||||
Release: fmt.Sprintf("authentik@%s", constants.VERSION),
|
||||
Dsn: dsn,
|
||||
Environment: globalConfig.ErrorReporting.Environment,
|
||||
TracesSampler: sentryutils.SamplerFunc(float64(globalConfig.ErrorReporting.TracesSampleRate)),
|
||||
Release: fmt.Sprintf("authentik@%s", constants.VERSION),
|
||||
IgnoreErrors: []string{
|
||||
http.ErrAbortHandler.Error(),
|
||||
},
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/internal/utils/sentry"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@ -25,6 +26,7 @@ var (
|
||||
func RunServer() {
|
||||
m := mux.NewRouter()
|
||||
l := log.WithField("logger", "authentik.outpost.metrics")
|
||||
m.Use(sentry.SentryNoSampleMiddleware)
|
||||
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(204)
|
||||
})
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/go-openapi/strfmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/api/v3"
|
||||
"goauthentik.io/internal/outpost/ldap/bind"
|
||||
directbind "goauthentik.io/internal/outpost/ldap/bind/direct"
|
||||
memorybind "goauthentik.io/internal/outpost/ldap/bind/memory"
|
||||
"goauthentik.io/internal/outpost/ldap/constants"
|
||||
@ -83,7 +84,11 @@ func (ls *LDAPServer) Refresh() error {
|
||||
providers[idx].searcher = directsearch.NewDirectSearcher(providers[idx])
|
||||
}
|
||||
if *provider.BindMode.Ptr() == api.LDAPAPIACCESSMODE_CACHED {
|
||||
providers[idx].binder = memorybind.NewSessionBinder(providers[idx], providers[idx].binder)
|
||||
var oldBinder bind.Binder
|
||||
if existing != nil {
|
||||
oldBinder = existing.binder
|
||||
}
|
||||
providers[idx].binder = memorybind.NewSessionBinder(providers[idx], oldBinder)
|
||||
} else if *provider.BindMode.Ptr() == api.LDAPAPIACCESSMODE_DIRECT {
|
||||
providers[idx].binder = directbind.NewDirectBinder(providers[idx])
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import (
|
||||
"goauthentik.io/internal/constants"
|
||||
)
|
||||
|
||||
var hasReportedMisconfiguration = false
|
||||
|
||||
func (a *Application) addHeaders(headers http.Header, c *Claims) {
|
||||
// https://goauthentik.io/docs/providers/proxy/proxy
|
||||
|
||||
@ -103,6 +105,9 @@ func (a *Application) getNginxForwardUrl(r *http.Request) (*url.URL, error) {
|
||||
func (a *Application) ReportMisconfiguration(r *http.Request, msg string, fields map[string]interface{}) {
|
||||
fields["message"] = msg
|
||||
a.log.WithFields(fields).Error("Reporting configuration error")
|
||||
if hasReportedMisconfiguration {
|
||||
return
|
||||
}
|
||||
req := api.EventRequest{
|
||||
Action: api.EVENTACTIONS_CONFIGURATION_ERROR,
|
||||
App: "authentik.providers.proxy", // must match python apps.py name
|
||||
@ -112,6 +117,8 @@ func (a *Application) ReportMisconfiguration(r *http.Request, msg string, fields
|
||||
_, _, err := a.ak.Client.EventsApi.EventsEventsCreate(context.Background()).EventRequest(req).Execute()
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to report configuration error")
|
||||
} else {
|
||||
hasReportedMisconfiguration = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,19 +12,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
envoyPrefix = "/outpost.goauthentik.io/auth/envoy"
|
||||
envoyPrefix = "/outpost.goauthentik.io/auth/envoy"
|
||||
traefikPrefix = "/outpost.goauthentik.io/auth/traefik"
|
||||
nginxPrefix = "/outpost.goauthentik.io/auth/nginx"
|
||||
)
|
||||
|
||||
func (a *Application) configureForward() error {
|
||||
a.mux.HandleFunc("/outpost.goauthentik.io/auth", func(rw http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := r.URL.Query()["traefik"]; ok {
|
||||
a.forwardHandleTraefik(rw, r)
|
||||
return
|
||||
}
|
||||
a.forwardHandleNginx(rw, r)
|
||||
})
|
||||
a.mux.HandleFunc("/outpost.goauthentik.io/auth/traefik", a.forwardHandleTraefik)
|
||||
a.mux.HandleFunc("/outpost.goauthentik.io/auth/nginx", a.forwardHandleNginx)
|
||||
a.mux.HandleFunc(traefikPrefix, a.forwardHandleTraefik)
|
||||
a.mux.HandleFunc(nginxPrefix, a.forwardHandleNginx)
|
||||
a.mux.PathPrefix(envoyPrefix).HandlerFunc(a.forwardHandleEnvoy)
|
||||
return nil
|
||||
}
|
||||
@ -59,7 +54,6 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
host := ""
|
||||
s, _ := a.sessions.Get(r, constants.SessionName)
|
||||
// Optional suffix, which is appended to the URL
|
||||
if *a.proxyConfig.Mode.Get() == api.PROXYMODE_FORWARD_SINGLE {
|
||||
host = web.GetHost(r)
|
||||
@ -75,10 +69,13 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
|
||||
// to a (possibly) different domain, but we want to be redirected back
|
||||
// to the application
|
||||
// X-Forwarded-Uri is only the path, so we need to build the entire URL
|
||||
s.Values[constants.SessionRedirect] = fwd.String()
|
||||
err = s.Save(r, rw)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
||||
s, _ := a.sessions.Get(r, constants.SessionName)
|
||||
if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet {
|
||||
s.Values[constants.SessionRedirect] = fwd.String()
|
||||
err = s.Save(r, rw)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
||||
}
|
||||
}
|
||||
|
||||
proto := r.Header.Get("X-Forwarded-Proto")
|
||||
@ -117,10 +114,12 @@ func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
s, _ := a.sessions.Get(r, constants.SessionName)
|
||||
s.Values[constants.SessionRedirect] = fwd.String()
|
||||
err = s.Save(r, rw)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
||||
if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet {
|
||||
s.Values[constants.SessionRedirect] = fwd.String()
|
||||
err = s.Save(r, rw)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
||||
}
|
||||
}
|
||||
|
||||
if fwd.String() != r.URL.String() {
|
||||
@ -152,7 +151,6 @@ func (a *Application) forwardHandleEnvoy(rw http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
host := ""
|
||||
s, _ := a.sessions.Get(r, constants.SessionName)
|
||||
// Optional suffix, which is appended to the URL
|
||||
if *a.proxyConfig.Mode.Get() == api.PROXYMODE_FORWARD_SINGLE {
|
||||
host = web.GetHost(r)
|
||||
@ -168,6 +166,7 @@ func (a *Application) forwardHandleEnvoy(rw http.ResponseWriter, r *http.Request
|
||||
// to a (possibly) different domain, but we want to be redirected back
|
||||
// to the application
|
||||
// X-Forwarded-Uri is only the path, so we need to build the entire URL
|
||||
s, _ := a.sessions.Get(r, constants.SessionName)
|
||||
if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet {
|
||||
s.Values[constants.SessionRedirect] = fwd.String()
|
||||
err = s.Save(r, rw)
|
||||
|
@ -36,10 +36,12 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
|
||||
redirectUrl = a.proxyConfig.ExternalHost
|
||||
}
|
||||
}
|
||||
s.Values[constants.SessionRedirect] = redirectUrl
|
||||
err = s.Save(r, rw)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
||||
if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet {
|
||||
s.Values[constants.SessionRedirect] = redirectUrl
|
||||
err = s.Save(r, rw)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
||||
}
|
||||
}
|
||||
|
||||
urlArgs := url.Values{
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"goauthentik.io/api/v3"
|
||||
"goauthentik.io/internal/outpost/proxyv2/application"
|
||||
"goauthentik.io/internal/outpost/proxyv2/metrics"
|
||||
sentryutils "goauthentik.io/internal/utils/sentry"
|
||||
"goauthentik.io/internal/utils/web"
|
||||
staticWeb "goauthentik.io/web"
|
||||
)
|
||||
@ -89,7 +90,7 @@ func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io/ping") {
|
||||
ps.HandlePing(rw, r)
|
||||
sentryutils.SentryNoSample(ps.HandlePing)(rw, r)
|
||||
return
|
||||
}
|
||||
a, host := ps.lookupApp(r)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/internal/utils/sentry"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@ -25,6 +26,7 @@ var (
|
||||
func RunServer() {
|
||||
m := mux.NewRouter()
|
||||
l := log.WithField("logger", "authentik.outpost.metrics")
|
||||
m.Use(sentry.SentryNoSampleMiddleware)
|
||||
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(204)
|
||||
})
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"goauthentik.io/internal/outpost/ak"
|
||||
"goauthentik.io/internal/outpost/proxyv2/application"
|
||||
"goauthentik.io/internal/outpost/proxyv2/metrics"
|
||||
sentryutils "goauthentik.io/internal/utils/sentry"
|
||||
"goauthentik.io/internal/utils/web"
|
||||
)
|
||||
|
||||
@ -65,7 +66,7 @@ func NewProxyServer(ac *ak.APIController, portOffset int) *ProxyServer {
|
||||
defaultCert: defaultCert,
|
||||
}
|
||||
globalMux.PathPrefix("/outpost.goauthentik.io/static").HandlerFunc(s.HandleStatic)
|
||||
globalMux.Path("/outpost.goauthentik.io/ping").HandlerFunc(s.HandlePing)
|
||||
globalMux.Path("/outpost.goauthentik.io/ping").HandlerFunc(sentryutils.SentryNoSample(s.HandlePing))
|
||||
rootMux.PathPrefix("/").HandlerFunc(s.Handle)
|
||||
return s
|
||||
}
|
||||
|
34
internal/utils/sentry/sentry.go
Normal file
34
internal/utils/sentry/sentry.go
Normal file
@ -0,0 +1,34 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
)
|
||||
|
||||
type contextSentryNoSample struct{}
|
||||
|
||||
func SentryNoSample(handler func(rw http.ResponseWriter, r *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.WithValue(r.Context(), contextSentryNoSample{}, true)
|
||||
handler(w, r.WithContext(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
func SentryNoSampleMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.WithValue(r.Context(), contextSentryNoSample{}, true)
|
||||
h.ServeHTTP(rw, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func SamplerFunc(defaultRate float64) sentry.TracesSamplerFunc {
|
||||
return sentry.TracesSamplerFunc(func(ctx sentry.SamplingContext) sentry.Sampled {
|
||||
data, ok := ctx.Span.Context().Value(contextSentryNoSample{}).(bool)
|
||||
if data && ok {
|
||||
return sentry.SampledFalse
|
||||
}
|
||||
return sentry.UniformTracesSampler(defaultRate).Sample(ctx)
|
||||
})
|
||||
}
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/internal/config"
|
||||
"goauthentik.io/internal/utils/sentry"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -22,6 +23,7 @@ var (
|
||||
func RunMetricsServer() {
|
||||
m := mux.NewRouter()
|
||||
l := log.WithField("logger", "authentik.router.metrics")
|
||||
m.Use(sentry.SentryNoSampleMiddleware)
|
||||
m.Path("/metrics").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
promhttp.InstrumentMetricHandler(
|
||||
prometheus.DefaultRegisterer, promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{
|
||||
@ -30,7 +32,7 @@ func RunMetricsServer() {
|
||||
).ServeHTTP(rw, r)
|
||||
|
||||
// Get upstream metrics
|
||||
re, err := http.NewRequest("GET", "http://localhost:8000/metrics/", nil)
|
||||
re, err := http.NewRequest("GET", "http://localhost:8000/-/metrics/", nil)
|
||||
if err != nil {
|
||||
l.WithError(err).Warning("failed to get upstream metrics")
|
||||
return
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"goauthentik.io/internal/utils/sentry"
|
||||
"goauthentik.io/internal/utils/web"
|
||||
)
|
||||
|
||||
@ -41,10 +42,10 @@ func (ws *WebServer) configureProxy() {
|
||||
}
|
||||
ws.proxyErrorHandler(rw, r, fmt.Errorf("proxy not running"))
|
||||
})
|
||||
ws.m.Path("/-/health/live/").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
ws.m.Path("/-/health/live/").HandlerFunc(sentry.SentryNoSample(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(204)
|
||||
})
|
||||
ws.m.PathPrefix("/").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
ws.m.PathPrefix("/").HandlerFunc(sentry.SentryNoSample(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if !ws.p.IsRunning() {
|
||||
ws.proxyErrorHandler(rw, r, fmt.Errorf("authentik core not running yet"))
|
||||
return
|
||||
@ -63,7 +64,7 @@ func (ws *WebServer) configureProxy() {
|
||||
}).Observe(float64(time.Since(before)))
|
||||
ws.log.WithField("host", web.GetHost(r)).Trace("routing to application server")
|
||||
rp.ServeHTTP(rw, r)
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (ws *WebServer) proxyErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
|
@ -38,6 +38,9 @@ if [[ "$1" == "server" ]]; then
|
||||
wait_for_db
|
||||
echo "server" > $MODE_FILE
|
||||
python -m lifecycle.migrate
|
||||
if [[ ! -z "${AUTHENTIK_BOOTSTRAP_PASSWORD}" || ! -z "${AUTHENTIK_BOOTSTRAP_TOKEN}" ]]; then
|
||||
python -m manage bootstrap_tasks
|
||||
fi
|
||||
/authentik-proxy
|
||||
elif [[ "$1" == "worker" ]]; then
|
||||
wait_for_db
|
||||
|
6
poetry.lock
generated
6
poetry.lock
generated
@ -1322,7 +1322,7 @@ tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "pylint"
|
||||
version = "2.14.0"
|
||||
version = "2.14.1"
|
||||
description = "python code static checker"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@ -3237,8 +3237,8 @@ pyjwt = [
|
||||
{file = "PyJWT-2.4.0.tar.gz", hash = "sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba"},
|
||||
]
|
||||
pylint = [
|
||||
{file = "pylint-2.14.0-py3-none-any.whl", hash = "sha256:ef64ce5d4c17b8906caeaf2c2914ef3036a3a1b7f4a86f5fbf6caff9067c5f63"},
|
||||
{file = "pylint-2.14.0.tar.gz", hash = "sha256:10d291ea5133645f73fc1b51ca137ad6531223c1461a5632a1db029a9bc033b5"},
|
||||
{file = "pylint-2.14.1-py3-none-any.whl", hash = "sha256:bb71e6d169506de585edea997e48d9ff20c0dc0e2fbc1d166bad6b640120326b"},
|
||||
{file = "pylint-2.14.1.tar.gz", hash = "sha256:549261e0762c3466cc001024c4419c08252cb8c8d40f5c2c6966fea690e7fe2a"},
|
||||
]
|
||||
pylint-django = [
|
||||
{file = "pylint-django-2.5.3.tar.gz", hash = "sha256:0ac090d106c62fe33782a1d01bda1610b761bb1c9bf5035ced9d5f23a13d8591"},
|
||||
|
@ -90,7 +90,7 @@ addopts = "-p no:celery --junitxml=unittest.xml"
|
||||
|
||||
[tool.poetry]
|
||||
name = "authentik"
|
||||
version = "2022.6.1"
|
||||
version = "2022.6.3"
|
||||
description = ""
|
||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||
|
||||
|
77
schema.yml
77
schema.yml
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2022.6.1
|
||||
version: 2022.6.3
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
@ -11862,6 +11862,14 @@ paths:
|
||||
operationId: sources_all_list
|
||||
description: Source Viewset
|
||||
parameters:
|
||||
- in: query
|
||||
name: managed
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
type: string
|
||||
- name: ordering
|
||||
required: false
|
||||
in: query
|
||||
@ -11886,6 +11894,10 @@ paths:
|
||||
description: A search term.
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: slug
|
||||
schema:
|
||||
type: string
|
||||
tags:
|
||||
- sources
|
||||
security:
|
||||
@ -19151,6 +19163,9 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
readOnly: true
|
||||
open_in_new_tab:
|
||||
type: boolean
|
||||
description: Open launch URL in a new browser tab or window.
|
||||
meta_launch_url:
|
||||
type: string
|
||||
format: uri
|
||||
@ -19190,6 +19205,9 @@ components:
|
||||
provider:
|
||||
type: integer
|
||||
nullable: true
|
||||
open_in_new_tab:
|
||||
type: boolean
|
||||
description: Open launch URL in a new browser tab or window.
|
||||
meta_launch_url:
|
||||
type: string
|
||||
format: uri
|
||||
@ -22678,6 +22696,15 @@ components:
|
||||
- $ref: '#/components/schemas/UserMatchingModeEnum'
|
||||
description: How the source determines if an existing user should be authenticated
|
||||
or a new user enrolled.
|
||||
managed:
|
||||
type: string
|
||||
nullable: true
|
||||
title: Managed by authentik
|
||||
description: Objects which are managed by authentik. These objects are created
|
||||
and updated automatically. This is flag only indicates that an object
|
||||
can be overwritten by migrations. You can still modify the objects via
|
||||
the API, but expect changes to be overwritten in a later update.
|
||||
readOnly: true
|
||||
server_uri:
|
||||
type: string
|
||||
format: uri
|
||||
@ -22740,6 +22767,7 @@ components:
|
||||
required:
|
||||
- base_dn
|
||||
- component
|
||||
- managed
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
@ -23339,9 +23367,13 @@ components:
|
||||
logout:
|
||||
type: string
|
||||
readOnly: true
|
||||
jwks:
|
||||
type: string
|
||||
readOnly: true
|
||||
required:
|
||||
- authorize
|
||||
- issuer
|
||||
- jwks
|
||||
- logout
|
||||
- provider_info
|
||||
- token
|
||||
@ -23394,6 +23426,15 @@ components:
|
||||
- $ref: '#/components/schemas/UserMatchingModeEnum'
|
||||
description: How the source determines if an existing user should be authenticated
|
||||
or a new user enrolled.
|
||||
managed:
|
||||
type: string
|
||||
nullable: true
|
||||
title: Managed by authentik
|
||||
description: Objects which are managed by authentik. These objects are created
|
||||
and updated automatically. This is flag only indicates that an object
|
||||
can be overwritten by migrations. You can still modify the objects via
|
||||
the API, but expect changes to be overwritten in a later update.
|
||||
readOnly: true
|
||||
provider_type:
|
||||
$ref: '#/components/schemas/ProviderTypeEnum'
|
||||
request_token_url:
|
||||
@ -23439,6 +23480,7 @@ components:
|
||||
- callback_url
|
||||
- component
|
||||
- consumer_key
|
||||
- managed
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
@ -26721,6 +26763,9 @@ components:
|
||||
provider:
|
||||
type: integer
|
||||
nullable: true
|
||||
open_in_new_tab:
|
||||
type: boolean
|
||||
description: Open launch URL in a new browser tab or window.
|
||||
meta_launch_url:
|
||||
type: string
|
||||
format: uri
|
||||
@ -28620,6 +28665,15 @@ components:
|
||||
- $ref: '#/components/schemas/UserMatchingModeEnum'
|
||||
description: How the source determines if an existing user should be authenticated
|
||||
or a new user enrolled.
|
||||
managed:
|
||||
type: string
|
||||
nullable: true
|
||||
title: Managed by authentik
|
||||
description: Objects which are managed by authentik. These objects are created
|
||||
and updated automatically. This is flag only indicates that an object
|
||||
can be overwritten by migrations. You can still modify the objects via
|
||||
the API, but expect changes to be overwritten in a later update.
|
||||
readOnly: true
|
||||
client_id:
|
||||
type: string
|
||||
description: Client identifier used to talk to Plex.
|
||||
@ -28637,6 +28691,7 @@ components:
|
||||
description: Plex token used to check friends
|
||||
required:
|
||||
- component
|
||||
- managed
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
@ -30011,6 +30066,15 @@ components:
|
||||
- $ref: '#/components/schemas/UserMatchingModeEnum'
|
||||
description: How the source determines if an existing user should be authenticated
|
||||
or a new user enrolled.
|
||||
managed:
|
||||
type: string
|
||||
nullable: true
|
||||
title: Managed by authentik
|
||||
description: Objects which are managed by authentik. These objects are created
|
||||
and updated automatically. This is flag only indicates that an object
|
||||
can be overwritten by migrations. You can still modify the objects via
|
||||
the API, but expect changes to be overwritten in a later update.
|
||||
readOnly: true
|
||||
pre_authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
@ -30059,6 +30123,7 @@ components:
|
||||
doesn''t log out manually. (Format: hours=1;minutes=2;seconds=3).'
|
||||
required:
|
||||
- component
|
||||
- managed
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
@ -30437,8 +30502,18 @@ components:
|
||||
- $ref: '#/components/schemas/UserMatchingModeEnum'
|
||||
description: How the source determines if an existing user should be authenticated
|
||||
or a new user enrolled.
|
||||
managed:
|
||||
type: string
|
||||
nullable: true
|
||||
title: Managed by authentik
|
||||
description: Objects which are managed by authentik. These objects are created
|
||||
and updated automatically. This is flag only indicates that an object
|
||||
can be overwritten by migrations. You can still modify the objects via
|
||||
the API, but expect changes to be overwritten in a later update.
|
||||
readOnly: true
|
||||
required:
|
||||
- component
|
||||
- managed
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
|
266
web/package-lock.json
generated
266
web/package-lock.json
generated
@ -14,15 +14,15 @@
|
||||
"@babel/plugin-transform-runtime": "^7.18.2",
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@formatjs/intl-listformat": "^7.0.1",
|
||||
"@formatjs/intl-listformat": "^7.0.2",
|
||||
"@fortawesome/fontawesome-free": "^6.1.1",
|
||||
"@goauthentik/api": "^2022.5.3-1654278040",
|
||||
"@goauthentik/api": "^2022.6.1-1654625853",
|
||||
"@jackfranklin/rollup-plugin-markdown": "^0.3.0",
|
||||
"@lingui/cli": "^3.13.3",
|
||||
"@lingui/core": "^3.13.3",
|
||||
"@lingui/detect-locale": "^3.13.3",
|
||||
"@lingui/macro": "^3.13.3",
|
||||
"@patternfly/patternfly": "^4.194.4",
|
||||
"@patternfly/patternfly": "^4.196.7",
|
||||
"@polymer/iron-form": "^3.0.1",
|
||||
"@polymer/paper-input": "^3.2.1",
|
||||
"@rollup/plugin-babel": "^5.3.1",
|
||||
@ -37,8 +37,8 @@
|
||||
"@types/chart.js": "^2.9.37",
|
||||
"@types/codemirror": "5.60.5",
|
||||
"@types/grecaptcha": "^3.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"@webcomponents/webcomponentsjs": "^2.6.0",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"base64-js": "^1.5.1",
|
||||
@ -47,7 +47,7 @@
|
||||
"codemirror": "^5.65.5",
|
||||
"construct-style-sheets-polyfill": "^3.1.0",
|
||||
"country-flag-icons": "^1.5.5",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint": "^8.17.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-custom-elements": "0.0.6",
|
||||
"eslint-plugin-lit": "^1.6.1",
|
||||
@ -65,7 +65,7 @@
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.2",
|
||||
"typescript": "^4.7.3",
|
||||
"webcomponent-qr-code": "^1.0.6",
|
||||
"yaml": "^2.1.1"
|
||||
}
|
||||
@ -1733,28 +1733,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.6.tgz",
|
||||
"integrity": "sha512-6TcI+IroIK+GTWXBJ643LBJklmCBsqLt1sUTGWfzdBcI5Y6b1L1iamrJB1B5OAQLnhzWveLbmzPYHYsFEZfeig==",
|
||||
"version": "1.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.7.tgz",
|
||||
"integrity": "sha512-uNaok4XWMJBtPZk/veTDamFCm5HeWJUk2jwoVfH5/+wenQ60QHjH6T3UQ0GOOCz9jpKmed7vqOri7xSf//Dt7g==",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "0.2.27",
|
||||
"@formatjs/intl-localematcher": "0.2.28",
|
||||
"tslib": "2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-listformat": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.0.1.tgz",
|
||||
"integrity": "sha512-sgE4B9+mu3ZF77vhZB0tR8O3evvcPA//WbA/8UJ21XOrSzfY6RXhSbvDfSd7Y5iEeBu+2C+5YxDuAwLnvq2SnQ==",
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.0.2.tgz",
|
||||
"integrity": "sha512-K+HXrYIvEcAH/dS8XXnSHQYC/z4w0eHjPlDx43HOoDY87/xV7rpHxFVXWXTgwLYC6iAPUO72Y/AaT9iq873juw==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.11.6",
|
||||
"@formatjs/intl-localematcher": "0.2.27",
|
||||
"@formatjs/ecma402-abstract": "1.11.7",
|
||||
"@formatjs/intl-localematcher": "0.2.28",
|
||||
"tslib": "2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.2.27",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.27.tgz",
|
||||
"integrity": "sha512-XHYcVas2ebDTh3VtfdluvbTjqyMUHqFHARnuJo5KYF/0MKOTmozVSK7PJGnu1IEHdmRdTWuG6TB+2RnkasaxVw==",
|
||||
"version": "0.2.28",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.28.tgz",
|
||||
"integrity": "sha512-FLsc6Gifs1np/8HnCn/7Q+lHMmenrD5fuDhRT82yj0gi9O19kfaFwjQUw1gZsyILuRyT93GuzdifHj7TKRhBcw==",
|
||||
"dependencies": {
|
||||
"tslib": "2.4.0"
|
||||
}
|
||||
@ -1769,9 +1769,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@goauthentik/api": {
|
||||
"version": "2022.5.3-1654278040",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.5.3-1654278040.tgz",
|
||||
"integrity": "sha512-cVGJJZzivBC1xHUf1QmQDVWEW6A5uxyZOm977YXhisOKnVAMRSMXz3tNQ+NVJq/Fb1Z+yD3ffGAGcOjThGGoXA=="
|
||||
"version": "2022.6.1-1654625853",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.6.1-1654625853.tgz",
|
||||
"integrity": "sha512-VW02zziDmk92hxSDxWU/NUptVnPcZHi1iSSX5CWevNmvrf4kU+j0yBRrlXaWCE/nelgQC2oFiPbwI5FN9MypBg=="
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.9.2",
|
||||
@ -2235,9 +2235,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@patternfly/patternfly": {
|
||||
"version": "4.194.4",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.194.4.tgz",
|
||||
"integrity": "sha512-SJxr502v0xXk1N5OiPLunD9pdKvHp5XXJLXcD5lIPrimjjUcy46m48X8YONjDvnC/Y5xV92UI2KxoCVucE34eA=="
|
||||
"version": "4.196.7",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.196.7.tgz",
|
||||
"integrity": "sha512-hA7Oww411e1p0/IXjC1I+4/1NNis9V+NVBxfUIpRwyuLbCIDHBdtMu2qAPLdKxXjuibV9EE6ZdlT7ra/kcFuJQ=="
|
||||
},
|
||||
"node_modules/@polymer/font-roboto": {
|
||||
"version": "3.0.2",
|
||||
@ -2883,13 +2883,13 @@
|
||||
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.0.tgz",
|
||||
"integrity": "sha512-DDrIA7GXtmHXr1VCcx9HivA39eprYBIFxbQEHI6NyraRDxCGpxAFiYQAT/1Y0vh1C+o2vfBiy4IuPoXxtTZCAQ==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.1.tgz",
|
||||
"integrity": "sha512-6dM5NKT57ZduNnJfpY81Phe9nc9wolnMCnknb1im6brWi1RYv84nbMS3olJa27B6+irUVV1X/Wb+Am0FjJdGFw==",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "5.27.0",
|
||||
"@typescript-eslint/type-utils": "5.27.0",
|
||||
"@typescript-eslint/utils": "5.27.0",
|
||||
"@typescript-eslint/scope-manager": "5.27.1",
|
||||
"@typescript-eslint/type-utils": "5.27.1",
|
||||
"@typescript-eslint/utils": "5.27.1",
|
||||
"debug": "^4.3.4",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"ignore": "^5.2.0",
|
||||
@ -2929,13 +2929,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.0.tgz",
|
||||
"integrity": "sha512-8oGjQF46c52l7fMiPPvX4It3u3V3JipssqDfHQ2hcR0AeR8Zge+OYyKUCm5b70X72N1qXt0qgHenwN6Gc2SXZA==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.1.tgz",
|
||||
"integrity": "sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ==",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "5.27.0",
|
||||
"@typescript-eslint/types": "5.27.0",
|
||||
"@typescript-eslint/typescript-estree": "5.27.0",
|
||||
"@typescript-eslint/scope-manager": "5.27.1",
|
||||
"@typescript-eslint/types": "5.27.1",
|
||||
"@typescript-eslint/typescript-estree": "5.27.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -2955,12 +2955,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.0.tgz",
|
||||
"integrity": "sha512-VnykheBQ/sHd1Vt0LJ1JLrMH1GzHO+SzX6VTXuStISIsvRiurue/eRkTqSrG0CexHQgKG8shyJfR4o5VYioB9g==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz",
|
||||
"integrity": "sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg==",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.27.0",
|
||||
"@typescript-eslint/visitor-keys": "5.27.0"
|
||||
"@typescript-eslint/types": "5.27.1",
|
||||
"@typescript-eslint/visitor-keys": "5.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@ -2971,11 +2971,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.0.tgz",
|
||||
"integrity": "sha512-vpTvRRchaf628Hb/Xzfek+85o//zEUotr1SmexKvTfs7czXfYjXVT/a5yDbpzLBX1rhbqxjDdr1Gyo0x1Fc64g==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.1.tgz",
|
||||
"integrity": "sha512-+UC1vVUWaDHRnC2cQrCJ4QtVjpjjCgjNFpg8b03nERmkHv9JV9X5M19D7UFMd+/G7T/sgFwX2pGmWK38rqyvXw==",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/utils": "5.27.0",
|
||||
"@typescript-eslint/utils": "5.27.1",
|
||||
"debug": "^4.3.4",
|
||||
"tsutils": "^3.21.0"
|
||||
},
|
||||
@ -2996,9 +2996,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.0.tgz",
|
||||
"integrity": "sha512-lY6C7oGm9a/GWhmUDOs3xAVRz4ty/XKlQ2fOLr8GAIryGn0+UBOoJDWyHer3UgrHkenorwvBnphhP+zPmzmw0A==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.1.tgz",
|
||||
"integrity": "sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg==",
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
@ -3008,12 +3008,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.0.tgz",
|
||||
"integrity": "sha512-QywPMFvgZ+MHSLRofLI7BDL+UczFFHyj0vF5ibeChDAJgdTV8k4xgEwF0geFhVlPc1p8r70eYewzpo6ps+9LJQ==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz",
|
||||
"integrity": "sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw==",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.27.0",
|
||||
"@typescript-eslint/visitor-keys": "5.27.0",
|
||||
"@typescript-eslint/types": "5.27.1",
|
||||
"@typescript-eslint/visitor-keys": "5.27.1",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@ -3048,14 +3048,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.0.tgz",
|
||||
"integrity": "sha512-nZvCrkIJppym7cIbP3pOwIkAefXOmfGPnCM0LQfzNaKxJHI6VjI8NC662uoiPlaf5f6ymkTy9C3NQXev2mdXmA==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.1.tgz",
|
||||
"integrity": "sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w==",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@typescript-eslint/scope-manager": "5.27.0",
|
||||
"@typescript-eslint/types": "5.27.0",
|
||||
"@typescript-eslint/typescript-estree": "5.27.0",
|
||||
"@typescript-eslint/scope-manager": "5.27.1",
|
||||
"@typescript-eslint/types": "5.27.1",
|
||||
"@typescript-eslint/typescript-estree": "5.27.1",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-utils": "^3.0.0"
|
||||
},
|
||||
@ -3071,11 +3071,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.0.tgz",
|
||||
"integrity": "sha512-46cYrteA2MrIAjv9ai44OQDUoCZyHeGIc4lsjCUX2WT6r4C+kidz1bNiR4017wHOPUythYeH+Sc7/cFP97KEAA==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz",
|
||||
"integrity": "sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ==",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.27.0",
|
||||
"@typescript-eslint/types": "5.27.1",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -4165,9 +4165,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.16.0.tgz",
|
||||
"integrity": "sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==",
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz",
|
||||
"integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==",
|
||||
"dependencies": {
|
||||
"@eslint/eslintrc": "^1.3.0",
|
||||
"@humanwhocodes/config-array": "^0.9.2",
|
||||
@ -8476,9 +8476,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.7.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
|
||||
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==",
|
||||
"version": "4.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz",
|
||||
"integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@ -10075,28 +10075,28 @@
|
||||
}
|
||||
},
|
||||
"@formatjs/ecma402-abstract": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.6.tgz",
|
||||
"integrity": "sha512-6TcI+IroIK+GTWXBJ643LBJklmCBsqLt1sUTGWfzdBcI5Y6b1L1iamrJB1B5OAQLnhzWveLbmzPYHYsFEZfeig==",
|
||||
"version": "1.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.7.tgz",
|
||||
"integrity": "sha512-uNaok4XWMJBtPZk/veTDamFCm5HeWJUk2jwoVfH5/+wenQ60QHjH6T3UQ0GOOCz9jpKmed7vqOri7xSf//Dt7g==",
|
||||
"requires": {
|
||||
"@formatjs/intl-localematcher": "0.2.27",
|
||||
"@formatjs/intl-localematcher": "0.2.28",
|
||||
"tslib": "2.4.0"
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-listformat": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.0.1.tgz",
|
||||
"integrity": "sha512-sgE4B9+mu3ZF77vhZB0tR8O3evvcPA//WbA/8UJ21XOrSzfY6RXhSbvDfSd7Y5iEeBu+2C+5YxDuAwLnvq2SnQ==",
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.0.2.tgz",
|
||||
"integrity": "sha512-K+HXrYIvEcAH/dS8XXnSHQYC/z4w0eHjPlDx43HOoDY87/xV7rpHxFVXWXTgwLYC6iAPUO72Y/AaT9iq873juw==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.11.6",
|
||||
"@formatjs/intl-localematcher": "0.2.27",
|
||||
"@formatjs/ecma402-abstract": "1.11.7",
|
||||
"@formatjs/intl-localematcher": "0.2.28",
|
||||
"tslib": "2.4.0"
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-localematcher": {
|
||||
"version": "0.2.27",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.27.tgz",
|
||||
"integrity": "sha512-XHYcVas2ebDTh3VtfdluvbTjqyMUHqFHARnuJo5KYF/0MKOTmozVSK7PJGnu1IEHdmRdTWuG6TB+2RnkasaxVw==",
|
||||
"version": "0.2.28",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.28.tgz",
|
||||
"integrity": "sha512-FLsc6Gifs1np/8HnCn/7Q+lHMmenrD5fuDhRT82yj0gi9O19kfaFwjQUw1gZsyILuRyT93GuzdifHj7TKRhBcw==",
|
||||
"requires": {
|
||||
"tslib": "2.4.0"
|
||||
}
|
||||
@ -10107,9 +10107,9 @@
|
||||
"integrity": "sha512-J/3yg2AIXc9wznaVqpHVX3Wa5jwKovVF0AMYSnbmcXTiL3PpRPfF58pzWucCwEiCJBp+hCNRLWClTomD8SseKg=="
|
||||
},
|
||||
"@goauthentik/api": {
|
||||
"version": "2022.5.3-1654278040",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.5.3-1654278040.tgz",
|
||||
"integrity": "sha512-cVGJJZzivBC1xHUf1QmQDVWEW6A5uxyZOm977YXhisOKnVAMRSMXz3tNQ+NVJq/Fb1Z+yD3ffGAGcOjThGGoXA=="
|
||||
"version": "2022.6.1-1654625853",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.6.1-1654625853.tgz",
|
||||
"integrity": "sha512-VW02zziDmk92hxSDxWU/NUptVnPcZHi1iSSX5CWevNmvrf4kU+j0yBRrlXaWCE/nelgQC2oFiPbwI5FN9MypBg=="
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.9.2",
|
||||
@ -10452,9 +10452,9 @@
|
||||
}
|
||||
},
|
||||
"@patternfly/patternfly": {
|
||||
"version": "4.194.4",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.194.4.tgz",
|
||||
"integrity": "sha512-SJxr502v0xXk1N5OiPLunD9pdKvHp5XXJLXcD5lIPrimjjUcy46m48X8YONjDvnC/Y5xV92UI2KxoCVucE34eA=="
|
||||
"version": "4.196.7",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.196.7.tgz",
|
||||
"integrity": "sha512-hA7Oww411e1p0/IXjC1I+4/1NNis9V+NVBxfUIpRwyuLbCIDHBdtMu2qAPLdKxXjuibV9EE6ZdlT7ra/kcFuJQ=="
|
||||
},
|
||||
"@polymer/font-roboto": {
|
||||
"version": "3.0.2",
|
||||
@ -11027,13 +11027,13 @@
|
||||
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.0.tgz",
|
||||
"integrity": "sha512-DDrIA7GXtmHXr1VCcx9HivA39eprYBIFxbQEHI6NyraRDxCGpxAFiYQAT/1Y0vh1C+o2vfBiy4IuPoXxtTZCAQ==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.1.tgz",
|
||||
"integrity": "sha512-6dM5NKT57ZduNnJfpY81Phe9nc9wolnMCnknb1im6brWi1RYv84nbMS3olJa27B6+irUVV1X/Wb+Am0FjJdGFw==",
|
||||
"requires": {
|
||||
"@typescript-eslint/scope-manager": "5.27.0",
|
||||
"@typescript-eslint/type-utils": "5.27.0",
|
||||
"@typescript-eslint/utils": "5.27.0",
|
||||
"@typescript-eslint/scope-manager": "5.27.1",
|
||||
"@typescript-eslint/type-utils": "5.27.1",
|
||||
"@typescript-eslint/utils": "5.27.1",
|
||||
"debug": "^4.3.4",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"ignore": "^5.2.0",
|
||||
@ -11053,47 +11053,47 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.0.tgz",
|
||||
"integrity": "sha512-8oGjQF46c52l7fMiPPvX4It3u3V3JipssqDfHQ2hcR0AeR8Zge+OYyKUCm5b70X72N1qXt0qgHenwN6Gc2SXZA==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.1.tgz",
|
||||
"integrity": "sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ==",
|
||||
"requires": {
|
||||
"@typescript-eslint/scope-manager": "5.27.0",
|
||||
"@typescript-eslint/types": "5.27.0",
|
||||
"@typescript-eslint/typescript-estree": "5.27.0",
|
||||
"@typescript-eslint/scope-manager": "5.27.1",
|
||||
"@typescript-eslint/types": "5.27.1",
|
||||
"@typescript-eslint/typescript-estree": "5.27.1",
|
||||
"debug": "^4.3.4"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.0.tgz",
|
||||
"integrity": "sha512-VnykheBQ/sHd1Vt0LJ1JLrMH1GzHO+SzX6VTXuStISIsvRiurue/eRkTqSrG0CexHQgKG8shyJfR4o5VYioB9g==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz",
|
||||
"integrity": "sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg==",
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.27.0",
|
||||
"@typescript-eslint/visitor-keys": "5.27.0"
|
||||
"@typescript-eslint/types": "5.27.1",
|
||||
"@typescript-eslint/visitor-keys": "5.27.1"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/type-utils": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.0.tgz",
|
||||
"integrity": "sha512-vpTvRRchaf628Hb/Xzfek+85o//zEUotr1SmexKvTfs7czXfYjXVT/a5yDbpzLBX1rhbqxjDdr1Gyo0x1Fc64g==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.1.tgz",
|
||||
"integrity": "sha512-+UC1vVUWaDHRnC2cQrCJ4QtVjpjjCgjNFpg8b03nERmkHv9JV9X5M19D7UFMd+/G7T/sgFwX2pGmWK38rqyvXw==",
|
||||
"requires": {
|
||||
"@typescript-eslint/utils": "5.27.0",
|
||||
"@typescript-eslint/utils": "5.27.1",
|
||||
"debug": "^4.3.4",
|
||||
"tsutils": "^3.21.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.0.tgz",
|
||||
"integrity": "sha512-lY6C7oGm9a/GWhmUDOs3xAVRz4ty/XKlQ2fOLr8GAIryGn0+UBOoJDWyHer3UgrHkenorwvBnphhP+zPmzmw0A=="
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.1.tgz",
|
||||
"integrity": "sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg=="
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.0.tgz",
|
||||
"integrity": "sha512-QywPMFvgZ+MHSLRofLI7BDL+UczFFHyj0vF5ibeChDAJgdTV8k4xgEwF0geFhVlPc1p8r70eYewzpo6ps+9LJQ==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz",
|
||||
"integrity": "sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw==",
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.27.0",
|
||||
"@typescript-eslint/visitor-keys": "5.27.0",
|
||||
"@typescript-eslint/types": "5.27.1",
|
||||
"@typescript-eslint/visitor-keys": "5.27.1",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@ -11112,24 +11112,24 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/utils": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.0.tgz",
|
||||
"integrity": "sha512-nZvCrkIJppym7cIbP3pOwIkAefXOmfGPnCM0LQfzNaKxJHI6VjI8NC662uoiPlaf5f6ymkTy9C3NQXev2mdXmA==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.1.tgz",
|
||||
"integrity": "sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w==",
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@typescript-eslint/scope-manager": "5.27.0",
|
||||
"@typescript-eslint/types": "5.27.0",
|
||||
"@typescript-eslint/typescript-estree": "5.27.0",
|
||||
"@typescript-eslint/scope-manager": "5.27.1",
|
||||
"@typescript-eslint/types": "5.27.1",
|
||||
"@typescript-eslint/typescript-estree": "5.27.1",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-utils": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "5.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.0.tgz",
|
||||
"integrity": "sha512-46cYrteA2MrIAjv9ai44OQDUoCZyHeGIc4lsjCUX2WT6r4C+kidz1bNiR4017wHOPUythYeH+Sc7/cFP97KEAA==",
|
||||
"version": "5.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz",
|
||||
"integrity": "sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ==",
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.27.0",
|
||||
"@typescript-eslint/types": "5.27.1",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -11909,9 +11909,9 @@
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"eslint": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.16.0.tgz",
|
||||
"integrity": "sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==",
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz",
|
||||
"integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==",
|
||||
"requires": {
|
||||
"@eslint/eslintrc": "^1.3.0",
|
||||
"@humanwhocodes/config-array": "^0.9.2",
|
||||
@ -15155,9 +15155,9 @@
|
||||
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.7.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
|
||||
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A=="
|
||||
"version": "4.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz",
|
||||
"integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA=="
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.14.1",
|
||||
|
@ -57,15 +57,15 @@
|
||||
"@babel/plugin-transform-runtime": "^7.18.2",
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@formatjs/intl-listformat": "^7.0.1",
|
||||
"@formatjs/intl-listformat": "^7.0.2",
|
||||
"@fortawesome/fontawesome-free": "^6.1.1",
|
||||
"@goauthentik/api": "^2022.5.3-1654278040",
|
||||
"@goauthentik/api": "^2022.6.1-1654625853",
|
||||
"@jackfranklin/rollup-plugin-markdown": "^0.3.0",
|
||||
"@lingui/cli": "^3.13.3",
|
||||
"@lingui/core": "^3.13.3",
|
||||
"@lingui/detect-locale": "^3.13.3",
|
||||
"@lingui/macro": "^3.13.3",
|
||||
"@patternfly/patternfly": "^4.194.4",
|
||||
"@patternfly/patternfly": "^4.196.7",
|
||||
"@polymer/iron-form": "^3.0.1",
|
||||
"@polymer/paper-input": "^3.2.1",
|
||||
"@rollup/plugin-babel": "^5.3.1",
|
||||
@ -80,8 +80,8 @@
|
||||
"@types/chart.js": "^2.9.37",
|
||||
"@types/codemirror": "5.60.5",
|
||||
"@types/grecaptcha": "^3.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"@webcomponents/webcomponentsjs": "^2.6.0",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"base64-js": "^1.5.1",
|
||||
@ -90,7 +90,7 @@
|
||||
"codemirror": "^5.65.5",
|
||||
"construct-style-sheets-polyfill": "^3.1.0",
|
||||
"country-flag-icons": "^1.5.5",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint": "^8.17.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-custom-elements": "0.0.6",
|
||||
"eslint-plugin-lit": "^1.6.1",
|
||||
@ -108,7 +108,7 @@
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.2",
|
||||
"typescript": "^4.7.3",
|
||||
"webcomponent-qr-code": "^1.0.6",
|
||||
"yaml": "^2.1.1"
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
||||
export const ERROR_CLASS = "pf-m-danger";
|
||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
export const CURRENT_CLASS = "pf-m-current";
|
||||
export const VERSION = "2022.6.1";
|
||||
export const VERSION = "2022.6.3";
|
||||
export const TITLE_DEFAULT = "authentik";
|
||||
export const ROUTE_SEPARATOR = ";";
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import "../EmptyState";
|
||||
|
||||
export const SLUG_REGEX = "[-a-zA-Z0-9_]+";
|
||||
export const ID_REGEX = "\\d+";
|
||||
export const UUID_REGEX = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
||||
@ -47,7 +49,10 @@ export class Route {
|
||||
|
||||
render(args: RouteArgs): TemplateResult {
|
||||
if (this.callback) {
|
||||
return html`${until(this.callback(args))}`;
|
||||
return html`${until(
|
||||
this.callback(args),
|
||||
html`<ak-empty-state ?loading=${true}></ak-empty-state>`,
|
||||
)}`;
|
||||
}
|
||||
if (this.element) {
|
||||
return this.element;
|
||||
|
@ -103,12 +103,11 @@ export class RouterOutlet extends LitElement {
|
||||
});
|
||||
if (!matchedRoute) {
|
||||
console.debug(`authentik/router: route "${activeUrl}" not defined`);
|
||||
const route = new Route(
|
||||
RegExp(""),
|
||||
html`<div class="pf-c-page__main">
|
||||
const route = new Route(RegExp(""), async () => {
|
||||
return html`<div class="pf-c-page__main">
|
||||
<ak-router-404 url=${activeUrl}></ak-router-404>
|
||||
</div>`,
|
||||
);
|
||||
</div>`;
|
||||
});
|
||||
matchedRoute = new RouteMatch(route);
|
||||
matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {};
|
||||
matchedRoute.fullUrl = activeUrl;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, LitElement, TemplateResult, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import AKGlobal from "../../authentik.css";
|
||||
@ -137,6 +137,9 @@ export abstract class Table<T> extends LitElement {
|
||||
@property({ attribute: false })
|
||||
expandedElements: T[] = [];
|
||||
|
||||
@state()
|
||||
hasError?: Error;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
@ -170,6 +173,7 @@ export abstract class Table<T> extends LitElement {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
this.data = await this.apiEndpoint(this.page);
|
||||
this.hasError = undefined;
|
||||
this.page = this.data.pagination.current;
|
||||
const newSelected: T[] = [];
|
||||
const newExpanded: T[] = [];
|
||||
@ -204,8 +208,9 @@ export abstract class Table<T> extends LitElement {
|
||||
this.isLoading = false;
|
||||
this.selectedElements = newSelected;
|
||||
this.expandedElements = newExpanded;
|
||||
} catch {
|
||||
} catch (ex) {
|
||||
this.isLoading = false;
|
||||
this.hasError = ex as Error;
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,7 +240,16 @@ export abstract class Table<T> extends LitElement {
|
||||
</tbody>`;
|
||||
}
|
||||
|
||||
renderError(): TemplateResult {
|
||||
return html`<ak-empty-state header="${t`Failed to fetch objects.`}" icon="fa-times">
|
||||
<div slot="body">${this.hasError?.toString()}</div>
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
|
||||
private renderRows(): TemplateResult[] | undefined {
|
||||
if (this.hasError) {
|
||||
return [this.renderEmpty(this.renderError())];
|
||||
}
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage<
|
||||
@property({ type: Boolean })
|
||||
showBackButton = false;
|
||||
|
||||
transformedCredentialRequestOptions?: PublicKeyCredentialRequestOptions;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
@ -55,19 +57,12 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage<
|
||||
}
|
||||
|
||||
async authenticate(): Promise<void> {
|
||||
// convert certain members of the PublicKeyCredentialRequestOptions into
|
||||
// byte arrays as expected by the spec.
|
||||
const credentialRequestOptions = this.deviceChallenge
|
||||
?.challenge as PublicKeyCredentialRequestOptions;
|
||||
const transformedCredentialRequestOptions =
|
||||
transformCredentialRequestOptions(credentialRequestOptions);
|
||||
|
||||
// request the authenticator to create an assertion signature using the
|
||||
// credential private key
|
||||
let assertion;
|
||||
try {
|
||||
assertion = await navigator.credentials.get({
|
||||
publicKey: transformedCredentialRequestOptions,
|
||||
publicKey: this.transformedCredentialRequestOptions,
|
||||
});
|
||||
if (!assertion) {
|
||||
throw new Error(t`Assertions is empty`);
|
||||
@ -93,6 +88,12 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage<
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
// convert certain members of the PublicKeyCredentialRequestOptions into
|
||||
// byte arrays as expected by the spec.
|
||||
const credentialRequestOptions = this.deviceChallenge
|
||||
?.challenge as PublicKeyCredentialRequestOptions;
|
||||
this.transformedCredentialRequestOptions =
|
||||
transformCredentialRequestOptions(credentialRequestOptions);
|
||||
this.authenticateWrapper();
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,8 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
||||
@property()
|
||||
registerMessage = "";
|
||||
|
||||
publicKeyCredentialCreateOptions?: PublicKeyCredentialCreationOptions;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFLogin, PFFormControl, PFForm, PFTitle, PFButton, AKGlobal];
|
||||
}
|
||||
@ -47,18 +49,11 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
||||
if (!this.challenge) {
|
||||
return;
|
||||
}
|
||||
// convert certain members of the PublicKeyCredentialCreateOptions into
|
||||
// byte arrays as expected by the spec.
|
||||
const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(
|
||||
this.challenge?.registration as PublicKeyCredentialCreationOptions,
|
||||
this.challenge?.registration.user.id,
|
||||
);
|
||||
|
||||
// request the authenticator(s) to create a new credential keypair.
|
||||
let credential;
|
||||
try {
|
||||
credential = (await navigator.credentials.create({
|
||||
publicKey: publicKeyCredentialCreateOptions,
|
||||
publicKey: this.publicKeyCredentialCreateOptions,
|
||||
})) as PublicKeyCredential;
|
||||
if (!credential) {
|
||||
throw new Error("Credential is empty");
|
||||
@ -98,6 +93,12 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
// convert certain members of the PublicKeyCredentialCreateOptions into
|
||||
// byte arrays as expected by the spec.
|
||||
this.publicKeyCredentialCreateOptions = transformCredentialCreateOptions(
|
||||
this.challenge?.registration as PublicKeyCredentialCreationOptions,
|
||||
this.challenge?.registration.user.id,
|
||||
);
|
||||
this.registerWrapper();
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
UserFieldsEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import "../../../elements/Divider";
|
||||
import "../../../elements/EmptyState";
|
||||
import "../../../elements/forms/FormElement";
|
||||
import { BaseStage } from "../base";
|
||||
@ -240,7 +241,6 @@ export class IdentificationStage extends BaseStage<
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="${t`Password`}"
|
||||
autofocus=""
|
||||
autocomplete="current-password"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
@ -258,14 +258,15 @@ export class IdentificationStage extends BaseStage<
|
||||
</button>
|
||||
</div>
|
||||
${this.challenge.passwordlessUrl
|
||||
? html`<div class="pf-c-form__group pf-m-action">
|
||||
<a
|
||||
href=${this.challenge.passwordlessUrl}
|
||||
class="pf-c-button pf-m-secondary pf-m-block"
|
||||
>
|
||||
${t`Use a security key`}
|
||||
</a>
|
||||
</div>`
|
||||
? html`<ak-divider>${t`Or`}</ak-divider>
|
||||
<div>
|
||||
<a
|
||||
href=${this.challenge.passwordlessUrl}
|
||||
class="pf-c-button pf-m-secondary pf-m-block"
|
||||
>
|
||||
${t`Use a security key`}
|
||||
</a>
|
||||
</div>`
|
||||
: html``}`;
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ export class UserInterface extends LitElement {
|
||||
tenant: CurrentTenant = DefaultTenant;
|
||||
|
||||
@property({ type: Number })
|
||||
notificationsCount = -1;
|
||||
notificationsCount = 0;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
|
@ -62,6 +62,7 @@ msgstr "(Format: hours=-1;minutes=-2;seconds=-3)."
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
@ -2100,6 +2101,10 @@ msgstr "Löschen von {0} fehlgeschlagen: {1}"
|
||||
msgid "Failed to disconnected source: {exc}"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "Failed to fetch objects."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserActiveForm.ts
|
||||
msgid "Failed to update {0}: {1}"
|
||||
msgstr "Aktualisieren von {0} fehlgeschlagen: {1}"
|
||||
@ -2536,6 +2541,10 @@ msgstr "Kennung"
|
||||
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "If checked, the launch URL will open in a new browser tab or window from the user's application library."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage."
|
||||
msgstr ""
|
||||
@ -2737,6 +2746,10 @@ msgstr "Ausstellermodus"
|
||||
msgid "JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
msgid "JWKS URL"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT Algorithmus"
|
||||
|
||||
@ -3604,6 +3617,10 @@ msgstr "API-Browser öffnen"
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr "Öffne Anwendung"
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "Open in new tab"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Open issue on GitHub..."
|
||||
msgstr "Offenes Problem auf GitHub..."
|
||||
@ -3663,6 +3680,10 @@ msgstr "Legen Sie optional den Wert „FriendlyName“ des Assertion-Attributs f
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr "Setzen Sie dies optional auf Ihre übergeordnete Domäne, wenn Sie die Authentifizierung und Autorisierung auf Domänenebene durchführen möchten. Wenn Sie Anwendungen als app1.domain.tld, app2.domain.tld ausführen, setzen Sie dies auf „domain.tld“."
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
msgid "Or"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
@ -4264,8 +4285,8 @@ msgstr "Erforderlich"
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr "Erforderlich. 150 Zeichen oder weniger. Nur Buchstaben, Zahlen und @/./+/-/_."
|
||||
#~ msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
#~ msgstr "Erforderlich. 150 Zeichen oder weniger. Nur Buchstaben, Zahlen und @/./+/-/_."
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "Reset Password"
|
||||
@ -6189,6 +6210,11 @@ msgstr "Avatar des Benutzers"
|
||||
msgid "User's display name."
|
||||
msgstr "Anzeigename"
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "User's primary identifier. 150 characters or fewer."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User(s)"
|
||||
|
@ -46,6 +46,7 @@ msgstr "(Format: hours=1;minutes=2;seconds=3)."
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
@ -2135,6 +2136,10 @@ msgstr "Failed to delete {0}: {1}"
|
||||
msgid "Failed to disconnected source: {exc}"
|
||||
msgstr "Failed to disconnected source: {exc}"
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "Failed to fetch objects."
|
||||
msgstr "Failed to fetch objects."
|
||||
|
||||
#: src/pages/users/UserActiveForm.ts
|
||||
msgid "Failed to update {0}: {1}"
|
||||
msgstr "Failed to update {0}: {1}"
|
||||
@ -2578,6 +2583,10 @@ msgstr "Identifier"
|
||||
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
|
||||
msgstr "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "If checked, the launch URL will open in a new browser tab or window from the user's application library."
|
||||
msgstr "If checked, the launch URL will open in a new browser tab or window from the user's application library."
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage."
|
||||
msgstr "If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage."
|
||||
@ -2786,6 +2795,10 @@ msgstr "Issuer mode"
|
||||
msgid "JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source."
|
||||
msgstr "JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source."
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
msgid "JWKS URL"
|
||||
msgstr "JWKS URL"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT Algorithm"
|
||||
@ -3663,6 +3676,10 @@ msgstr "Open API Browser"
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr "Open application"
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "Open in new tab"
|
||||
msgstr "Open in new tab"
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Open issue on GitHub..."
|
||||
msgstr "Open issue on GitHub..."
|
||||
@ -3723,6 +3740,10 @@ msgstr "Optionally set the 'FriendlyName' value of the Assertion attribute."
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
msgid "Or"
|
||||
msgstr "Or"
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
@ -4342,8 +4363,8 @@ msgstr "Required."
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
#~ msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
#~ msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "Reset Password"
|
||||
@ -6315,6 +6336,11 @@ msgstr "User's avatar"
|
||||
msgid "User's display name."
|
||||
msgstr "User's display name."
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "User's primary identifier. 150 characters or fewer."
|
||||
msgstr "User's primary identifier. 150 characters or fewer."
|
||||
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User(s)"
|
||||
|
@ -49,6 +49,7 @@ msgstr "(Formato: horas = 1; minutos = 2; segundos = 3)."
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
@ -2091,6 +2092,10 @@ msgstr "No se pudo eliminar {0}: {1}"
|
||||
msgid "Failed to disconnected source: {exc}"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "Failed to fetch objects."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserActiveForm.ts
|
||||
msgid "Failed to update {0}: {1}"
|
||||
msgstr "No se pudo actualizar {0}: {1}"
|
||||
@ -2527,6 +2532,10 @@ msgstr "Identificador"
|
||||
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "If checked, the launch URL will open in a new browser tab or window from the user's application library."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage."
|
||||
msgstr ""
|
||||
@ -2730,6 +2739,10 @@ msgstr "Modo emisor"
|
||||
msgid "JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
msgid "JWKS URL"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "algoritmo JWT"
|
||||
|
||||
@ -3597,6 +3610,10 @@ msgstr "Abrir navegador de API"
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr "Solicitud abierta"
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "Open in new tab"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Open issue on GitHub..."
|
||||
msgstr "Problema abierto en GitHub..."
|
||||
@ -3656,6 +3673,10 @@ msgstr "Si lo desea, defina el valor «FriendlyName» del atributo Assertion."
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr "Si lo desea, configúrelo en su dominio principal, si desea que la autenticación y la autorización se realicen a nivel de dominio. Si ejecuta aplicaciones como app1.domain.tld, app2.domain.tld, defina esto en «domain.tld»."
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
msgid "Or"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
@ -4257,8 +4278,8 @@ msgstr "Necesario."
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr "Obligatorio. 150 caracteres o menos. Solo letras, dígitos y @/./+/-/_."
|
||||
#~ msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
#~ msgstr "Obligatorio. 150 caracteres o menos. Solo letras, dígitos y @/./+/-/_."
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "Reset Password"
|
||||
@ -6183,6 +6204,11 @@ msgstr "Avatar del usuario"
|
||||
msgid "User's display name."
|
||||
msgstr "Nombre para mostrar del usuario."
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "User's primary identifier. 150 characters or fewer."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User(s)"
|
||||
|
@ -52,6 +52,7 @@ msgstr ""
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
@ -2116,6 +2117,10 @@ msgstr "Impossible de supprimer {0} : {1}"
|
||||
msgid "Failed to disconnected source: {exc}"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "Failed to fetch objects."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserActiveForm.ts
|
||||
msgid "Failed to update {0}: {1}"
|
||||
msgstr "Impossible de mettre à jour {0} : {1}"
|
||||
@ -2556,6 +2561,10 @@ msgstr "Identifiant"
|
||||
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "If checked, the launch URL will open in a new browser tab or window from the user's application library."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage."
|
||||
msgstr ""
|
||||
@ -2759,6 +2768,10 @@ msgstr "Mode de l'émetteur"
|
||||
msgid "JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
msgid "JWKS URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "Algorithme JWT"
|
||||
@ -3631,6 +3644,10 @@ msgstr ""
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr "Ouvrir l'appication"
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "Open in new tab"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Open issue on GitHub..."
|
||||
msgstr "Ouvrir un ticket sur GitHub..."
|
||||
@ -3691,6 +3708,10 @@ msgstr "Indiquer la valeur \"FriendlyName\" de l'attribut d'assertion (optionnel
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr "Indiquer votre domaine parent (optionnel), si vous souhaitez que l'authentification et l'autorisation soient réalisés au niveau du domaine. Si vous exécutez des applications sur app1.domain.tld, app2.domain.tld, indiquez ici \"domain.tld\"."
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
msgid "Or"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
@ -4304,8 +4325,8 @@ msgstr "Obligatoire."
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr "Obligatoire. 150 caractères ou moins. Lettres, chiffres et @/./+/-/_ uniquement."
|
||||
#~ msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
#~ msgstr "Obligatoire. 150 caractères ou moins. Lettres, chiffres et @/./+/-/_ uniquement."
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "Reset Password"
|
||||
@ -6244,6 +6265,11 @@ msgstr "Avatar de l'utilisateu"
|
||||
msgid "User's display name."
|
||||
msgstr "Nom d'affichage de l'utilisateur"
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "User's primary identifier. 150 characters or fewer."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User(s)"
|
||||
|
@ -49,6 +49,7 @@ msgstr "(Format: hours=1;minutes=2;seconds=3)."
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
@ -2088,6 +2089,10 @@ msgstr "Nie udało się usunąć {0}: {1}"
|
||||
msgid "Failed to disconnected source: {exc}"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "Failed to fetch objects."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserActiveForm.ts
|
||||
msgid "Failed to update {0}: {1}"
|
||||
msgstr "Nie udało się zaktualizować {0}: {1}"
|
||||
@ -2524,6 +2529,10 @@ msgstr "Identyfikator"
|
||||
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "If checked, the launch URL will open in a new browser tab or window from the user's application library."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage."
|
||||
msgstr ""
|
||||
@ -2727,6 +2736,10 @@ msgstr "Tryb wystawcy"
|
||||
msgid "JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
msgid "JWKS URL"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "Algorytm JWT"
|
||||
|
||||
@ -3594,6 +3607,10 @@ msgstr "Otwórz przeglądarkę API"
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr "Otwórz aplikację"
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "Open in new tab"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Open issue on GitHub..."
|
||||
msgstr "Otwórz problem w serwisie GitHub..."
|
||||
@ -3653,6 +3670,10 @@ msgstr "Opcjonalnie ustaw wartość „FriendlyName” atrybutu asercji."
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr "Opcjonalnie ustaw to na swoją domenę nadrzędną, jeśli chcesz, aby uwierzytelnianie i autoryzacja odbywały się na poziomie domeny. Jeśli używasz aplikacji jako app1.domain.tld, app2.domain.tld, ustaw to na „domain.tld”."
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
msgid "Or"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
@ -4254,8 +4275,8 @@ msgstr "Wymagany."
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr "Wymagane. 150 znaków lub mniej. Tylko litery, cyfry i @/./+/-/_."
|
||||
#~ msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
#~ msgstr "Wymagane. 150 znaków lub mniej. Tylko litery, cyfry i @/./+/-/_."
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "Reset Password"
|
||||
@ -6180,6 +6201,11 @@ msgstr "Awatar użytkownika"
|
||||
msgid "User's display name."
|
||||
msgstr "Wyświetlana nazwa użytkownika."
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "User's primary identifier. 150 characters or fewer."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User(s)"
|
||||
|
@ -46,6 +46,7 @@ msgstr ""
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
@ -2121,6 +2122,10 @@ msgstr ""
|
||||
msgid "Failed to disconnected source: {exc}"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "Failed to fetch objects."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserActiveForm.ts
|
||||
msgid "Failed to update {0}: {1}"
|
||||
msgstr ""
|
||||
@ -2564,6 +2569,10 @@ msgstr ""
|
||||
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "If checked, the launch URL will open in a new browser tab or window from the user's application library."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage."
|
||||
msgstr ""
|
||||
@ -2768,6 +2777,10 @@ msgstr ""
|
||||
msgid "JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
msgid "JWKS URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr ""
|
||||
@ -3645,6 +3658,10 @@ msgstr ""
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "Open in new tab"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Open issue on GitHub..."
|
||||
msgstr ""
|
||||
@ -3705,6 +3722,10 @@ msgstr ""
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
msgid "Or"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
@ -4322,8 +4343,8 @@ msgstr ""
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr ""
|
||||
#~ msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "Reset Password"
|
||||
@ -6285,6 +6306,11 @@ msgstr ""
|
||||
msgid "User's display name."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "User's primary identifier. 150 characters or fewer."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User(s)"
|
||||
|
@ -49,6 +49,7 @@ msgstr "(Biçim: saat=1; dakika=2; saniye= 3)."
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
@ -2091,6 +2092,10 @@ msgstr "{0} silinemedi: {1}"
|
||||
msgid "Failed to disconnected source: {exc}"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "Failed to fetch objects."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserActiveForm.ts
|
||||
msgid "Failed to update {0}: {1}"
|
||||
msgstr "{0} güncellenemedi: {1}"
|
||||
@ -2527,6 +2532,10 @@ msgstr "Tanımlayıcı"
|
||||
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "If checked, the launch URL will open in a new browser tab or window from the user's application library."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage."
|
||||
msgstr ""
|
||||
@ -2731,6 +2740,10 @@ msgstr "Yayımcı kipi"
|
||||
msgid "JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
msgid "JWKS URL"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT Algoritması"
|
||||
|
||||
@ -3599,6 +3612,10 @@ msgstr "API Tarayıcısını aç"
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr "Uygulamayı aç"
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "Open in new tab"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Open issue on GitHub..."
|
||||
msgstr "GitHub'da açık sorun..."
|
||||
@ -3658,6 +3675,10 @@ msgstr "İsteğe bağlı olarak onaylama özniteliğinin 'FriendlyName' değerin
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr "Kimlik doğrulama ve yetkilendirmenin etki alanı düzeyinde gerçekleşmesini istiyorsanız, isteğe bağlı olarak bunu üst etki alanınıza ayarlayın. Uygulamaları app1.domain.tld, app2.domain.tld olarak çalıştırıyorsanız, bunu 'domain.tld' olarak ayarlayın."
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
msgid "Or"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
@ -4259,8 +4280,8 @@ msgstr "Zorunlu."
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr "Gerekli. 150 karakter veya daha az. Harfler, rakamlar ve yalnızca @/./+/-/_."
|
||||
#~ msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
#~ msgstr "Gerekli. 150 karakter veya daha az. Harfler, rakamlar ve yalnızca @/./+/-/_."
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "Reset Password"
|
||||
@ -6185,6 +6206,11 @@ msgstr "Kullanıcının avatarı"
|
||||
msgid "User's display name."
|
||||
msgstr "Kullanıcının görünen adı."
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "User's primary identifier. 150 characters or fewer."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User(s)"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -51,6 +51,7 @@ msgstr "(格式: hours=1;minutes=2;seconds=3)."
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
@ -2082,6 +2083,10 @@ msgstr "无法删除 {0}: {1}"
|
||||
msgid "Failed to disconnected source: {exc}"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "Failed to fetch objects."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserActiveForm.ts
|
||||
msgid "Failed to update {0}: {1}"
|
||||
msgstr "更新失败 {0}:{1}"
|
||||
@ -2516,6 +2521,10 @@ msgstr "标识符"
|
||||
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "If checked, the launch URL will open in a new browser tab or window from the user's application library."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage."
|
||||
msgstr ""
|
||||
@ -2718,6 +2727,10 @@ msgstr "Issuer mode"
|
||||
msgid "JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
msgid "JWKS URL"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT 算法"
|
||||
|
||||
@ -3580,6 +3593,10 @@ msgstr "打开 API 浏览器"
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr "打开应用程序"
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "Open in new tab"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Open issue on GitHub..."
|
||||
msgstr "在 GitHub 上打开问题..."
|
||||
@ -3639,6 +3656,10 @@ msgstr "(可选)设置 “断言” 属性的'友好名称'值。"
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr "如果您希望在域级别进行身份验证和授权,可以选择将其设置为您的父域。如果你以 app1.domain.tld、app2.domain.tld 的身份运行应用程序,请将其设置为 “domain.tld”。"
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
msgid "Or"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
@ -4232,8 +4253,8 @@ msgstr "必需。"
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr "必填。不超过 150 个字符。仅限字母、数字和 @/./+/-/_ 。"
|
||||
#~ msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
#~ msgstr "必填。不超过 150 个字符。仅限字母、数字和 @/./+/-/_ 。"
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "Reset Password"
|
||||
@ -6151,6 +6172,11 @@ msgstr "用户的头像"
|
||||
msgid "User's display name."
|
||||
msgstr "用户的显示名称。"
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "User's primary identifier. 150 characters or fewer."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User(s)"
|
||||
|
@ -51,6 +51,7 @@ msgstr "(格式: hours=1;minutes=2;seconds=3)."
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
@ -2082,6 +2083,10 @@ msgstr "无法删除 {0}: {1}"
|
||||
msgid "Failed to disconnected source: {exc}"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "Failed to fetch objects."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserActiveForm.ts
|
||||
msgid "Failed to update {0}: {1}"
|
||||
msgstr "更新失败 {0}:{1}"
|
||||
@ -2516,6 +2521,10 @@ msgstr "标识符"
|
||||
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "If checked, the launch URL will open in a new browser tab or window from the user's application library."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage."
|
||||
msgstr ""
|
||||
@ -2718,6 +2727,10 @@ msgstr "Issuer mode"
|
||||
msgid "JSON Web Key URL. Keys from the URL will be used to validate JWTs from this source."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
msgid "JWKS URL"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT 算法"
|
||||
|
||||
@ -3580,6 +3593,10 @@ msgstr "打开 API 浏览器"
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr "打开应用程序"
|
||||
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
msgid "Open in new tab"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Open issue on GitHub..."
|
||||
msgstr "在 GitHub 上打开问题..."
|
||||
@ -3639,6 +3656,10 @@ msgstr "(可选)设置 “断言” 属性的'友好名称'值。"
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr "如果您希望在域级别进行身份验证和授权,可以选择将其设置为您的父域。如果你以 app1.domain.tld、app2.domain.tld 的身份运行应用程序,请将其设置为 “domain.tld”。"
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
msgid "Or"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
@ -4232,8 +4253,8 @@ msgstr "必需。"
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr "必填。不超过 150 个字符。仅限字母、数字和 @/./+/-/_ 。"
|
||||
#~ msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
#~ msgstr "必填。不超过 150 个字符。仅限字母、数字和 @/./+/-/_ 。"
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "Reset Password"
|
||||
@ -6151,6 +6172,11 @@ msgstr "用户的头像"
|
||||
msgid "User's display name."
|
||||
msgstr "用户的显示名称。"
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
msgid "User's primary identifier. 150 characters or fewer."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/RelatedUserList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User(s)"
|
||||
|
@ -187,6 +187,12 @@ export class ApplicationForm extends ModelForm<Application, string> {
|
||||
${t`If left empty, authentik will try to extract the launch URL based on the selected provider.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Open in new tab`} name="openInNewTab">
|
||||
<input type="checkbox" ?checked=${this.instance?.openInNewTab} />
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`If checked, the launch URL will open in a new browser tab or window from the user's application library.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
${until(
|
||||
config().then((c) => {
|
||||
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
||||
|
@ -67,6 +67,9 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||
if (item.user) {
|
||||
return html` <a href=${`#/identity/users/${item.user}`}> ${label} </a> `;
|
||||
}
|
||||
if (item.group) {
|
||||
return html` <a href=${`#/identity/groups/${item.group}`}> ${label} </a> `;
|
||||
}
|
||||
return html`${label}`;
|
||||
}
|
||||
|
||||
|
@ -256,6 +256,19 @@ export class OAuth2ProviderViewPage extends LitElement {
|
||||
value="${this.providerUrls?.logout || t`-`}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`JWKS URL`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${this.providerUrls?.jwks || t`-`}"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -42,7 +42,7 @@ export class ServiceAccountForm extends Form<UserServiceAccountRequest> {
|
||||
<ak-form-element-horizontal label=${t`Username`} ?required=${true} name="name">
|
||||
<input type="text" value="" class="pf-c-form-control" required />
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.`}
|
||||
${t`User's primary identifier. 150 characters or fewer.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="createGroup">
|
||||
|
@ -66,7 +66,7 @@ export class UserForm extends ModelForm<User, number> {
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.`}
|
||||
${t`User's primary identifier. 150 characters or fewer.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Name`} name="name">
|
||||
|
@ -88,7 +88,12 @@ export class LibraryApplication extends LitElement {
|
||||
style="background: ${this.background} !important"
|
||||
>
|
||||
<div class="pf-c-card__header">
|
||||
<a href="${ifDefined(this.application.launchUrl ?? "")}"> ${this.renderIcon()} </a>
|
||||
<a
|
||||
href="${ifDefined(this.application.launchUrl ?? "")}"
|
||||
target="${ifDefined(this.application.openInNewTab ? "_blank" : undefined)}"
|
||||
>
|
||||
${this.renderIcon()}
|
||||
</a>
|
||||
${until(
|
||||
uiConfig().then((config) => {
|
||||
if (!config.enabledFeatures.applicationEdit) {
|
||||
@ -110,7 +115,9 @@ export class LibraryApplication extends LitElement {
|
||||
</div>
|
||||
<div class="pf-c-card__title">
|
||||
<p>
|
||||
<a href="${ifDefined(this.application.launchUrl ?? "")}"
|
||||
<a
|
||||
href="${ifDefined(this.application.launchUrl ?? "")}"
|
||||
target="${ifDefined(this.application.openInNewTab ? "_blank" : undefined)}"
|
||||
>${this.application.name}</a
|
||||
>
|
||||
</p>
|
||||
|
@ -5,7 +5,23 @@ title: Writing documentation
|
||||
Writing documentation for authentik is a great way for both new and experienced users to improve and contribute to the project. Here are a few guidelines to ensure
|
||||
the documentation is easy to read and uses similar phrasing.
|
||||
|
||||
# General guidelines
|
||||
## Setup
|
||||
|
||||
Requirements:
|
||||
|
||||
- Node 16 (or greater)
|
||||
|
||||
The documentation site is situated in the `/website` folder of the authentik GitHub repository.
|
||||
|
||||
The site is built using npm, below are some useful make commands:
|
||||
|
||||
- Install: `make website-install` (Needed for any of the other tasks)
|
||||
- Formatting: `make website-lint-fix` or `make website` (Run this before committing)
|
||||
- Live editing: `make website-watch` (For real time viewing of changes)
|
||||
|
||||
Be sure to run the formatter before committing changes.
|
||||
|
||||
## General guidelines
|
||||
|
||||
- authentik should always be stylized as `authentik` (with a lower-case a and ending with a k)
|
||||
- Documentation should use American english
|
||||
@ -27,6 +43,8 @@ If you find any documentation that doesn't match these guidelines, feel free to
|
||||
|
||||
These guidelines apply in addition to the ones above.
|
||||
|
||||
See the template in `/website/integrations/_template/service.md`.
|
||||
|
||||
- For placeholders, use angle brackets (`<placeholder-name>`).
|
||||
|
||||
Make sure to also define if the placeholder is something the user needs to define, something another system defines, or randomly generated.
|
||||
@ -36,3 +54,5 @@ These guidelines apply in addition to the ones above.
|
||||
|
||||
- For placeholder domains, use `authentik.company` and `app-name.company`, where `app-name` is the name of the application you are writing documentation for.
|
||||
- Try to order the documentation in the order that makes it easiest for the user to configure.
|
||||
|
||||
- Make sure to add the service to a fitting category in `/website/sidebarsIntegrations.js`
|
||||
|
@ -2,21 +2,21 @@
|
||||
title: Full development environment
|
||||
---
|
||||
|
||||
## Backend
|
||||
|
||||
To create a local development setup for authentik, you need the following:
|
||||
|
||||
### Requirements
|
||||
## Requirements
|
||||
|
||||
- Python 3.10
|
||||
- poetry, which is used to manage dependencies, and can be installed with `pip install poetry`
|
||||
- Go 1.18
|
||||
- PostgreSQL (any recent version will do)
|
||||
- Redis (any recent version will do)
|
||||
- Node 16 (or later)
|
||||
|
||||
For PostgreSQL and Redis, you can use the docker-compose file in `scripts/`. You can also use a native install, if you prefer.
|
||||
## Services Setup
|
||||
|
||||
### Setup
|
||||
For PostgreSQL and Redis, you can use the docker-compose file in `scripts/`.
|
||||
You can also use a native install, if you prefer.
|
||||
|
||||
## Backend Setup
|
||||
|
||||
```shell
|
||||
poetry shell # Creates a python virtualenv, and activates it in a new shell
|
||||
@ -36,8 +36,6 @@ secret_key: "A long key you can generate with `pwgen 40 1` for example"
|
||||
|
||||
To apply database migrations, run `make migrate`. This is needed after the initial setup, and whenever you fetch new source from upstream.
|
||||
|
||||
Afterwards, you can start authentik by running `make run`. authentik is now accessible under `localhost:9000`.
|
||||
|
||||
Generally speaking, authentik is a Django application, ran by gunicorn, proxied by a Go application. The Go application serves static files.
|
||||
|
||||
Most functions and classes have type-hints and docstrings, so it is recommended to install a Python Type-checking Extension in your IDE to navigate around the code.
|
||||
@ -46,18 +44,17 @@ Before committing code, run `make lint` to ensure your code is formatted well. T
|
||||
|
||||
Run `make gen` to generate an updated OpenAPI document for any changes you made.
|
||||
|
||||
## Frontend
|
||||
## Frontend Setup
|
||||
|
||||
By default, no compiled bundle of the frontend is included. To build the UI, you need Node 14 or newer.
|
||||
By default, no compiled bundle of the frontend is included so this step is required even if you're not developing for the UI.
|
||||
|
||||
To build the UI, run these commands:
|
||||
To build the UI once, run `web-build`.
|
||||
|
||||
```
|
||||
cd web/
|
||||
npm i
|
||||
npm run build
|
||||
```
|
||||
Alternatively, if you want to live-edit the UI, you can run `make web-watch` instead.
|
||||
This will immediately update the UI with any changes you make so you can see the results in real time without needing to rebuild.
|
||||
|
||||
If you want to make changes to the UI, run `npm run watch` instead.
|
||||
To format the frontend code, run `make web`.
|
||||
|
||||
To ensure the code is formatted well, run `npx eslint . --fix` and `npm run lit-analyse`.
|
||||
## Running
|
||||
|
||||
Now that the backend and frontend have been setup and built, you can start authentik by running `make run`. authentik should now be accessible at `http://localhost:9000`.
|
||||
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Installation
|
||||
---
|
||||
|
||||
If you want to try out authentik, or only want a small deployment you should use [docker-compose](./docker-compose).
|
||||
|
||||
If you want a larger deployment, or you want High-Availability, you should use [Kubernetes](./kubernetes).
|
@ -11,7 +11,7 @@ Scopes can be configured using Scope Mappings, a type of [Property Mappings](../
|
||||
| Authorization | `/application/o/authorize/` |
|
||||
| Token | `/application/o/token/` |
|
||||
| User Info | `/application/o/userinfo/` |
|
||||
| End Session | `/application/o/end-session/` |
|
||||
| End Session | `/application/o/<application slug>/end-session/` |
|
||||
| JWKS | `/application/o/<application slug>/jwks/` |
|
||||
| OpenID Configuration | `/application/o/<application slug>/.well-known/openid-configuration` |
|
||||
|
||||
|
@ -13,7 +13,7 @@ This update brings a lot of big features, such as:
|
||||
|
||||
Due to this new OAuth2 Provider, the Application Gateway Provider, now simply called "Proxy Provider" has been revamped as well. The new authentik Proxy integrates more tightly with authentik via the new Outposts system. The new proxy also supports multiple applications per proxy instance, can configure TLS based on authentik Keypairs, and more.
|
||||
|
||||
See [Proxy](../providers/proxy/proxy.md)
|
||||
See [Proxy](../providers/proxy/index.md)
|
||||
|
||||
- Outpost System
|
||||
|
||||
|
@ -45,6 +45,41 @@ slug: "2022.6"
|
||||
- web/user: fix static prompt fields being rendered with label
|
||||
- web/user: improve ux for restarting user settings flow
|
||||
|
||||
## Fixed in 2022.6.2
|
||||
|
||||
- \*: make user logging more consistent
|
||||
- core: add additional filters to source viewset
|
||||
- core: add setting to open application launch URL in a new browser tab (#3037)
|
||||
- core: add slug to built-in source
|
||||
- events: fix error when attempting to create event with GeoIP City in context
|
||||
- providers/ldap: fix existing binder not being carried forward correctly
|
||||
- providers/oauth2: add JWKS URL to OAuth2ProviderSetupURLs
|
||||
- providers/proxy: use same redirect-save code for all modes
|
||||
- sources/oauth: fix twitter client missing basic auth
|
||||
- stages/authenticator_validate: fix error in passwordless webauthn
|
||||
- web/elements: add error handler when table fails to fetch objects
|
||||
|
||||
## Fixed in 2022.6.3
|
||||
|
||||
- core: fix migrations when creating bootstrap token
|
||||
- internal: dont sample gunicorn proxied requests
|
||||
- internal: fix routing to embedded outpost
|
||||
- internal: skip tracing for go healthcheck and metrics endpoints
|
||||
- lifecycle: run bootstrap tasks inline when using automated install
|
||||
- policies: consolidate log user and application
|
||||
- providers/oauth2: add test to ensure capitalised redirect_uri isn't changed
|
||||
- providers/oauth2: dont lowercase URL for token requests (#3114)
|
||||
- providers/oauth2: if a redirect_uri cannot be parsed as regex, compare strict (#3070)
|
||||
- providers/proxy: only send misconfiguration event once
|
||||
- root: ignore healthcheck routes in sentry tracing
|
||||
- stages/authenticator_validate: add webauthn tests (#3069)
|
||||
- web/admin: lint bound group under policies
|
||||
- web/admin: remove invalid requirement for usernames
|
||||
- web/elements: add spinner when loading dynamic routes
|
||||
- web/flows: add divider to identification stage for security key
|
||||
- web/flows: fix error when webauthn operations failed and user retries
|
||||
- web/flows: remove autofocus from password field of identifications tage
|
||||
|
||||
## Upgrading
|
||||
|
||||
This release does not introduce any new requirements.
|
||||
|
@ -1,3 +1,10 @@
|
||||
const sidebar = require("./sidebars.js");
|
||||
|
||||
const releases = sidebar.docs
|
||||
.filter((doc) => doc.link?.slug === "releases")[0]
|
||||
.items.filter((release) => typeof release === "string");
|
||||
const latestVersion = releases[0].replace("releases/v", "");
|
||||
|
||||
module.exports = {
|
||||
title: "authentik",
|
||||
tagline: "Making authentication simple.",
|
||||
@ -39,6 +46,20 @@ module.exports = {
|
||||
label: "API",
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
type: "dropdown",
|
||||
label: `Version ${latestVersion}`,
|
||||
position: "right",
|
||||
items: releases.map((release) => {
|
||||
const subdomain = release
|
||||
.replace("releases/v", "")
|
||||
.replace(".", "-");
|
||||
return {
|
||||
label: release.replace("releases/", ""),
|
||||
href: `https://version-${subdomain}.goauthentik.io`,
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
href: "https://github.com/goauthentik/authentik",
|
||||
label: "GitHub",
|
||||
|
28
website/integrations/_template/service.md
Normal file
28
website/integrations/_template/service.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Service Name
|
||||
---
|
||||
|
||||
<span class="badge badge--secondary">Support level: Community</span>
|
||||
|
||||
## What is Service Name
|
||||
|
||||
From https://service.name
|
||||
|
||||
:::note
|
||||
Insert a quick overview of what Service Name is and what it does
|
||||
:::
|
||||
|
||||
## Preparation
|
||||
|
||||
The following placeholders will be used:
|
||||
|
||||
- `service.company` is the FQDN of the Service install. (Remove this for SaaS)
|
||||
- `authentik.company` is the FQDN of the authentik install.
|
||||
|
||||
## Service Configuration
|
||||
|
||||
Insert Service configuration
|
||||
|
||||
## authentik Configuration
|
||||
|
||||
Insert authentik configuration
|
@ -1,6 +0,0 @@
|
||||
---
|
||||
title: Integrations
|
||||
slug: /
|
||||
---
|
||||
|
||||
Here you can find a full list of applications that have been documented to work with authentik. If you find any mistake or a step does not work for you, open a GitHub issue [here](https://github.com/goauthentik/authentik/issues/new/choose).
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user