Compare commits

..

23 Commits

Author SHA1 Message Date
9201fc1834 release: 2022.6.3 2022-06-19 22:01:06 +02:00
5385feb428 website/docs: add 2022.6.3 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:41:36 +02:00
db557401aa web/admin: lint bound group under policies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:37:28 +02:00
c824af5bc3 web/elements: add spinner when loading dynamic routes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:37:22 +02:00
1faba11a57 providers/oauth2: add test to ensure capitalised redirect_uri isn't changed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#3114
2022-06-19 21:37:20 +02:00
9p4
f0c72e8536 providers/oauth2: dont lowercase URL for token requests (#3114)
this was a leftover from before the migration regex checking for redirect URIs

closes #3076 and #3083
2022-06-19 21:37:17 +02:00
91f91b08e5 core: fix migrations when creating bootstrap token
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:37:14 +02:00
8faa909c32 internal: fix routing to embedded outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:37:03 +02:00
49142fa80b internal: dont sample gunicorn proxied requests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:57 +02:00
2a6fccd22a providers/proxy: only send misconfiguration event once
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:50 +02:00
1d10afa209 website/docs: add version dropdown for subdomains
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:45 +02:00
4b7c3c38cd website/docs: support levels (#3103)
* website/docs: add badges for integration level

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add badge for sources

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:42 +02:00
440cacbafe webiste/docs: use autogenerated pages and categories (#3102)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:39 +02:00
b33bff92ee web/flows: fix error when webauthn operations failed and user retries
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:28 +02:00
caed306346 providers/oauth2: if a redirect_uri cannot be parsed as regex, compare strict (#3070)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:19 +02:00
d0eb6af7e9 web/admin: remove invalid requirement for usernames
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:15 +02:00
ec5ed67f6c web/flows: add divider to identification stage for security key
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:08 +02:00
59b899ddff internal: skip tracing for go healthcheck and metrics endpoints
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:48 +02:00
85784f796c root: ignore healthcheck routes in sentry tracing
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:46 +02:00
4c0e19cbea web/flows: remove autofocus from password field of identifications tage
closes #2561

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:43 +02:00
b42eb9464f lifecycle: run bootstrap tasks inline when using automated install
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:33 +02:00
6559fdee15 stages/authenticator_validate: add webauthn tests (#3069)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:23 +02:00
3455bf3d27 policies: consolidate log user and application
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:04 +02:00
124 changed files with 1065 additions and 258 deletions

View File

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

View File

@ -30,9 +30,9 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2022.6.2,
beryju/authentik:2022.6.3,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2022.6.2,
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.2,
beryju/authentik-${{ matrix.type }}:2022.6.3,
beryju/authentik-${{ matrix.type }}:latest,
ghcr.io/goauthentik/${{ matrix.type }}:2022.6.2,
ghcr.io/goauthentik/${{ matrix.type }}:2022.6.3,
ghcr.io/goauthentik/${{ matrix.type }}:latest
file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
@ -152,7 +152,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2022.6.2
version: authentik@2022.6.3
environment: beryjuorg-prod
sourcemaps: './web/dist'
url_prefix: '~/static/dist'

View File

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

View 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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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__}"),
]
)
)

View File

@ -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

View File

@ -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:

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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.

View File

@ -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"),
]

View File

@ -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(

View File

@ -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,
)

View File

@ -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)

View File

@ -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",
},
)

View File

@ -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{

View File

@ -29,7 +29,7 @@ services:
retries: 5
timeout: 3s
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.6.2}
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.2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.6.3}
restart: unless-stopped
command: worker
environment:

View File

@ -25,4 +25,4 @@ func OutpostUserAgent() string {
return fmt.Sprintf("goauthentik.io/outpost/%s", FullVersion())
}
const VERSION = "2022.6.2"
const VERSION = "2022.6.3"

View File

@ -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(),
},

View File

@ -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)
})

View File

@ -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
}
}

View File

@ -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)

View File

@ -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)
})

View File

@ -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
}

View 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)
})
}

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -90,7 +90,7 @@ addopts = "-p no:celery --junitxml=unittest.xml"
[tool.poetry]
name = "authentik"
version = "2022.6.2"
version = "2022.6.3"
description = ""
authors = ["authentik Team <hello@goauthentik.io>"]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2022.6.2
version: 2022.6.3
description: Making authentication simple.
contact:
email: hello@goauthentik.io

View File

@ -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.2";
export const VERSION = "2022.6.3";
export const TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";";

View File

@ -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;

View File

@ -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;

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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``}`;
}

View File

@ -53,7 +53,7 @@ export class UserInterface extends LitElement {
tenant: CurrentTenant = DefaultTenant;
@property({ type: Number })
notificationsCount = -1;
notificationsCount = 0;
static get styles(): CSSResult[] {
return [

View File

@ -3680,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
@ -4281,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"
@ -6206,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)"

View File

@ -3740,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
@ -4359,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"
@ -6332,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)"

View File

@ -3673,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
@ -4274,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"
@ -6200,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)"

View File

@ -3708,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
@ -4321,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"
@ -6261,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)"

View File

@ -3670,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
@ -4271,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"
@ -6197,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)"

View File

@ -3722,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
@ -4339,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"
@ -6302,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)"

View File

@ -3675,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
@ -4276,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"
@ -6202,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)"

View File

@ -1538,7 +1538,6 @@ msgstr "删除 {0}"
msgid "Deny the user access"
msgstr "拒绝用户访问"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
msgid "Deprecated. Instead of using this field, configure the JWKS data/URL in Sources."
msgstr "已弃用。请在身份来源中配置 JWKS 数据 / URL 代替此字段。"
@ -2448,7 +2447,6 @@ msgstr "隐藏服务账户"
#: src/pages/outposts/OutpostForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
#: src/pages/sources/ldap/LDAPSourceForm.ts
@ -2731,7 +2729,6 @@ msgstr ""
#~ msgid "JWT Algorithm"
#~ msgstr "JWT 算法"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
msgstr "此处配置的证书签名的 JWT 可以用于此提供程序的身份验证。"
@ -2913,7 +2910,6 @@ msgstr "正在加载"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
@ -3651,6 +3647,10 @@ msgstr "可选,设置断言属性的 'FriendlyName' 值。"
#~ 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
@ -4243,8 +4243,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"
@ -6159,6 +6159,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)"
@ -6252,8 +6257,8 @@ msgid "Verification Certificate"
msgstr "验证证书"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
msgid "Verification certificates"
msgstr "验证证书"
#~ msgid "Verification certificates"
#~ msgstr "验证证书"
#~ msgid "Verify only"
#~ msgstr "仅验证"

View File

@ -3656,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
@ -4249,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"
@ -6168,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)"

View File

@ -3656,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
@ -4249,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"
@ -6168,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)"

View File

@ -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}`;
}

View File

@ -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">

View File

@ -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">

View File

@ -43,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.
@ -52,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`

View File

@ -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).

View File

@ -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

View File

@ -59,6 +59,27 @@ slug: "2022.6"
- 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.

View File

@ -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",

View 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

View File

@ -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).

View File

@ -2,6 +2,8 @@
title: Apache Guacamole™
---
<span class="badge badge--primary">Support level: authentik</span>
## What is Apache Guacamole™
From https://guacamole.apache.org/

View File

@ -2,6 +2,8 @@
title: Amazon Web Services
---
<span class="badge badge--primary">Support level: authentik</span>
## What is AWS
:::note

View File

@ -2,6 +2,8 @@
title: Ansible Tower / AWX
---
<span class="badge badge--secondary"></span>
## What is Tower
From https://docs.ansible.com/ansible/2.5/reference_appendices/tower.html

View File

@ -2,6 +2,8 @@
title: Bookstack
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Bookstack
From https://en.wikipedia.org/wiki/BookStack

View File

@ -2,6 +2,8 @@
title: Budibase
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Budibase
From https://github.com/Budibase/budibase

View File

@ -2,6 +2,8 @@
title: FortiManager
---
<span class="badge badge--secondary">Support level: Community</span>
## What is FortiManager
From https://www.fortinet.com/products/management/fortimanager

View File

@ -2,6 +2,8 @@
title: Gitea
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Gitea
From https://gitea.io/

View File

@ -2,6 +2,8 @@
title: GitLab
---
<span class="badge badge--primary">Support level: authentik</span>
## What is GitLab
From https://about.gitlab.com/what-is-gitlab/

View File

@ -2,6 +2,8 @@
title: Grafana
---
<span class="badge badge--primary">Support level: authentik</span>
## What is Grafana
From https://en.wikipedia.org/wiki/Grafana

View File

@ -2,6 +2,8 @@
title: Harbor
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Harbor
From https://goharbor.io

View File

@ -2,6 +2,8 @@
title: Hashicorp Vault
---
<span class="badge badge--primary">Support level: authentik</span>
## What is Vault
From https://vaultproject.io

View File

@ -2,6 +2,8 @@
title: HedgeDoc
---
<span class="badge badge--secondary">Support level: Community</span>
## What is HedgeDoc
From https://github.com/hedgedoc/hedgedoc

View File

@ -2,6 +2,8 @@
title: Home-Assistant
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Home-Assistant
From https://www.home-assistant.io/

View File

@ -0,0 +1,25 @@
---
title: Applications
slug: /
---
import DocCardList from "@theme/DocCardList";
import { useCurrentSidebarCategory } from "@docusaurus/theme-common";
Below is a list of all applications that are known to work with authentik.
All integrations will have a combination of these badges:
- <span class="badge badge--secondary">Support level: Community</span>
The integration is community maintained.
- <span class="badge badge--info">Support level: Vendor</span>
The integration is supported by the vendor.
- <span class="badge badge--primary">Support level: authentik</span>
The integration is regularly tested by the authentik team.
<DocCardList items={useCurrentSidebarCategory().items} />

View File

@ -2,6 +2,8 @@
title: Kimai
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Kimai
From https://www.kimai.org/about/

View File

@ -2,6 +2,8 @@
title: Matrix Synapse
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Matrix Synapse
From https://matrix.org/

View File

@ -2,6 +2,8 @@
title: MinIO
---
<span class="badge badge--primary">Support level: authentik</span>
## What is MinIO
From https://en.wikipedia.org/wiki/MinIO

View File

@ -2,6 +2,8 @@
title: NextCloud
---
<span class="badge badge--secondary">Support level: Community</span>
## What is NextCloud
From https://en.wikipedia.org/wiki/Nextcloud

View File

@ -2,6 +2,8 @@
title: OnlyOffice
---
<span class="badge badge--secondary">Support level: Community</span>
## What is OnlyOffice
From https://en.wikipedia.org/wiki/OnlyOffice

View File

@ -2,6 +2,8 @@
title: OPNsense
---
<span class="badge badge--secondary">Support level: Community</span>
## What is OPNsense
From https://opnsense.org/

View File

@ -2,6 +2,8 @@
title: Paperless-ng
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Paperless-ng
Modified from https://github.com/jonaswinkler/paperless-ng

View File

@ -2,6 +2,8 @@
title: pfSense
---
<span class="badge badge--secondary">Support level: Community</span>
## What is pfSense
From https://www.pfsense.org/
@ -58,7 +60,7 @@ In authentik, create an outpost (under _Applications/Outposts_) of type `LDAP` t
## pfSense unsecure setup (without SSL)
:::warning
This setup should only be used for testing purpose, because passwords will be sent in clear text to authentik.
This setup should only be used for testing purpose, because passwords will be sent in clear text to authentik.
:::
Add your authentik LDAP server to pfSense by going to your pfSense Web UI and clicking the `+ Add` under _System/User Manager/Authentication Servers_.

View File

@ -2,6 +2,8 @@
title: pgAdmin
---
<span class="badge badge--secondary">Support level: Community</span>
## What is pgAdmin
From https://www.pgadmin.org/

View File

@ -2,6 +2,8 @@
title: Portainer
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Portainer
From https://www.portainer.io/

View File

@ -2,6 +2,8 @@
title: PowerDNS-Admin
---
<span class="badge badge--secondary">Support level: Community</span>
## What is PowerDNS-Admin
From https://github.com/ngoduykhanh/PowerDNS-Admin

View File

@ -2,6 +2,8 @@
title: Proxmox VE
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Proxmox VE
From https://pve.proxmox.com/wiki/Main_Page

View File

@ -2,6 +2,8 @@
title: Rancher
---
<span class="badge badge--primary">Support level: authentik</span>
## What is Rancher
From https://rancher.com/products/rancher

View File

@ -2,6 +2,8 @@
title: Rocket.chat
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Rocket.chat
From https://github.com/RocketChat/Rocket.Chat

View File

@ -2,6 +2,8 @@
title: Roundcube
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Roundcube
From https://roundcube.net

View File

@ -2,6 +2,8 @@
title: Sentry
---
<span class="badge badge--primary">Support level: authentik</span>
## What is Sentry
From https://sentry.io

View File

@ -2,6 +2,8 @@
title: Sonarr
---
<span class="badge badge--secondary">Support level: Community</span>
:::note
These instructions apply to all projects in the \*arr Family. If you use multiple of these projects, you can assign them to the same Outpost.
:::

View File

@ -2,6 +2,8 @@
title: sssd
---
<span class="badge badge--secondary">Support level: Community</span>
## What is sssd
From https://sssd.io/

View File

@ -2,6 +2,8 @@
title: Tautulli
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Tautulli
From https://tautulli.com/

View File

@ -2,6 +2,8 @@
title: Ubuntu Landscape
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Ubuntu Landscape
From https://en.wikipedia.org/wiki/Landscape_(software)

View File

@ -2,6 +2,8 @@
title: Uptime Kuma
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Uptime Kuma
From https://github.com/louislam/uptime-kuma
@ -29,7 +31,7 @@ Create an application in authentik. Create a Proxy provider with the following p
- External host
`https://uptime-kuma.company`
`https://uptime-kuma.company`
Set this to the external URL you will be accessing Uptime Kuma from.
- Skip path regex

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