Compare commits
150 Commits
version-20
...
version-20
Author | SHA1 | Date | |
---|---|---|---|
4a1acd377b | |||
c5b84a91d1 | |||
e77ecda3b8 | |||
4e317c10c5 | |||
eb05a3ddb8 | |||
a22d6a0924 | |||
3f0d67779a | |||
0a937ae8e9 | |||
f8d94f3039 | |||
6bb261ac62 | |||
45f2c5bae7 | |||
5d8c1aa0b0 | |||
0101368369 | |||
4854f81592 | |||
4bed6e02e5 | |||
908f123d0e | |||
256dd24a1e | |||
d4284407f9 | |||
80da5dfc52 | |||
b6edf990e0 | |||
a66dcf9382 | |||
9095a840d5 | |||
72259f6479 | |||
0973c74b9d | |||
c7ed4f7ac1 | |||
3d577cf15e | |||
5474a32573 | |||
a5940b88e3 | |||
ff15716012 | |||
c040b13b29 | |||
4915e980c5 | |||
df362dd9ea | |||
d4e4f93cb4 | |||
3af0de6a00 | |||
4f24d61290 | |||
4c5c4dcf2c | |||
660b5cb6c6 | |||
6ff1ea73a9 | |||
3de224690a | |||
d4624b510a | |||
8856d762d0 | |||
5d1cbf14d1 | |||
6d5207f644 | |||
3b6497cd51 | |||
ff7320b0f8 | |||
e5a393c534 | |||
bb4be944dc | |||
21efee8f44 | |||
f61549a60f | |||
0a7bafd1b2 | |||
b3987c5fa0 | |||
0da043a9fe | |||
f336f204cb | |||
3bfcf18492 | |||
dfafe8b43d | |||
b5d43b15f8 | |||
2ccab75021 | |||
9070df6c26 | |||
a1c8ad55ad | |||
872c05c690 | |||
a9528dc1b5 | |||
0e59ade1f2 | |||
5ac49c695d | |||
3a30ecbe76 | |||
1f838bb2aa | |||
cc42830e23 | |||
593eb959ca | |||
5bb6785ad6 | |||
535c11a729 | |||
a0fa8d8524 | |||
c14025c579 | |||
8bc3db7c90 | |||
e194715c3e | |||
787f02d5dc | |||
a0ed01a610 | |||
02ba493759 | |||
a7fea5434d | |||
4fb783e953 | |||
affbf85699 | |||
0d92112a3f | |||
b1ad3ec9db | |||
c0601baca6 | |||
057c5c5e9a | |||
05429ab848 | |||
b66d51a699 | |||
f834bc0ff2 | |||
93fd883d7a | |||
7e080d4d68 | |||
3e3ca22d04 | |||
e741caa6b3 | |||
4343246a41 | |||
3f6f83b4b6 | |||
c63e1c9b87 | |||
f44cf06d22 | |||
3f609b8601 | |||
edd89b44a4 | |||
3e58748862 | |||
7088a6b0e6 | |||
6c880e0e62 | |||
cb1e70be7f | |||
6ba150f737 | |||
131769ea73 | |||
e68adbb30d | |||
f1eef09099 | |||
5ab3c7fa9f | |||
d0cec39a0f | |||
e15f53a39a | |||
25fb995663 | |||
eac658c64f | |||
15e2032493 | |||
c87f6cd9d9 | |||
e758995458 | |||
20c284a188 | |||
b0936ea8f3 | |||
bfc0f4a413 | |||
1a9a90cf6a | |||
00f1a6fa48 | |||
33754a06d2 | |||
69b838e1cf | |||
d5e04a2301 | |||
fbf251280f | |||
eaadf62f01 | |||
8c33e7a7c1 | |||
a7d9a80a28 | |||
2ea5dce8d3 | |||
14bf01efe4 | |||
67b24a60e4 | |||
e6775297cb | |||
4e4e2b36b6 | |||
3189c56fc3 | |||
5b5ea47b7a | |||
caa382f898 | |||
2d63488197 | |||
c1c8e4c8d4 | |||
a0e451c5e5 | |||
eaba8006e6 | |||
39ff202f8c | |||
654e0d6245 | |||
ec04443493 | |||
d247c262af | |||
dff49b2bef | |||
50666a76fb | |||
b51a7f9746 | |||
001dfd9f6c | |||
5e4fbeeb25 | |||
2c910bf6ca | |||
9b11319e81 | |||
40dc4b3fb8 | |||
0e37b98968 | |||
7e132eb014 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2022.1.5
|
||||
current_version = 2022.2.1
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
||||
|
3
.github/workflows/ci-main.yml
vendored
3
.github/workflows/ci-main.yml
vendored
@ -86,10 +86,9 @@ jobs:
|
||||
cp authentik/lib/default.yml local.env.yml
|
||||
cp -R .github ..
|
||||
cp -R scripts ..
|
||||
cp -R poetry.lock pyproject.toml ..
|
||||
git checkout $(git describe --abbrev=0 --match 'version/*')
|
||||
rm -rf .github/ scripts/
|
||||
mv ../.github ../scripts ../poetry.lock ../pyproject.toml .
|
||||
mv ../.github ../scripts .
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
|
||||
|
14
.github/workflows/release-publish.yml
vendored
14
.github/workflows/release-publish.yml
vendored
@ -30,14 +30,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik:2022.1.5,
|
||||
beryju/authentik:2022.2.1,
|
||||
beryju/authentik:latest,
|
||||
ghcr.io/goauthentik/server:2022.1.5,
|
||||
ghcr.io/goauthentik/server:2022.2.1,
|
||||
ghcr.io/goauthentik/server:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2022.1.5', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2022.2.1', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik:latest
|
||||
docker tag beryju/authentik:latest beryju/authentik:stable
|
||||
@ -78,14 +78,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-${{ matrix.type }}:2022.1.5,
|
||||
beryju/authentik-${{ matrix.type }}:2022.2.1,
|
||||
beryju/authentik-${{ matrix.type }}:latest,
|
||||
ghcr.io/goauthentik/${{ matrix.type }}:2022.1.5,
|
||||
ghcr.io/goauthentik/${{ matrix.type }}:2022.2.1,
|
||||
ghcr.io/goauthentik/${{ matrix.type }}:latest
|
||||
file: ${{ matrix.type }}.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2022.1.5', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2022.2.1', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik-${{ matrix.type }}:latest
|
||||
docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable
|
||||
@ -170,7 +170,7 @@ jobs:
|
||||
SENTRY_PROJECT: authentik
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
with:
|
||||
version: authentik@2022.1.5
|
||||
version: authentik@2022.2.1
|
||||
environment: beryjuorg-prod
|
||||
sourcemaps: './web/dist'
|
||||
url_prefix: '~/static/dist'
|
||||
|
2
.github/workflows/release-tag.yml
vendored
2
.github/workflows/release-tag.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
docker-compose run -u root server test
|
||||
- name: Extract version number
|
||||
id: get_version
|
||||
uses: actions/github-script@v5
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
@ -16,7 +16,7 @@ ENV NODE_ENV=production
|
||||
RUN cd /work/web && npm i && npm run build
|
||||
|
||||
# Stage 3: Build go proxy
|
||||
FROM docker.io/golang:1.17.6-bullseye AS builder
|
||||
FROM docker.io/golang:1.17.7-bullseye AS builder
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
| Version | Supported |
|
||||
| ---------- | ------------------ |
|
||||
| 2021.10.x | :white_check_mark: |
|
||||
| 2021.12.x | :white_check_mark: |
|
||||
| 2022.1.x | :white_check_mark: |
|
||||
| 2022.2.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
from os import environ
|
||||
from typing import Optional
|
||||
|
||||
__version__ = "2022.1.5"
|
||||
__version__ = "2022.2.1"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -12,10 +12,13 @@ from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ViewSet
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class TaskSerializer(PassiveSerializer):
|
||||
"""Serialize TaskInfo and TaskResult"""
|
||||
@ -89,6 +92,7 @@ class TaskViewSet(ViewSet):
|
||||
try:
|
||||
task_module = import_module(task.task_call_module)
|
||||
task_func = getattr(task_module, task.task_call_func)
|
||||
LOGGER.debug("Running task", task=task_func)
|
||||
task_func.delay(*task.task_call_args, **task.task_call_kwargs)
|
||||
messages.success(
|
||||
self.request,
|
||||
@ -96,6 +100,7 @@ class TaskViewSet(ViewSet):
|
||||
)
|
||||
return Response(status=204)
|
||||
except (ImportError, AttributeError): # pragma: no cover
|
||||
LOGGER.warning("Failed to run task, remove state", task=task)
|
||||
# if we get an import error, the module path has probably changed
|
||||
task.delete()
|
||||
return Response(status=500)
|
||||
|
@ -1,10 +1,9 @@
|
||||
"""core Configs API"""
|
||||
from os import environ, path
|
||||
from os import path
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
|
||||
from rest_framework.fields import (
|
||||
BooleanField,
|
||||
CharField,
|
||||
@ -28,7 +27,6 @@ class Capabilities(models.TextChoices):
|
||||
|
||||
CAN_SAVE_MEDIA = "can_save_media"
|
||||
CAN_GEO_IP = "can_geo_ip"
|
||||
CAN_BACKUP = "can_backup"
|
||||
|
||||
|
||||
class ErrorReportingConfigSerializer(PassiveSerializer):
|
||||
@ -65,13 +63,6 @@ class ConfigView(APIView):
|
||||
caps.append(Capabilities.CAN_SAVE_MEDIA)
|
||||
if GEOIP_READER.enabled:
|
||||
caps.append(Capabilities.CAN_GEO_IP)
|
||||
if SERVICE_HOST_ENV_NAME in environ:
|
||||
# Running in k8s, only s3 backup is supported
|
||||
if CONFIG.y("postgresql.s3_backup"):
|
||||
caps.append(Capabilities.CAN_BACKUP)
|
||||
else:
|
||||
# Running in compose, backup is always supported
|
||||
caps.append(Capabilities.CAN_BACKUP)
|
||||
return caps
|
||||
|
||||
@extend_schema(responses={200: ConfigSerializer(many=False)})
|
||||
|
@ -56,7 +56,11 @@ class ApplicationSerializer(ModelSerializer):
|
||||
if isinstance(user, SimpleLazyObject):
|
||||
user._setup()
|
||||
user = user._wrapped
|
||||
return url % user.__dict__
|
||||
try:
|
||||
return url % user.__dict__
|
||||
except ValueError as exc:
|
||||
LOGGER.warning("Failed to format launch url", exc=exc)
|
||||
return url
|
||||
|
||||
class Meta:
|
||||
|
||||
|
@ -1,17 +1,7 @@
|
||||
"""authentik core tasks"""
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from os import environ
|
||||
|
||||
from boto3.exceptions import Boto3Error
|
||||
from botocore.exceptions import BotoCoreError, ClientError
|
||||
from dbbackup.db.exceptions import CommandConnectorError
|
||||
from django.contrib.humanize.templatetags.humanize import naturaltime
|
||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||
from django.core import management
|
||||
from django.core.cache import cache
|
||||
from django.utils.timezone import now
|
||||
from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import AuthenticatedSession, ExpiringModel
|
||||
@ -21,7 +11,6 @@ from authentik.events.monitored_tasks import (
|
||||
TaskResultStatus,
|
||||
prefill_task,
|
||||
)
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.root.celery import CELERY_APP
|
||||
|
||||
LOGGER = get_logger()
|
||||
@ -53,46 +42,3 @@ def clean_expired_models(self: MonitoredTask):
|
||||
LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount)
|
||||
messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}")
|
||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages))
|
||||
|
||||
|
||||
def should_backup() -> bool:
|
||||
"""Check if we should be doing backups"""
|
||||
if SERVICE_HOST_ENV_NAME in environ and not CONFIG.y("postgresql.s3_backup.bucket"):
|
||||
LOGGER.info("Running in k8s and s3 backups are not configured, skipping")
|
||||
return False
|
||||
if not CONFIG.y_bool("postgresql.backup.enabled"):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task
|
||||
def backup_database(self: MonitoredTask): # pragma: no cover
|
||||
"""Database backup"""
|
||||
self.result_timeout_hours = 25
|
||||
if not should_backup():
|
||||
self.set_status(TaskResult(TaskResultStatus.UNKNOWN, ["Backups are not configured."]))
|
||||
return
|
||||
try:
|
||||
start = datetime.now()
|
||||
out = StringIO()
|
||||
management.call_command("dbbackup", quiet=True, stdout=out)
|
||||
self.set_status(
|
||||
TaskResult(
|
||||
TaskResultStatus.SUCCESSFUL,
|
||||
[
|
||||
f"Successfully finished database backup {naturaltime(start)} {out.getvalue()}",
|
||||
],
|
||||
)
|
||||
)
|
||||
LOGGER.info("Successfully backed up database.")
|
||||
except (
|
||||
IOError,
|
||||
BotoCoreError,
|
||||
ClientError,
|
||||
Boto3Error,
|
||||
PermissionError,
|
||||
CommandConnectorError,
|
||||
ValueError,
|
||||
) as exc:
|
||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||
|
@ -16,6 +16,7 @@
|
||||
{% block head_before %}
|
||||
{% endblock %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}">
|
||||
<script src="{% static 'dist/poly.js' %}" type="module"></script>
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
|
@ -1,7 +1,5 @@
|
||||
"""events GeoIP Reader"""
|
||||
from datetime import datetime
|
||||
from os import stat
|
||||
from time import time
|
||||
from typing import Optional, TypedDict
|
||||
|
||||
from geoip2.database import Reader
|
||||
@ -46,14 +44,18 @@ class GeoIPReader:
|
||||
LOGGER.warning("Failed to load GeoIP database", exc=exc)
|
||||
|
||||
def __check_expired(self):
|
||||
"""Check if the geoip database has been opened longer than 8 hours,
|
||||
and re-open it, as it will probably will have been re-downloaded"""
|
||||
now = time()
|
||||
diff = datetime.fromtimestamp(now) - datetime.fromtimestamp(self.__last_mtime)
|
||||
diff_hours = diff.total_seconds() // 3600
|
||||
if diff_hours >= 8:
|
||||
LOGGER.info("GeoIP databased loaded too long, re-opening", diff=diff)
|
||||
self.__open()
|
||||
"""Check if the modification date of the GeoIP database has
|
||||
changed, and reload it if so"""
|
||||
path = CONFIG.y("geoip")
|
||||
try:
|
||||
mtime = stat(path).st_mtime
|
||||
diff = self.__last_mtime < mtime
|
||||
if diff > 0:
|
||||
LOGGER.info("Found new GeoIP Database, reopening", diff=diff)
|
||||
self.__open()
|
||||
except OSError as exc:
|
||||
LOGGER.warning("Failed to check GeoIP age", exc=exc)
|
||||
return
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
|
@ -5,16 +5,6 @@ postgresql:
|
||||
user: authentik
|
||||
port: 5432
|
||||
password: 'env://POSTGRES_PASSWORD'
|
||||
backup:
|
||||
enabled: false
|
||||
s3_backup:
|
||||
access_key: ""
|
||||
secret_key: ""
|
||||
bucket: ""
|
||||
region: eu-central-1
|
||||
host: ""
|
||||
location: ""
|
||||
insecure_skip_verify: false
|
||||
|
||||
web:
|
||||
listen: 0.0.0.0:9000
|
||||
@ -65,6 +55,7 @@ outposts:
|
||||
# %(version)s: Current version; 2021.4.1
|
||||
# %(build_hash)s: Build hash if you're running a beta version
|
||||
container_image_base: ghcr.io/goauthentik/%(type)s:%(version)s
|
||||
discover: true
|
||||
|
||||
cookie_domain: null
|
||||
disable_update_check: false
|
||||
|
6
authentik/lib/merge.py
Normal file
6
authentik/lib/merge.py
Normal file
@ -0,0 +1,6 @@
|
||||
"""merge utils"""
|
||||
from deepmerge import Merger
|
||||
|
||||
MERGE_LIST_UNIQUE = Merger(
|
||||
[(list, ["append_unique"]), (dict, ["merge"]), (set, ["union"])], ["override"], ["override"]
|
||||
)
|
@ -3,8 +3,6 @@ from typing import Optional
|
||||
|
||||
from aioredis.errors import ConnectionClosedError, ReplyError
|
||||
from billiard.exceptions import SoftTimeLimitExceeded, WorkerLostError
|
||||
from botocore.client import ClientError
|
||||
from botocore.exceptions import BotoCoreError
|
||||
from celery.exceptions import CeleryError
|
||||
from channels.middleware import BaseMiddleware
|
||||
from channels_redis.core import ChannelFull
|
||||
@ -81,9 +79,6 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
WorkerLostError,
|
||||
CeleryError,
|
||||
SoftTimeLimitExceeded,
|
||||
# S3 errors
|
||||
BotoCoreError,
|
||||
ClientError,
|
||||
# custom baseclass
|
||||
SentryIgnoredException,
|
||||
# ldap errors
|
||||
@ -101,8 +96,6 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
return None
|
||||
if "logger" in event:
|
||||
if event["logger"] in [
|
||||
"dbbackup",
|
||||
"botocore",
|
||||
"kombu",
|
||||
"asyncio",
|
||||
"multiprocessing",
|
||||
|
@ -3,6 +3,8 @@ import os
|
||||
from pathlib import Path
|
||||
from tempfile import gettempdir
|
||||
|
||||
from docker.errors import DockerException
|
||||
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
|
||||
HEADER = "### Managed by authentik"
|
||||
@ -27,6 +29,8 @@ class DockerInlineSSH:
|
||||
def __init__(self, host: str, keypair: CertificateKeyPair) -> None:
|
||||
self.host = host
|
||||
self.keypair = keypair
|
||||
if not self.keypair:
|
||||
raise DockerException("keypair must be set for SSH connections")
|
||||
self.config_path = Path("~/.ssh/config").expanduser()
|
||||
self.header = f"{HEADER} - {self.host}\n"
|
||||
|
||||
|
@ -23,6 +23,7 @@ from authentik.events.monitored_tasks import (
|
||||
TaskResultStatus,
|
||||
prefill_task,
|
||||
)
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.utils.reflection import path_to_class
|
||||
from authentik.outposts.controllers.base import BaseController, ControllerException
|
||||
from authentik.outposts.controllers.docker import DockerClient
|
||||
@ -231,6 +232,9 @@ def _outpost_single_update(outpost: Outpost, layer=None):
|
||||
@CELERY_APP.task()
|
||||
def outpost_local_connection():
|
||||
"""Checks the local environment and create Service connections."""
|
||||
if not CONFIG.y_bool("outposts.discover"):
|
||||
LOGGER.debug("outpost integration discovery is disabled")
|
||||
return
|
||||
# Explicitly check against token filename, as that's
|
||||
# only present when the integration is enabled
|
||||
if Path(SERVICE_TOKEN_FILENAME).exists():
|
||||
|
@ -45,6 +45,13 @@ class GrantTypes(models.TextChoices):
|
||||
HYBRID = "hybrid"
|
||||
|
||||
|
||||
class ResponseMode(models.TextChoices):
|
||||
"""https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#OAuth.Post"""
|
||||
|
||||
QUERY = "query"
|
||||
FRAGMENT = "fragment"
|
||||
|
||||
|
||||
class SubModes(models.TextChoices):
|
||||
"""Mode after which 'sub' attribute is generateed, for compatibility reasons"""
|
||||
|
||||
|
@ -43,7 +43,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
redirect_uris="http://local.invalid/Foo",
|
||||
)
|
||||
with self.assertRaises(AuthorizeError):
|
||||
request = self.factory.get(
|
||||
@ -51,7 +51,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
data={
|
||||
"response_type": "code",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
"redirect_uri": "http://local.invalid/Foo",
|
||||
"request": "foo",
|
||||
},
|
||||
)
|
||||
@ -105,26 +105,30 @@ class TestAuthorize(OAuthTestCase):
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
redirect_uris="http://local.invalid/Foo",
|
||||
)
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "code",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
"redirect_uri": "http://local.invalid/Foo",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
OAuthAuthorizationParams.from_request(request).grant_type,
|
||||
GrantTypes.AUTHORIZATION_CODE,
|
||||
)
|
||||
self.assertEqual(
|
||||
OAuthAuthorizationParams.from_request(request).redirect_uri,
|
||||
"http://local.invalid/Foo",
|
||||
)
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "id_token",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
"redirect_uri": "http://local.invalid/Foo",
|
||||
"scope": "openid",
|
||||
"state": "foo",
|
||||
},
|
||||
@ -140,7 +144,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
data={
|
||||
"response_type": "id_token",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
"redirect_uri": "http://local.invalid/Foo",
|
||||
"state": "foo",
|
||||
},
|
||||
)
|
||||
@ -153,7 +157,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
data={
|
||||
"response_type": "code token",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
"redirect_uri": "http://local.invalid/Foo",
|
||||
"scope": "openid",
|
||||
"state": "foo",
|
||||
},
|
||||
@ -167,7 +171,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
data={
|
||||
"response_type": "invalid",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
"redirect_uri": "http://local.invalid/Foo",
|
||||
},
|
||||
)
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
|
@ -44,6 +44,7 @@ from authentik.providers.oauth2.models import (
|
||||
AuthorizationCode,
|
||||
GrantTypes,
|
||||
OAuth2Provider,
|
||||
ResponseMode,
|
||||
ResponseTypes,
|
||||
)
|
||||
from authentik.providers.oauth2.utils import HttpResponseRedirectScheme
|
||||
@ -99,7 +100,7 @@ class OAuthAuthorizationParams:
|
||||
# and POST request.
|
||||
query_dict = request.POST if request.method == "POST" else request.GET
|
||||
state = query_dict.get("state")
|
||||
redirect_uri = query_dict.get("redirect_uri", "").lower()
|
||||
redirect_uri = query_dict.get("redirect_uri", "")
|
||||
|
||||
response_type = query_dict.get("response_type", "")
|
||||
grant_type = None
|
||||
@ -153,7 +154,10 @@ class OAuthAuthorizationParams:
|
||||
def check_redirect_uri(self):
|
||||
"""Redirect URI validation."""
|
||||
allowed_redirect_urls = self.provider.redirect_uris.split()
|
||||
if not self.redirect_uri:
|
||||
# We don't want to actually lowercase the final URL we redirect to,
|
||||
# we only lowercase it for comparison
|
||||
redirect_uri = self.redirect_uri.lower()
|
||||
if not redirect_uri:
|
||||
LOGGER.warning("Missing redirect uri.")
|
||||
raise RedirectUriError("", allowed_redirect_urls)
|
||||
|
||||
@ -169,7 +173,7 @@ class OAuthAuthorizationParams:
|
||||
allow=self.redirect_uri,
|
||||
)
|
||||
return
|
||||
if self.redirect_uri not in [x.lower() for x in allowed_redirect_urls]:
|
||||
if redirect_uri not in [x.lower() for x in allowed_redirect_urls]:
|
||||
LOGGER.warning(
|
||||
"Invalid redirect uri",
|
||||
redirect_uri=self.redirect_uri,
|
||||
@ -299,13 +303,23 @@ class OAuthFulfillmentStage(StageView):
|
||||
code = self.params.create_code(self.request)
|
||||
code.save(force_insert=True)
|
||||
|
||||
if self.params.grant_type == GrantTypes.AUTHORIZATION_CODE:
|
||||
query_dict = self.request.POST if self.request.method == "POST" else self.request.GET
|
||||
response_mode = ResponseMode.QUERY
|
||||
# Get response mode from url param, otherwise decide based on grant type
|
||||
if "response_mode" in query_dict:
|
||||
response_mode = query_dict["response_mode"]
|
||||
elif self.params.grant_type == GrantTypes.AUTHORIZATION_CODE:
|
||||
response_mode = ResponseMode.QUERY
|
||||
elif self.params.grant_type in [GrantTypes.IMPLICIT, GrantTypes.HYBRID]:
|
||||
response_mode = ResponseMode.FRAGMENT
|
||||
|
||||
if response_mode == ResponseMode.QUERY:
|
||||
query_params["code"] = code.code
|
||||
query_params["state"] = [str(self.params.state) if self.params.state else ""]
|
||||
|
||||
uri = uri._replace(query=urlencode(query_params, doseq=True))
|
||||
return urlunsplit(uri)
|
||||
if self.params.grant_type in [GrantTypes.IMPLICIT, GrantTypes.HYBRID]:
|
||||
if response_mode == ResponseMode.FRAGMENT:
|
||||
query_fragment = self.create_implicit_response(code)
|
||||
|
||||
uri = uri._replace(
|
||||
|
@ -12,4 +12,8 @@ class AuthentikProviderProxyConfig(AppConfig):
|
||||
verbose_name = "authentik Providers.Proxy"
|
||||
|
||||
def ready(self) -> None:
|
||||
from authentik.providers.proxy.tasks import proxy_set_defaults
|
||||
|
||||
import_module("authentik.providers.proxy.managed")
|
||||
|
||||
proxy_set_defaults.delay()
|
||||
|
@ -28,12 +28,12 @@ class ProxyDockerController(DockerController):
|
||||
labels["traefik.enable"] = "true"
|
||||
labels[
|
||||
f"traefik.http.routers.{traefik_name}-router.rule"
|
||||
] = f"Host({','.join(hosts)}) && PathPrefix(`/akprox`)"
|
||||
] = f"Host({','.join(hosts)}) && PathPrefix(`/outpost.goauthentik.io`)"
|
||||
labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true"
|
||||
labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service"
|
||||
labels[
|
||||
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"
|
||||
] = "/akprox/ping"
|
||||
] = "/outpost.goauthentik.io/ping"
|
||||
labels[
|
||||
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.port"
|
||||
] = "9300"
|
||||
|
@ -92,6 +92,8 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
|
||||
# Buffer sizes for large headers with JWTs
|
||||
"nginx.ingress.kubernetes.io/proxy-buffers-number": "4",
|
||||
"nginx.ingress.kubernetes.io/proxy-buffer-size": "16k",
|
||||
# Enable TLS in traefik
|
||||
"traefik.ingress.kubernetes.io/router.tls": "true",
|
||||
}
|
||||
annotations.update(self.controller.outpost.config.kubernetes_ingress_annotations)
|
||||
return annotations
|
||||
@ -126,7 +128,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
|
||||
port=V1ServiceBackendPort(name="http"),
|
||||
),
|
||||
),
|
||||
path="/akprox",
|
||||
path="/outpost.goauthentik.io",
|
||||
path_type="ImplementationSpecific",
|
||||
)
|
||||
]
|
||||
|
@ -119,7 +119,10 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
|
||||
),
|
||||
spec=TraefikMiddlewareSpec(
|
||||
forwardAuth=TraefikMiddlewareSpecForwardAuth(
|
||||
address=f"http://{self.name}.{self.namespace}:9000/akprox/auth/traefik",
|
||||
address=(
|
||||
f"http://{self.name}.{self.namespace}:9000/"
|
||||
"outpost.goauthentik.io/auth/traefik"
|
||||
),
|
||||
authResponseHeaders=[
|
||||
"X-authentik-username",
|
||||
"X-authentik-groups",
|
||||
|
@ -27,7 +27,7 @@ def get_cookie_secret():
|
||||
|
||||
|
||||
def _get_callback_url(uri: str) -> str:
|
||||
return urljoin(uri, "/akprox/callback")
|
||||
return urljoin(uri, "outpost.goauthentik.io/callback")
|
||||
|
||||
|
||||
class ProxyMode(models.TextChoices):
|
||||
|
11
authentik/providers/proxy/tasks.py
Normal file
11
authentik/providers/proxy/tasks.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""proxy provider tasks"""
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
from authentik.root.celery import CELERY_APP
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
def proxy_set_defaults():
|
||||
"""Ensure correct defaults are set for all providers"""
|
||||
for provider in ProxyProvider.objects.all():
|
||||
provider.set_oauth_defaults()
|
||||
provider.save()
|
@ -6,7 +6,6 @@ import os
|
||||
import sys
|
||||
from hashlib import sha512
|
||||
from json import dumps
|
||||
from tempfile import gettempdir
|
||||
from time import time
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
@ -137,7 +136,6 @@ INSTALLED_APPS = [
|
||||
"guardian",
|
||||
"django_prometheus",
|
||||
"channels",
|
||||
"dbbackup",
|
||||
]
|
||||
|
||||
GUARDIAN_MONKEY_PATCH = False
|
||||
@ -357,32 +355,6 @@ CELERY_RESULT_BACKEND = (
|
||||
f"{_redis_url}/{CONFIG.y('redis.message_queue_db')}{REDIS_CELERY_TLS_REQUIREMENTS}"
|
||||
)
|
||||
|
||||
# Database backup
|
||||
DBBACKUP_STORAGE = "django.core.files.storage.FileSystemStorage"
|
||||
DBBACKUP_STORAGE_OPTIONS = {"location": "./backups" if DEBUG else "/backups"}
|
||||
DBBACKUP_FILENAME_TEMPLATE = f"authentik-backup-{__version__}-{{datetime}}.sql"
|
||||
DBBACKUP_CONNECTOR_MAPPING = {
|
||||
"django_prometheus.db.backends.postgresql": "dbbackup.db.postgresql.PgDumpConnector",
|
||||
}
|
||||
DBBACKUP_TMP_DIR = gettempdir() if DEBUG else "/tmp" # nosec
|
||||
DBBACKUP_CLEANUP_KEEP = 10
|
||||
if CONFIG.y("postgresql.s3_backup.bucket", "") != "":
|
||||
DBBACKUP_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
|
||||
DBBACKUP_STORAGE_OPTIONS = {
|
||||
"access_key": CONFIG.y("postgresql.s3_backup.access_key"),
|
||||
"secret_key": CONFIG.y("postgresql.s3_backup.secret_key"),
|
||||
"bucket_name": CONFIG.y("postgresql.s3_backup.bucket"),
|
||||
"region_name": CONFIG.y("postgresql.s3_backup.region", "eu-central-1"),
|
||||
"default_acl": "private",
|
||||
"endpoint_url": CONFIG.y("postgresql.s3_backup.host"),
|
||||
"location": CONFIG.y("postgresql.s3_backup.location", ""),
|
||||
"verify": not CONFIG.y_bool("postgresql.s3_backup.insecure_skip_verify", False),
|
||||
}
|
||||
j_print(
|
||||
"Database backup to S3 is configured",
|
||||
host=CONFIG.y("postgresql.s3_backup.host"),
|
||||
)
|
||||
|
||||
# Sentry integration
|
||||
SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
|
||||
|
||||
@ -493,12 +465,9 @@ _LOGGING_HANDLER_MAP = {
|
||||
"urllib3": "WARNING",
|
||||
"websockets": "WARNING",
|
||||
"daphne": "WARNING",
|
||||
"dbbackup": "ERROR",
|
||||
"kubernetes": "INFO",
|
||||
"asyncio": "WARNING",
|
||||
"aioredis": "WARNING",
|
||||
"s3transfer": "WARNING",
|
||||
"botocore": "WARNING",
|
||||
}
|
||||
for handler_name, level in _LOGGING_HANDLER_MAP.items():
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
|
@ -1,13 +1,13 @@
|
||||
"""Sync LDAP Users and groups into authentik"""
|
||||
from typing import Any
|
||||
|
||||
from deepmerge import always_merger
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.query import QuerySet
|
||||
from structlog.stdlib import BoundLogger, get_logger
|
||||
|
||||
from authentik.core.exceptions import PropertyMappingExpressionException
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.merge import MERGE_LIST_UNIQUE
|
||||
from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME
|
||||
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
||||
|
||||
@ -123,8 +123,8 @@ class BaseLDAPSynchronizer:
|
||||
continue
|
||||
setattr(instance, key, value)
|
||||
final_atttributes = {}
|
||||
always_merger.merge(final_atttributes, instance.attributes)
|
||||
always_merger.merge(final_atttributes, data.get("attributes", {}))
|
||||
MERGE_LIST_UNIQUE.merge(final_atttributes, instance.attributes)
|
||||
MERGE_LIST_UNIQUE.merge(final_atttributes, data.get("attributes", {}))
|
||||
instance.attributes = final_atttributes
|
||||
instance.save()
|
||||
return (instance, False)
|
||||
|
@ -13,8 +13,8 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
|
||||
|
||||
def validate_not_configured_action(self, value):
|
||||
"""Ensure that a configuration stage is set when not_configured_action is configure"""
|
||||
configuration_stage = self.initial_data.get("configuration_stage")
|
||||
if value == NotConfiguredAction.CONFIGURE and configuration_stage is None:
|
||||
configuration_stages = self.initial_data.get("configuration_stages")
|
||||
if value == NotConfiguredAction.CONFIGURE and configuration_stages is None:
|
||||
raise ValidationError(
|
||||
(
|
||||
'When "Not configured action" is set to "Configure", '
|
||||
@ -29,7 +29,7 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
|
||||
fields = StageSerializer.Meta.fields + [
|
||||
"not_configured_action",
|
||||
"device_classes",
|
||||
"configuration_stage",
|
||||
"configuration_stages",
|
||||
]
|
||||
|
||||
|
||||
@ -38,5 +38,5 @@ class AuthenticatorValidateStageViewSet(UsedByMixin, ModelViewSet):
|
||||
|
||||
queryset = AuthenticatorValidateStage.objects.all()
|
||||
serializer_class = AuthenticatorValidateStageSerializer
|
||||
filterset_fields = ["name", "not_configured_action", "configuration_stage"]
|
||||
filterset_fields = ["name", "not_configured_action", "configuration_stages"]
|
||||
ordering = ["name"]
|
||||
|
@ -0,0 +1,44 @@
|
||||
# Generated by Django 4.0.1 on 2022-01-05 22:09
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def migrate_configuration_stage(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
AuthenticatorValidateStage = apps.get_model(
|
||||
"authentik_stages_authenticator_validate", "AuthenticatorValidateStage"
|
||||
)
|
||||
|
||||
for stage in AuthenticatorValidateStage.objects.using(db_alias).all():
|
||||
if stage.configuration_stage:
|
||||
stage.configuration_stages.set([stage.configuration_stage])
|
||||
stage.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0021_auto_20211227_2103"),
|
||||
("authentik_stages_authenticator_validate", "0009_default_stage"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="authenticatorvalidatestage",
|
||||
name="configuration_stages",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.",
|
||||
related_name="+",
|
||||
to="authentik_flows.Stage",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(migrate_configuration_stage),
|
||||
migrations.RemoveField(
|
||||
model_name="authenticatorvalidatestage",
|
||||
name="configuration_stage",
|
||||
),
|
||||
]
|
@ -38,16 +38,14 @@ class AuthenticatorValidateStage(Stage):
|
||||
choices=NotConfiguredAction.choices, default=NotConfiguredAction.SKIP
|
||||
)
|
||||
|
||||
configuration_stage = models.ForeignKey(
|
||||
configuration_stages = models.ManyToManyField(
|
||||
Stage,
|
||||
null=True,
|
||||
blank=True,
|
||||
default=None,
|
||||
on_delete=models.SET_DEFAULT,
|
||||
related_name="+",
|
||||
help_text=_(
|
||||
(
|
||||
"Stage used to configure Authenticator when user doesn't have any compatible "
|
||||
"Stages used to configure Authenticator when user doesn't have any compatible "
|
||||
"devices. After this configuration Stage passes, the user is not prompted again."
|
||||
)
|
||||
),
|
||||
|
@ -1,10 +1,12 @@
|
||||
"""Authenticator Validation"""
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django_otp import devices_for_user
|
||||
from rest_framework.fields import CharField, IntegerField, JSONField, ListField
|
||||
from rest_framework.fields import CharField, IntegerField, JSONField, ListField, UUIDField
|
||||
from rest_framework.serializers import ValidationError
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.core.models import User
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.events.utils import cleanse_dict, sanitize_dict
|
||||
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
|
||||
@ -26,6 +28,18 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
||||
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
|
||||
|
||||
LOGGER = get_logger()
|
||||
SESSION_STAGES = "goauthentik.io/stages/authenticator_validate/stages"
|
||||
SESSION_SELECTED_STAGE = "goauthentik.io/stages/authenticator_validate/selected_stage"
|
||||
SESSION_DEVICE_CHALLENGES = "goauthentik.io/stages/authenticator_validate/device_challenges"
|
||||
|
||||
|
||||
class SelectableStageSerializer(PassiveSerializer):
|
||||
"""Serializer for stages which can be selected by users"""
|
||||
|
||||
pk = UUIDField()
|
||||
name = CharField()
|
||||
verbose_name = CharField()
|
||||
meta_model_name = CharField()
|
||||
|
||||
|
||||
class AuthenticatorValidationChallenge(WithUserInfoChallenge):
|
||||
@ -33,12 +47,14 @@ class AuthenticatorValidationChallenge(WithUserInfoChallenge):
|
||||
|
||||
device_challenges = ListField(child=DeviceChallenge())
|
||||
component = CharField(default="ak-stage-authenticator-validate")
|
||||
configuration_stages = ListField(child=SelectableStageSerializer())
|
||||
|
||||
|
||||
class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
||||
"""Challenge used for Code-based and WebAuthn authenticators"""
|
||||
|
||||
selected_challenge = DeviceChallenge(required=False)
|
||||
selected_stage = CharField(required=False)
|
||||
|
||||
code = CharField(required=False)
|
||||
webauthn = JSONField(required=False)
|
||||
@ -46,7 +62,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
||||
component = CharField(default="ak-stage-authenticator-validate")
|
||||
|
||||
def _challenge_allowed(self, classes: list):
|
||||
device_challenges: list[dict] = self.stage.request.session.get("device_challenges")
|
||||
device_challenges: list[dict] = self.stage.request.session.get(SESSION_DEVICE_CHALLENGES)
|
||||
if not any(x["device_class"] in classes for x in device_challenges):
|
||||
raise ValidationError("No compatible device class allowed")
|
||||
|
||||
@ -71,7 +87,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
||||
def validate_selected_challenge(self, challenge: dict) -> dict:
|
||||
"""Check which challenge the user has selected. Actual logic only used for SMS stage."""
|
||||
# First check if the challenge is valid
|
||||
for device_challenge in self.stage.request.session.get("device_challenges"):
|
||||
for device_challenge in self.stage.request.session.get(SESSION_DEVICE_CHALLENGES):
|
||||
if device_challenge.get("device_class", "") != challenge.get("device_class", ""):
|
||||
raise ValidationError("invalid challenge selected")
|
||||
if device_challenge.get("device_uid", "") != challenge.get("device_uid", ""):
|
||||
@ -84,6 +100,15 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
||||
select_challenge(self.stage.request, devices.first())
|
||||
return challenge
|
||||
|
||||
def validate_selected_stage(self, stage_pk: str) -> str:
|
||||
"""Check that the selected stage is valid"""
|
||||
stages = self.stage.request.session.get(SESSION_STAGES, [])
|
||||
if not any(str(stage.pk) == stage_pk for stage in stages):
|
||||
raise ValidationError("Selected stage is invalid")
|
||||
LOGGER.debug("Setting selected stage to ", stage=stage_pk)
|
||||
self.stage.request.session[SESSION_SELECTED_STAGE] = stage_pk
|
||||
return stage_pk
|
||||
|
||||
def validate(self, attrs: dict):
|
||||
# Checking if the given data is from a valid device class is done above
|
||||
# Here we only check if the any data was sent at all
|
||||
@ -164,7 +189,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||
else:
|
||||
LOGGER.debug("No pending user, continuing")
|
||||
return self.executor.stage_ok()
|
||||
self.request.session["device_challenges"] = challenges
|
||||
self.request.session[SESSION_DEVICE_CHALLENGES] = challenges
|
||||
|
||||
# No allowed devices
|
||||
if len(challenges) < 1:
|
||||
@ -175,35 +200,74 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||
LOGGER.debug("Authenticator not configured, denying")
|
||||
return self.executor.stage_invalid()
|
||||
if stage.not_configured_action == NotConfiguredAction.CONFIGURE:
|
||||
if not stage.configuration_stage:
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message=(
|
||||
"Authenticator validation stage is set to configure user "
|
||||
"but no configuration flow is set."
|
||||
),
|
||||
stage=self,
|
||||
).from_http(self.request).set_user(user).save()
|
||||
return self.executor.stage_invalid()
|
||||
LOGGER.debug("Authenticator not configured, sending user to configure")
|
||||
# Because the foreign key to stage.configuration_stage points to
|
||||
# a base stage class, we need to do another lookup
|
||||
stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk)
|
||||
# plan.insert inserts at 1 index, so when stage_ok pops 0,
|
||||
# the configuration stage is next
|
||||
self.executor.plan.insert_stage(stage)
|
||||
return self.executor.stage_ok()
|
||||
LOGGER.debug("Authenticator not configured, forcing configure")
|
||||
return self.prepare_stages(user)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_challenge(self) -> AuthenticatorValidationChallenge:
|
||||
challenges = self.request.session.get("device_challenges")
|
||||
if not challenges:
|
||||
LOGGER.debug("Authenticator Validation stage ran without challenges")
|
||||
def prepare_stages(self, user: User, *args, **kwargs) -> HttpResponse:
|
||||
"""Check how the user can configure themselves. If no stages are set, return an error.
|
||||
If a single stage is set, insert that stage directly. If multiple are selected, include
|
||||
them in the challenge."""
|
||||
stage: AuthenticatorValidateStage = self.executor.current_stage
|
||||
if not stage.configuration_stages.exists():
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message=(
|
||||
"Authenticator validation stage is set to configure user "
|
||||
"but no configuration flow is set."
|
||||
),
|
||||
stage=self,
|
||||
).from_http(self.request).set_user(user).save()
|
||||
return self.executor.stage_invalid()
|
||||
if stage.configuration_stages.count() == 1:
|
||||
next_stage = Stage.objects.get_subclass(pk=stage.configuration_stages.first().pk)
|
||||
LOGGER.debug("Single stage configured, auto-selecting", stage=next_stage)
|
||||
self.request.session[SESSION_SELECTED_STAGE] = next_stage
|
||||
# Because that normal insetion only happens on post, we directly inject it here and
|
||||
# return it
|
||||
self.executor.plan.insert_stage(next_stage)
|
||||
return self.executor.stage_ok()
|
||||
stages = Stage.objects.filter(pk__in=stage.configuration_stages.all()).select_subclasses()
|
||||
self.request.session[SESSION_STAGES] = stages
|
||||
return super().get(self.request, *args, **kwargs)
|
||||
|
||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
res = super().post(request, *args, **kwargs)
|
||||
if (
|
||||
SESSION_SELECTED_STAGE in self.request.session
|
||||
and self.executor.current_stage.not_configured_action == NotConfiguredAction.CONFIGURE
|
||||
):
|
||||
LOGGER.debug("Got selected stage in session, running that")
|
||||
stage_pk = self.request.session.get(SESSION_SELECTED_STAGE)
|
||||
# Because the foreign key to stage.configuration_stage points to
|
||||
# a base stage class, we need to do another lookup
|
||||
stage = Stage.objects.get_subclass(pk=stage_pk)
|
||||
# plan.insert inserts at 1 index, so when stage_ok pops 0,
|
||||
# the configuration stage is next
|
||||
self.executor.plan.insert_stage(stage)
|
||||
return self.executor.stage_ok()
|
||||
return res
|
||||
|
||||
def get_challenge(self) -> AuthenticatorValidationChallenge:
|
||||
challenges = self.request.session.get(SESSION_DEVICE_CHALLENGES, [])
|
||||
stages = self.request.session.get(SESSION_STAGES, [])
|
||||
stage_challenges = []
|
||||
for stage in stages:
|
||||
serializer = SelectableStageSerializer(
|
||||
data={
|
||||
"pk": stage.pk,
|
||||
"name": stage.name,
|
||||
"verbose_name": str(stage._meta.verbose_name),
|
||||
"meta_model_name": f"{stage._meta.app_label}.{stage._meta.model_name}",
|
||||
}
|
||||
)
|
||||
serializer.is_valid()
|
||||
stage_challenges.append(serializer.data)
|
||||
return AuthenticatorValidationChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.NATIVE.value,
|
||||
"device_challenges": challenges,
|
||||
"configuration_stages": stage_challenges,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -43,8 +43,8 @@ class AuthenticatorValidateStageTests(FlowTestCase):
|
||||
stage = AuthenticatorValidateStage.objects.create(
|
||||
name="foo",
|
||||
not_configured_action=NotConfiguredAction.CONFIGURE,
|
||||
configuration_stage=conf_stage,
|
||||
)
|
||||
stage.configuration_stages.set([conf_stage])
|
||||
flow = Flow.objects.create(name="test", slug="test", title="test")
|
||||
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
|
||||
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
|
||||
|
@ -17,7 +17,7 @@ services:
|
||||
image: redis:alpine
|
||||
restart: unless-stopped
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.5}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.2.1}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -38,7 +38,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.1.5}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.2.1}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
8
go.mod
8
go.mod
@ -8,16 +8,16 @@ require (
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/garyburd/redigo v1.6.2 // indirect
|
||||
github.com/getsentry/sentry-go v0.12.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.1
|
||||
github.com/go-openapi/runtime v0.22.0
|
||||
github.com/go-openapi/strfmt v0.21.1
|
||||
github.com/go-ldap/ldap/v3 v3.4.2
|
||||
github.com/go-openapi/runtime v0.23.0
|
||||
github.com/go-openapi/strfmt v0.21.2
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
|
16
go.sum
16
go.sum
@ -125,8 +125,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-ldap/ldap/v3 v3.4.1 h1:fU/0xli6HY02ocbMuozHAYsaHLcnkLjvho2r5a34BUU=
|
||||
github.com/go-ldap/ldap/v3 v3.4.1/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||
github.com/go-ldap/ldap/v3 v3.4.2 h1:zFZKcXKLqZpFMrMQGHeHWKXbDTdNCmhGY9AK41zPh+8=
|
||||
github.com/go-ldap/ldap/v3 v3.4.2/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
@ -183,8 +183,8 @@ github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29g
|
||||
github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
|
||||
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
|
||||
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
|
||||
github.com/go-openapi/runtime v0.22.0 h1:vY2D0u807kkcwidaj0YJuq4zyAWQnjLNDpJcVBrUFNs=
|
||||
github.com/go-openapi/runtime v0.22.0/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs=
|
||||
github.com/go-openapi/runtime v0.23.0 h1:HX6ET2sHCIvaKeDDQoU01CtO1ekg5EkekHSkLTtWXH0=
|
||||
github.com/go-openapi/runtime v0.23.0/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs=
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
||||
@ -208,8 +208,8 @@ github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLs
|
||||
github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc=
|
||||
github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk=
|
||||
github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
|
||||
github.com/go-openapi/strfmt v0.21.1 h1:G6s2t5V5kGCHLVbSdZ/6lI8Wm4OzoPFkc3/cjAsKQrM=
|
||||
github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
|
||||
github.com/go-openapi/strfmt v0.21.2 h1:5NDNgadiX1Vhemth/TH4gCGopWSTdDjxl60H3B7f+os=
|
||||
github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
@ -334,8 +334,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+
|
||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
|
@ -25,4 +25,4 @@ func OutpostUserAgent() string {
|
||||
return fmt.Sprintf("authentik-outpost@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2022.1.5"
|
||||
const VERSION = "2022.2.1"
|
||||
|
@ -25,7 +25,7 @@ var (
|
||||
func RunServer() {
|
||||
m := mux.NewRouter()
|
||||
l := log.WithField("logger", "authentik.outpost.metrics")
|
||||
m.HandleFunc("/akprox/ping", func(rw http.ResponseWriter, r *http.Request) {
|
||||
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(204)
|
||||
})
|
||||
m.Path("/metrics").Handler(promhttp.Handler())
|
||||
|
@ -78,7 +78,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
||||
oauth2Config := oauth2.Config{
|
||||
ClientID: *p.ClientId,
|
||||
ClientSecret: *p.ClientSecret,
|
||||
RedirectURL: urlJoin(p.ExternalHost, "/akprox/callback"),
|
||||
RedirectURL: urlJoin(p.ExternalHost, "/outpost.goauthentik.io/callback"),
|
||||
Endpoint: endpoint.Endpoint,
|
||||
Scopes: p.ScopesToRequest,
|
||||
}
|
||||
@ -145,10 +145,10 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
||||
mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
|
||||
|
||||
// Support /start and /sign_in for backwards compatibility
|
||||
mux.HandleFunc("/akprox/start", a.handleRedirect)
|
||||
mux.HandleFunc("/akprox/sign_in", a.handleRedirect)
|
||||
mux.HandleFunc("/akprox/callback", a.handleCallback)
|
||||
mux.HandleFunc("/akprox/sign_out", a.handleSignOut)
|
||||
mux.HandleFunc("/outpost.goauthentik.io/start", a.handleRedirect)
|
||||
mux.HandleFunc("/outpost.goauthentik.io/sign_in", a.handleRedirect)
|
||||
mux.HandleFunc("/outpost.goauthentik.io/callback", a.handleCallback)
|
||||
mux.HandleFunc("/outpost.goauthentik.io/sign_out", a.handleSignOut)
|
||||
switch *p.Mode {
|
||||
case api.PROXYMODE_PROXY:
|
||||
err = a.configureProxy()
|
||||
|
@ -18,7 +18,7 @@ func (a *Application) ErrorPage(rw http.ResponseWriter, r *http.Request, err str
|
||||
data := ErrorPageData{
|
||||
Title: "Bad Gateway",
|
||||
Message: "Error proxying to upstream server",
|
||||
ProxyPrefix: "/akprox",
|
||||
ProxyPrefix: "/outpost.goauthentik.io",
|
||||
}
|
||||
if claims != nil && len(err) > 0 {
|
||||
data.Message = err
|
||||
|
@ -12,15 +12,15 @@ import (
|
||||
)
|
||||
|
||||
func (a *Application) configureForward() error {
|
||||
a.mux.HandleFunc("/akprox/auth", func(rw http.ResponseWriter, r *http.Request) {
|
||||
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("/akprox/auth/traefik", a.forwardHandleTraefik)
|
||||
a.mux.HandleFunc("/akprox/auth/nginx", a.forwardHandleNginx)
|
||||
a.mux.HandleFunc("/outpost.goauthentik.io/auth/traefik", a.forwardHandleTraefik)
|
||||
a.mux.HandleFunc("/outpost.goauthentik.io/auth/nginx", a.forwardHandleNginx)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -49,8 +49,8 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
|
||||
a.log.Trace("path can be accessed without authentication")
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(r.Header.Get("X-Forwarded-Uri"), "/akprox") {
|
||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /akprox, allowing access")
|
||||
if strings.HasPrefix(r.Header.Get("X-Forwarded-Uri"), "/outpost.goauthentik.io") {
|
||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /outpost.goauthentik.io, allowing access")
|
||||
return
|
||||
}
|
||||
host := ""
|
||||
@ -80,7 +80,7 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
|
||||
if proto != "" {
|
||||
proto = proto + ":"
|
||||
}
|
||||
rdFinal := fmt.Sprintf("%s//%s%s", proto, host, "/akprox/start")
|
||||
rdFinal := fmt.Sprintf("%s//%s%s", proto, host, "/outpost.goauthentik.io/start")
|
||||
a.log.WithField("url", rdFinal).Debug("Redirecting to login")
|
||||
http.Redirect(rw, r, rdFinal, http.StatusTemporaryRedirect)
|
||||
}
|
||||
@ -119,8 +119,8 @@ func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
if fwd.String() != r.URL.String() {
|
||||
if strings.HasPrefix(fwd.Path, "/akprox") {
|
||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /akprox, allowing access")
|
||||
if strings.HasPrefix(fwd.Path, "/outpost.goauthentik.io") {
|
||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /outpost.goauthentik.io, allowing access")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
|
||||
func TestForwardHandleNginx_Single_Blank(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
@ -22,7 +22,7 @@ func TestForwardHandleNginx_Single_Blank(t *testing.T) {
|
||||
|
||||
func TestForwardHandleNginx_Single_Skip(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URL", "http://test.goauthentik.io/skip")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@ -33,7 +33,7 @@ func TestForwardHandleNginx_Single_Skip(t *testing.T) {
|
||||
|
||||
func TestForwardHandleNginx_Single_Headers(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@ -47,7 +47,7 @@ func TestForwardHandleNginx_Single_Headers(t *testing.T) {
|
||||
|
||||
func TestForwardHandleNginx_Single_URI(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "https://foo.bar/akprox/auth/nginx", nil)
|
||||
req, _ := http.NewRequest("GET", "https://foo.bar/outpost.goauthentik.io/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URI", "/app")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@ -61,7 +61,7 @@ func TestForwardHandleNginx_Single_URI(t *testing.T) {
|
||||
|
||||
func TestForwardHandleNginx_Single_Claims(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URI", "/")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@ -108,7 +108,7 @@ func TestForwardHandleNginx_Domain_Blank(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
@ -121,7 +121,7 @@ func TestForwardHandleNginx_Domain_Header(t *testing.T) {
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||
a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io"
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
|
||||
func TestForwardHandleTraefik_Single_Blank(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleTraefik(rr, req)
|
||||
@ -22,7 +22,7 @@ func TestForwardHandleTraefik_Single_Blank(t *testing.T) {
|
||||
|
||||
func TestForwardHandleTraefik_Single_Skip(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil)
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||
req.Header.Set("X-Forwarded-Uri", "/skip")
|
||||
@ -35,7 +35,7 @@ func TestForwardHandleTraefik_Single_Skip(t *testing.T) {
|
||||
|
||||
func TestForwardHandleTraefik_Single_Headers(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil)
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||
req.Header.Set("X-Forwarded-Uri", "/app")
|
||||
@ -45,7 +45,7 @@ func TestForwardHandleTraefik_Single_Headers(t *testing.T) {
|
||||
|
||||
assert.Equal(t, rr.Code, http.StatusTemporaryRedirect)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, loc.String(), "http://test.goauthentik.io/akprox/start")
|
||||
assert.Equal(t, loc.String(), "http://test.goauthentik.io/outpost.goauthentik.io/start")
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||
@ -53,7 +53,7 @@ func TestForwardHandleTraefik_Single_Headers(t *testing.T) {
|
||||
|
||||
func TestForwardHandleTraefik_Single_Claims(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil)
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||
req.Header.Set("X-Forwarded-Uri", "/app")
|
||||
@ -102,7 +102,7 @@ func TestForwardHandleTraefik_Domain_Blank(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleTraefik(rr, req)
|
||||
@ -115,7 +115,7 @@ func TestForwardHandleTraefik_Domain_Header(t *testing.T) {
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||
a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io"
|
||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil)
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||
req.Header.Set("X-Forwarded-Uri", "/app")
|
||||
@ -125,7 +125,7 @@ func TestForwardHandleTraefik_Domain_Header(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, rr.Code)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, "http://auth.test.goauthentik.io/akprox/start", loc.String())
|
||||
assert.Equal(t, "http://auth.test.goauthentik.io/outpost.goauthentik.io/start", loc.String())
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||
|
@ -73,10 +73,10 @@ func (a *Application) configureProxy() error {
|
||||
|
||||
func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) {
|
||||
return func(r *http.Request) {
|
||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||
claims, _ := a.getClaims(r)
|
||||
r.URL.Scheme = ou.Scheme
|
||||
r.URL.Host = ou.Host
|
||||
r.Host = ou.Host
|
||||
if claims != nil && claims.Proxy != nil && claims.Proxy.BackendOverride != "" {
|
||||
u, err := url.Parse(claims.Proxy.BackendOverride)
|
||||
if err != nil {
|
||||
@ -84,7 +84,6 @@ func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) {
|
||||
} else {
|
||||
r.URL.Scheme = u.Scheme
|
||||
r.URL.Host = u.Host
|
||||
r.Host = u.Host
|
||||
}
|
||||
}
|
||||
a.log.WithField("upstream_url", r.URL.String()).Trace("final upstream url")
|
||||
|
@ -19,9 +19,10 @@ func TestProxy_ModifyRequest(t *testing.T) {
|
||||
}
|
||||
a.proxyModifyRequest(u)(req)
|
||||
|
||||
assert.Equal(t, "frontend", req.Header.Get("X-Forwarded-Host"))
|
||||
assert.Equal(t, "/foo", req.URL.Path)
|
||||
assert.Equal(t, "backend:8012", req.URL.Host)
|
||||
assert.Equal(t, "backend:8012", req.Host)
|
||||
assert.Equal(t, "frontend", req.Host)
|
||||
}
|
||||
|
||||
func TestProxy_ModifyRequest_Claims(t *testing.T) {
|
||||
@ -49,7 +50,7 @@ func TestProxy_ModifyRequest_Claims(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "/foo", req.URL.Path)
|
||||
assert.Equal(t, "other-backend:8123", req.URL.Host)
|
||||
assert.Equal(t, "other-backend:8123", req.Host)
|
||||
assert.Equal(t, "frontend", req.Host)
|
||||
}
|
||||
|
||||
func TestProxy_ModifyRequest_Claims_Invalid(t *testing.T) {
|
||||
@ -77,5 +78,5 @@ func TestProxy_ModifyRequest_Claims_Invalid(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "/foo", req.URL.Path)
|
||||
assert.Equal(t, "backend:8012", req.URL.Host)
|
||||
assert.Equal(t, "backend:8012", req.Host)
|
||||
assert.Equal(t, "frontend", req.Host)
|
||||
}
|
||||
|
@ -3,14 +3,46 @@ package application
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
"goauthentik.io/api"
|
||||
"goauthentik.io/internal/outpost/proxyv2/constants"
|
||||
)
|
||||
|
||||
const (
|
||||
redirectParam = "rd"
|
||||
)
|
||||
|
||||
func (a *Application) checkRedirectParam(r *http.Request) (string, bool) {
|
||||
rd := r.URL.Query().Get(redirectParam)
|
||||
if rd == "" {
|
||||
return "", false
|
||||
}
|
||||
u, err := url.Parse(rd)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Warning("Failed to parse redirect URL")
|
||||
return "", false
|
||||
}
|
||||
// Check to make sure we only redirect to allowed places
|
||||
if a.Mode() == api.PROXYMODE_PROXY || a.Mode() == api.PROXYMODE_FORWARD_SINGLE {
|
||||
if !strings.Contains(u.String(), a.proxyConfig.ExternalHost) {
|
||||
a.log.WithField("url", u.String()).WithField("ext", a.proxyConfig.ExternalHost).Warning("redirect URI did not contain external host")
|
||||
return "", false
|
||||
}
|
||||
} else {
|
||||
if !strings.HasSuffix(u.Host, *a.proxyConfig.CookieDomain) {
|
||||
a.log.WithField("host", u.Host).WithField("dom", *a.proxyConfig.CookieDomain).Warning("redirect URI Host was not included in cookie domain")
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return u.String(), true
|
||||
}
|
||||
|
||||
func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) {
|
||||
newState := base64.RawStdEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
||||
newState := base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
||||
s, err := a.sessions.Get(r, constants.SeesionName)
|
||||
if err != nil {
|
||||
s.Values[constants.SessionOAuthState] = []string{}
|
||||
@ -20,6 +52,11 @@ func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) {
|
||||
s.Values[constants.SessionOAuthState] = []string{}
|
||||
state = []string{}
|
||||
}
|
||||
rd, ok := a.checkRedirectParam(r)
|
||||
if ok {
|
||||
s.Values[constants.SessionRedirect] = rd
|
||||
a.log.WithField("rd", rd).Trace("Setting redirect")
|
||||
}
|
||||
s.Values[constants.SessionOAuthState] = append(state, newState)
|
||||
err = s.Save(r, rw)
|
||||
if err != nil {
|
||||
@ -29,7 +66,10 @@ func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a *Application) handleCallback(rw http.ResponseWriter, r *http.Request) {
|
||||
s, _ := a.sessions.Get(r, constants.SeesionName)
|
||||
s, err := a.sessions.Get(r, constants.SeesionName)
|
||||
if err != nil {
|
||||
a.log.WithError(err).Trace("failed to get session")
|
||||
}
|
||||
state, ok := s.Values[constants.SessionOAuthState]
|
||||
if !ok {
|
||||
a.log.Warning("No state saved in session")
|
||||
@ -62,8 +102,8 @@ func (a *Application) handleCallback(rw http.ResponseWriter, r *http.Request) {
|
||||
redirect := a.proxyConfig.ExternalHost
|
||||
redirectR, ok := s.Values[constants.SessionRedirect]
|
||||
if ok {
|
||||
a.log.WithField("redirect", redirectR).Trace("got final redirect from session")
|
||||
redirect = redirectR.(string)
|
||||
}
|
||||
a.log.WithField("redirect", redirect).Trace("final redirect")
|
||||
http.Redirect(rw, r, redirect, http.StatusFound)
|
||||
}
|
||||
|
51
internal/outpost/proxyv2/application/oauth_test.go
Normal file
51
internal/outpost/proxyv2/application/oauth_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"goauthentik.io/api"
|
||||
)
|
||||
|
||||
func TestCheckRedirectParam(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/start", nil)
|
||||
|
||||
rd, ok := a.checkRedirectParam(req)
|
||||
|
||||
assert.Equal(t, false, ok)
|
||||
assert.Equal(t, "", rd)
|
||||
|
||||
req, _ = http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://google.com", nil)
|
||||
|
||||
rd, ok = a.checkRedirectParam(req)
|
||||
|
||||
assert.Equal(t, false, ok)
|
||||
assert.Equal(t, "", rd)
|
||||
|
||||
req, _ = http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://ext.t.goauthentik.io/test", nil)
|
||||
|
||||
rd, ok = a.checkRedirectParam(req)
|
||||
|
||||
assert.Equal(t, true, ok)
|
||||
assert.Equal(t, "https://ext.t.goauthentik.io/test", rd)
|
||||
}
|
||||
|
||||
func TestCheckRedirectParam_Domain(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
a.proxyConfig.CookieDomain = api.PtrString("t.goauthentik.io")
|
||||
req, _ := http.NewRequest("GET", "https://a.t.goauthentik.io/outpost.goauthentik.io/auth/start", nil)
|
||||
|
||||
rd, ok := a.checkRedirectParam(req)
|
||||
|
||||
assert.Equal(t, false, ok)
|
||||
assert.Equal(t, "", rd)
|
||||
req, _ = http.NewRequest("GET", "/outpost.goauthentik.io/auth/start?rd=https://ext.t.goauthentik.io/test", nil)
|
||||
|
||||
rd, ok = a.checkRedirectParam(req)
|
||||
|
||||
assert.Equal(t, true, ok)
|
||||
assert.Equal(t, "https://ext.t.goauthentik.io/test", rd)
|
||||
}
|
@ -15,6 +15,7 @@ func newTestApplication() *Application {
|
||||
ClientId: api.PtrString(ak.TestSecret()),
|
||||
ClientSecret: api.PtrString(ak.TestSecret()),
|
||||
CookieSecret: api.PtrString(ak.TestSecret()),
|
||||
ExternalHost: "https://ext.t.goauthentik.io",
|
||||
CookieDomain: api.PtrString(""),
|
||||
Mode: api.PROXYMODE_FORWARD_SINGLE.Ptr(),
|
||||
SkipPathRegex: api.PtrString("/skip.*"),
|
||||
|
@ -42,7 +42,7 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
|
||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
||||
}
|
||||
|
||||
authUrl := urlJoin(a.proxyConfig.ExternalHost, "/akprox/start")
|
||||
authUrl := urlJoin(a.proxyConfig.ExternalHost, "/outpost.goauthentik.io/start")
|
||||
http.Redirect(rw, r, authUrl, http.StatusFound)
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ func TestRedirectToStart_Proxy(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String())
|
||||
assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start", loc.String())
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect])
|
||||
@ -38,7 +38,7 @@ func TestRedirectToStart_Forward(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String())
|
||||
assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start", loc.String())
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect])
|
||||
@ -56,7 +56,7 @@ func TestRedirectToStart_Forward_Domain_Invalid(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String())
|
||||
assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start", loc.String())
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect])
|
||||
@ -74,7 +74,7 @@ func TestRedirectToStart_Forward_Domain(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusFound, rr.Code)
|
||||
loc, _ := rr.Result().Location()
|
||||
assert.Equal(t, "https://test.goauthentik.io/akprox/start", loc.String())
|
||||
assert.Equal(t, "https://test.goauthentik.io/outpost.goauthentik.io/start", loc.String())
|
||||
|
||||
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||
assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect])
|
||||
|
@ -32,7 +32,7 @@ func (ps *ProxyServer) HandlePing(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (ps *ProxyServer) HandleStatic(rw http.ResponseWriter, r *http.Request) {
|
||||
before := time.Now()
|
||||
web.DisableIndex(http.StripPrefix("/akprox/static/dist", staticWeb.StaticHandler)).ServeHTTP(rw, r)
|
||||
web.DisableIndex(http.StripPrefix("/outpost.goauthentik.io/static/dist", staticWeb.StaticHandler)).ServeHTTP(rw, r)
|
||||
after := time.Since(before)
|
||||
metrics.Requests.With(prometheus.Labels{
|
||||
"outpost_name": ps.akAPI.Outpost.Name,
|
||||
@ -90,11 +90,11 @@ func (ps *ProxyServer) lookupApp(r *http.Request) (*application.Application, str
|
||||
}
|
||||
|
||||
func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/akprox/static") {
|
||||
if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io/static") {
|
||||
ps.HandleStatic(rw, r)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(r.URL.Path, "/akprox/ping") {
|
||||
if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io/ping") {
|
||||
ps.HandlePing(rw, r)
|
||||
return
|
||||
}
|
||||
@ -109,6 +109,7 @@ func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
ps.log.WithField("headers", r.Header).Trace("tracing headers for no hostname match")
|
||||
ps.log.WithField("host", host).Warning("no app for hostname")
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
@ -25,7 +25,7 @@ var (
|
||||
func RunServer() {
|
||||
m := mux.NewRouter()
|
||||
l := log.WithField("logger", "authentik.outpost.metrics")
|
||||
m.HandleFunc("/akprox/ping", func(rw http.ResponseWriter, r *http.Request) {
|
||||
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(204)
|
||||
})
|
||||
m.Path("/metrics").Handler(promhttp.Handler())
|
||||
|
@ -64,8 +64,8 @@ func NewProxyServer(ac *ak.APIController, portOffset int) *ProxyServer {
|
||||
akAPI: ac,
|
||||
defaultCert: defaultCert,
|
||||
}
|
||||
globalMux.PathPrefix("/akprox/static").HandlerFunc(s.HandleStatic)
|
||||
globalMux.Path("/akprox/ping").HandlerFunc(s.HandlePing)
|
||||
globalMux.PathPrefix("/outpost.goauthentik.io/static").HandlerFunc(s.HandleStatic)
|
||||
globalMux.Path("/outpost.goauthentik.io/ping").HandlerFunc(s.HandlePing)
|
||||
rootMux.PathPrefix("/").HandlerFunc(s.Handle)
|
||||
return s
|
||||
}
|
||||
|
@ -5,12 +5,13 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>{{.Title}}</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/akprox/static/dist/assets/icons/icon.png">
|
||||
<link rel="stylesheet" type="text/css" href="/akprox/static/dist/patternfly.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/akprox/static/dist/authentik.css">
|
||||
<link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png">
|
||||
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/patternfly.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/authentik.css">
|
||||
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/custom.css">
|
||||
<style>
|
||||
.pf-c-background-image::before {
|
||||
--ak-flow-background: url("/akprox/static/dist/assets/images/flow_background.jpg");
|
||||
--ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg");
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@ -32,7 +33,7 @@
|
||||
<div class="ak-login-container">
|
||||
<header class="pf-c-login__header">
|
||||
<div class="pf-c-brand ak-brand">
|
||||
<img src="/akprox/static/dist/assets/icons/icon_left_brand.svg" alt="authentik icon" />
|
||||
<img src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik icon" />
|
||||
</div>
|
||||
</header>
|
||||
<main class="pf-c-login__main">
|
||||
|
@ -3,7 +3,8 @@ package templates
|
||||
import (
|
||||
_ "embed"
|
||||
"html/template"
|
||||
"log"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
//go:embed error.html
|
||||
|
@ -30,7 +30,7 @@ func (ws *WebServer) configureProxy() {
|
||||
rp := &httputil.ReverseProxy{Director: director}
|
||||
rp.ErrorHandler = ws.proxyErrorHandler
|
||||
rp.ModifyResponse = ws.proxyModifyResponse
|
||||
ws.m.PathPrefix("/akprox").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
ws.m.PathPrefix("/outpost.goauthentik.io").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if ws.ProxyServer != nil {
|
||||
before := time.Now()
|
||||
ws.ProxyServer.Handle(rw, r)
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Stage 1: Build
|
||||
FROM docker.io/golang:1.17.6-bullseye AS builder
|
||||
FROM docker.io/golang:1.17.7-bullseye AS builder
|
||||
|
||||
WORKDIR /go/src/goauthentik.io
|
||||
|
||||
@ -19,7 +19,7 @@ ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
||||
|
||||
COPY --from=builder /go/ldap /
|
||||
|
||||
HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/akprox/ping" ]
|
||||
HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/outpost.goauthentik.io/ping" ]
|
||||
|
||||
EXPOSE 3389 6636 9300
|
||||
|
||||
|
30
lifecycle/ak
30
lifecycle/ak
@ -32,30 +32,6 @@ function check_if_root {
|
||||
chpst -u authentik:$GROUP env HOME=/authentik $1
|
||||
}
|
||||
|
||||
function prefixwith {
|
||||
local prefix="$1"
|
||||
shift
|
||||
"$@" > >(sed "s/^/$prefix: /") 2> >(sed "s/^/$prefix (err): /" >&2)
|
||||
}
|
||||
|
||||
function restore {
|
||||
PG_HOST=$(python -m authentik.lib.config postgresql.host 2> /dev/null)
|
||||
PG_NAME=$(python -m authentik.lib.config postgresql.name 2> /dev/null)
|
||||
PG_USER=$(python -m authentik.lib.config postgresql.user 2> /dev/null)
|
||||
PG_PORT=$(python -m authentik.lib.config postgresql.port 2> /dev/null)
|
||||
export PGPASSWORD=$(python -m authentik.lib.config postgresql.password 2> /dev/null)
|
||||
log "Ensuring no one can connect to the database"
|
||||
prefixwith "psql" psql -h"${PG_HOST}" -U"${PG_USER}" -c"UPDATE pg_database SET datallowconn = 'false' WHERE datname = '${PG_NAME}';" "postgres"
|
||||
prefixwith "psql" psql -h"${PG_HOST}" -U"${PG_USER}" -c"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${PG_NAME}';" "postgres"
|
||||
log "deleting and re-creating database"
|
||||
prefixwith "psql" dropdb -h"${PG_HOST}" -U"${PG_USER}" "${PG_NAME}" || trueacku
|
||||
prefixwith "psql" createdb -h"${PG_HOST}" -U"${PG_USER}" "${PG_NAME}"
|
||||
log "running initial migrations"
|
||||
prefixwith "migrate" python -m lifecycle.migrate 2> /dev/null
|
||||
log "restoring database"
|
||||
prefixwith "restore" python -m manage dbrestore -i ${@:2}
|
||||
}
|
||||
|
||||
MODE_FILE="/tmp/authentik-mode"
|
||||
|
||||
if [[ "$1" == "server" ]]; then
|
||||
@ -75,12 +51,6 @@ elif [[ "$1" == "worker" ]]; then
|
||||
elif [[ "$1" == "flower" ]]; then
|
||||
echo "flower" > $MODE_FILE
|
||||
celery -A authentik.root.celery flower
|
||||
elif [[ "$1" == "backup" ]]; then
|
||||
wait_for_db
|
||||
python -m manage dbbackup --clean
|
||||
elif [[ "$1" == "restore" ]]; then
|
||||
wait_for_db
|
||||
restore $@
|
||||
elif [[ "$1" == "bash" ]]; then
|
||||
/bin/bash
|
||||
elif [[ "$1" == "test" ]]; then
|
||||
|
BIN
locale/zh-Hans/LC_MESSAGES/django.mo
Normal file
BIN
locale/zh-Hans/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
1749
locale/zh-Hans/LC_MESSAGES/django.po
Normal file
1749
locale/zh-Hans/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
locale/zh-Hant/LC_MESSAGES/django.mo
Normal file
BIN
locale/zh-Hant/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
1749
locale/zh-Hant/LC_MESSAGES/django.po
Normal file
1749
locale/zh-Hant/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
locale/zh_TW/LC_MESSAGES/django.mo
Normal file
BIN
locale/zh_TW/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
1749
locale/zh_TW/LC_MESSAGES/django.po
Normal file
1749
locale/zh_TW/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -22,13 +22,6 @@ warnings.filterwarnings(
|
||||
"efault_app_config."
|
||||
),
|
||||
)
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
message=(
|
||||
"'dbbackup' defines default_app_config = 'dbbackup.apps.DbbackupConfig'. Django now det"
|
||||
"ects this configuration automatically. You can remove default_app_config."
|
||||
),
|
||||
)
|
||||
|
||||
defuse_stdlib()
|
||||
|
||||
|
631
poetry.lock
generated
631
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,7 @@ ENV NODE_ENV=production
|
||||
RUN cd /static && npm i && npm run build-proxy
|
||||
|
||||
# Stage 2: Build
|
||||
FROM docker.io/golang:1.17.6-bullseye AS builder
|
||||
FROM docker.io/golang:1.17.7-bullseye AS builder
|
||||
|
||||
WORKDIR /go/src/goauthentik.io
|
||||
|
||||
@ -32,7 +32,7 @@ COPY --from=web-builder /static/security.txt /web/security.txt
|
||||
COPY --from=web-builder /static/dist/ /web/dist/
|
||||
COPY --from=web-builder /static/authentik/ /web/authentik/
|
||||
|
||||
HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/akprox/ping" ]
|
||||
HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/outpost.goauthentik.io/ping" ]
|
||||
|
||||
EXPOSE 9000 9300 9443
|
||||
|
||||
|
@ -92,12 +92,11 @@ addopts = "-p no:celery --junitxml=unittest.xml"
|
||||
|
||||
[tool.poetry]
|
||||
name = "authentik"
|
||||
version = "2022.1.5"
|
||||
version = "2022.2.1"
|
||||
description = ""
|
||||
authors = ["Jens Langhammer <jens.langhammer@beryju.org>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
boto3 = "*"
|
||||
celery = "*"
|
||||
channels = "*"
|
||||
channels-redis = "*"
|
||||
@ -107,14 +106,12 @@ dacite = "*"
|
||||
deepmerge = "*"
|
||||
defusedxml = "*"
|
||||
django = "*"
|
||||
django-dbbackup = "=4.0.0b0"
|
||||
django-filter = "*"
|
||||
django-guardian = "*"
|
||||
django-model-utils = "*"
|
||||
django-otp = "*"
|
||||
django-prometheus = "*"
|
||||
django-redis = "*"
|
||||
django-storages = "*"
|
||||
djangorestframework = "*"
|
||||
djangorestframework-guardian = "*"
|
||||
docker = "*"
|
||||
|
72
schema.yml
72
schema.yml
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2022.1.5
|
||||
version: 2022.2.1
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@beryju.org
|
||||
@ -15045,10 +15045,14 @@ paths:
|
||||
description: AuthenticatorValidateStage Viewset
|
||||
parameters:
|
||||
- in: query
|
||||
name: configuration_stage
|
||||
name: configuration_stages
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
explode: true
|
||||
style: form
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
@ -19826,11 +19830,12 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/DeviceClassesEnum'
|
||||
description: Device classes which can be used to authenticate
|
||||
configuration_stage:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Stage used to configure Authenticator when user doesn't have
|
||||
configuration_stages:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Stages used to configure Authenticator when user doesn't have
|
||||
any compatible devices. After this configuration Stage passes, the user
|
||||
is not prompted again.
|
||||
required:
|
||||
@ -19858,11 +19863,12 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/DeviceClassesEnum'
|
||||
description: Device classes which can be used to authenticate
|
||||
configuration_stage:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Stage used to configure Authenticator when user doesn't have
|
||||
configuration_stages:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Stages used to configure Authenticator when user doesn't have
|
||||
any compatible devices. After this configuration Stage passes, the user
|
||||
is not prompted again.
|
||||
required:
|
||||
@ -19892,7 +19898,12 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DeviceChallenge'
|
||||
configuration_stages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SelectableStage'
|
||||
required:
|
||||
- configuration_stages
|
||||
- device_challenges
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
@ -19907,6 +19918,9 @@ components:
|
||||
default: ak-stage-authenticator-validate
|
||||
selected_challenge:
|
||||
$ref: '#/components/schemas/DeviceChallengeRequest'
|
||||
selected_stage:
|
||||
type: string
|
||||
minLength: 1
|
||||
code:
|
||||
type: string
|
||||
minLength: 1
|
||||
@ -20017,7 +20031,6 @@ components:
|
||||
enum:
|
||||
- can_save_media
|
||||
- can_geo_ip
|
||||
- can_backup
|
||||
type: string
|
||||
CaptchaChallenge:
|
||||
type: object
|
||||
@ -26678,11 +26691,12 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/DeviceClassesEnum'
|
||||
description: Device classes which can be used to authenticate
|
||||
configuration_stage:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Stage used to configure Authenticator when user doesn't have
|
||||
configuration_stages:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Stages used to configure Authenticator when user doesn't have
|
||||
any compatible devices. After this configuration Stage passes, the user
|
||||
is not prompted again.
|
||||
PatchedCaptchaStageRequest:
|
||||
@ -30018,6 +30032,24 @@ components:
|
||||
- direct
|
||||
- cached
|
||||
type: string
|
||||
SelectableStage:
|
||||
type: object
|
||||
description: Serializer for stages which can be selected by users
|
||||
properties:
|
||||
pk:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
verbose_name:
|
||||
type: string
|
||||
meta_model_name:
|
||||
type: string
|
||||
required:
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
- verbose_name
|
||||
ServiceConnection:
|
||||
type: object
|
||||
description: ServiceConnection Serializer
|
||||
|
@ -105,7 +105,7 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
self.assertIn(f"X-Authentik-Username: {self.user.username}", full_body_text)
|
||||
self.assertIn("X-Foo: bar", full_body_text)
|
||||
|
||||
self.driver.get("http://localhost:9000/akprox/sign_out")
|
||||
self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out")
|
||||
sleep(2)
|
||||
full_body_text = self.driver.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text
|
||||
self.assertIn("You've logged out of proxy.", full_body_text)
|
||||
|
873
web/package-lock.json
generated
873
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -22,7 +22,11 @@
|
||||
"fr_FR",
|
||||
"tr",
|
||||
"es",
|
||||
"pl"
|
||||
"pl",
|
||||
"zh_TW",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
"de"
|
||||
],
|
||||
"formatOptions": {
|
||||
"lineNumbers": false
|
||||
@ -48,14 +52,14 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.16.12",
|
||||
"@babel/plugin-proposal-decorators": "^7.16.7",
|
||||
"@babel/plugin-transform-runtime": "^7.16.10",
|
||||
"@babel/core": "^7.17.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.17.2",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@formatjs/intl-listformat": "^6.5.1",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@goauthentik/api": "^2022.1.3-1643236150",
|
||||
"@formatjs/intl-listformat": "^6.5.2",
|
||||
"@fortawesome/fontawesome-free": "^6.0.0",
|
||||
"@goauthentik/api": "^2022.1.5-1644681372",
|
||||
"@jackfranklin/rollup-plugin-markdown": "^0.3.0",
|
||||
"@lingui/cli": "^3.13.2",
|
||||
"@lingui/core": "^3.13.2",
|
||||
@ -67,36 +71,36 @@
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
"@rollup/plugin-replace": "^3.0.1",
|
||||
"@rollup/plugin-replace": "^3.1.0",
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@sentry/browser": "^6.17.3",
|
||||
"@sentry/tracing": "^6.17.3",
|
||||
"@sentry/browser": "^6.17.8",
|
||||
"@sentry/tracing": "^6.17.8",
|
||||
"@squoosh/cli": "^0.7.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^3.1.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^3.2.0",
|
||||
"@types/chart.js": "^2.9.35",
|
||||
"@types/codemirror": "5.60.5",
|
||||
"@types/grecaptcha": "^3.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"@typescript-eslint/parser": "^5.10.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
||||
"@typescript-eslint/parser": "^5.12.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.6.0",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"chart.js": "^3.7.0",
|
||||
"chart.js": "^3.7.1",
|
||||
"chartjs-adapter-moment": "^1.0.0",
|
||||
"codemirror": "^5.65.1",
|
||||
"construct-style-sheets-polyfill": "^3.1.0",
|
||||
"country-flag-icons": "^1.4.20",
|
||||
"eslint": "^8.8.0",
|
||||
"country-flag-icons": "^1.4.21",
|
||||
"eslint": "^8.9.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-custom-elements": "0.0.4",
|
||||
"eslint-plugin-lit": "^1.6.1",
|
||||
"flowchart.js": "^1.17.0",
|
||||
"flowchart.js": "^1.17.1",
|
||||
"fuse.js": "^6.5.3",
|
||||
"lit": "^2.1.2",
|
||||
"lit": "^2.1.4",
|
||||
"moment": "^2.29.1",
|
||||
"prettier": "^2.5.1",
|
||||
"rapidoc": "^9.1.4",
|
||||
"rollup": "^2.66.1",
|
||||
"rollup": "^2.67.2",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-cssimport": "^1.0.2",
|
||||
"rollup-plugin-minify-html-literals": "^1.2.6",
|
||||
|
@ -34,6 +34,7 @@ export const resources = [
|
||||
dest: "dist/",
|
||||
},
|
||||
{ src: "src/authentik.css", dest: "dist/" },
|
||||
{ src: "src/custom.css", dest: "dist/" },
|
||||
|
||||
{
|
||||
src: "node_modules/@patternfly/patternfly/assets/*",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 704 KiB After Width: | Height: | Size: 580 KiB |
@ -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.1.5";
|
||||
export const VERSION = "2022.2.1";
|
||||
export const TITLE_DEFAULT = "authentik";
|
||||
export const ROUTE_SEPARATOR = ";";
|
||||
|
||||
|
1
web/src/custom.css
Normal file
1
web/src/custom.css
Normal file
@ -0,0 +1 @@
|
||||
/* User customisable */
|
@ -433,8 +433,7 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||
)
|
||||
? html`
|
||||
<li>
|
||||
<a
|
||||
href="https://unsplash.com/@kimonmaritz"
|
||||
<a href="https://unsplash.com/@trime"
|
||||
>${t`Background image`}</a
|
||||
>
|
||||
</li>
|
||||
|
@ -67,7 +67,7 @@ export class AuthenticatorValidateStage
|
||||
return this._selectedDeviceChallenge;
|
||||
}
|
||||
|
||||
submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<void> {
|
||||
submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<boolean> {
|
||||
return this.host?.submit(payload) || Promise.resolve();
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ export class AuthenticatorValidateStage
|
||||
}
|
||||
|
||||
renderDevicePicker(): TemplateResult {
|
||||
return html` <ul>
|
||||
return html`<ul>
|
||||
${this.challenge?.deviceChallenges.map((challenges) => {
|
||||
return html`<li>
|
||||
<button
|
||||
@ -157,6 +157,30 @@ export class AuthenticatorValidateStage
|
||||
</ul>`;
|
||||
}
|
||||
|
||||
renderStagePicker(): TemplateResult {
|
||||
return html`<ul>
|
||||
${this.challenge?.configurationStages.map((stage) => {
|
||||
return html`<li>
|
||||
<button
|
||||
class="pf-c-button authenticator-button"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.submit({
|
||||
component: this.challenge.component || "",
|
||||
selectedStage: stage.pk,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div class="right">
|
||||
<p>${stage.name}</p>
|
||||
<small>${stage.verboseName}</small>
|
||||
</div>
|
||||
</button>
|
||||
</li>`;
|
||||
})}
|
||||
</ul>`;
|
||||
}
|
||||
|
||||
renderDeviceChallenge(): TemplateResult {
|
||||
if (!this.selectedDeviceChallenge) {
|
||||
return html``;
|
||||
@ -242,6 +266,9 @@ export class AuthenticatorValidateStage
|
||||
${this.selectedDeviceChallenge
|
||||
? ""
|
||||
: html`<p>${t`Select an authentication method.`}</p>`}
|
||||
${this.challenge.configurationStages.length > 0
|
||||
? this.renderStagePicker()
|
||||
: html``}
|
||||
</form>
|
||||
${this.renderDevicePicker()}
|
||||
</div>
|
||||
|
@ -1,15 +1,19 @@
|
||||
import { en, es, fr, pl, tr } from "make-plural/plurals";
|
||||
import { de, en, es, fr, pl, tr, zh } from "make-plural/plurals";
|
||||
|
||||
import { Messages, i18n } from "@lingui/core";
|
||||
import { detect, fromNavigator, fromStorage, fromUrl } from "@lingui/detect-locale";
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { messages as localeDE } from "../locales/de";
|
||||
import { messages as localeEN } from "../locales/en";
|
||||
import { messages as localeES } from "../locales/es";
|
||||
import { messages as localeFR_FR } from "../locales/fr_FR";
|
||||
import { messages as localePL } from "../locales/pl";
|
||||
import { messages as localeDEBUG } from "../locales/pseudo-LOCALE";
|
||||
import { messages as localeTR } from "../locales/tr";
|
||||
import { messages as localeZH_Hans } from "../locales/zh-Hans";
|
||||
import { messages as localeZH_Hant } from "../locales/zh-Hant";
|
||||
import { messages as localeZH_TW } from "../locales/zh_TW";
|
||||
|
||||
export const LOCALES: {
|
||||
code: string;
|
||||
@ -54,6 +58,30 @@ export const LOCALES: {
|
||||
label: t`Polish`,
|
||||
locale: localePL,
|
||||
},
|
||||
{
|
||||
code: "zh_TW",
|
||||
plurals: zh,
|
||||
label: t`Taiwanese Mandarin`,
|
||||
locale: localeZH_TW,
|
||||
},
|
||||
{
|
||||
code: "zh-Hans",
|
||||
plurals: zh,
|
||||
label: t`Chinese (simplified)`,
|
||||
locale: localeZH_Hans,
|
||||
},
|
||||
{
|
||||
code: "zh-Hant",
|
||||
plurals: zh,
|
||||
label: t`Chinese (traditional)`,
|
||||
locale: localeZH_Hant,
|
||||
},
|
||||
{
|
||||
code: "de",
|
||||
plurals: de,
|
||||
label: t`German`,
|
||||
locale: localeDE,
|
||||
},
|
||||
];
|
||||
|
||||
LOCALES.forEach((locale) => {
|
||||
|
6125
web/src/locales/de.po
Normal file
6125
web/src/locales/de.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -563,16 +563,16 @@ msgid "Background shown during execution."
|
||||
msgstr "Background shown during execution."
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with errors."
|
||||
msgstr "Backup finished with errors."
|
||||
#~ msgid "Backup finished with errors."
|
||||
#~ msgstr "Backup finished with errors."
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with warnings/backup not supported."
|
||||
msgstr "Backup finished with warnings/backup not supported."
|
||||
#~ msgid "Backup finished with warnings/backup not supported."
|
||||
#~ msgstr "Backup finished with warnings/backup not supported."
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Backup status"
|
||||
msgstr "Backup status"
|
||||
#~ msgid "Backup status"
|
||||
#~ msgstr "Backup status"
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||
#: src/pages/providers/ldap/LDAPProviderViewPage.ts
|
||||
@ -844,6 +844,14 @@ msgstr "Checks if the request's user's password has been changed in the last x d
|
||||
msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||
msgstr "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (simplified)"
|
||||
msgstr "Chinese (simplified)"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (traditional)"
|
||||
msgstr "Chinese (traditional)"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts
|
||||
msgid "Clear Flow cache"
|
||||
msgstr "Clear Flow cache"
|
||||
@ -948,8 +956,12 @@ msgid "Configuration flow"
|
||||
msgstr "Configuration flow"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stage"
|
||||
msgstr "Configuration stage"
|
||||
#~ msgid "Configuration stage"
|
||||
#~ msgstr "Configuration stage"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stages"
|
||||
msgstr "Configuration stages"
|
||||
|
||||
#:
|
||||
#~ msgid "Configure WebAuthn"
|
||||
@ -2205,6 +2217,10 @@ msgstr "Generic"
|
||||
msgid "Generic OpenID Connect"
|
||||
msgstr "Generic OpenID Connect"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "German"
|
||||
msgstr "German"
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "Get this value from https://console.twilio.com"
|
||||
@ -4483,8 +4499,8 @@ msgid "Stage type"
|
||||
msgstr "Stage type"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
#~ msgstr "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
|
||||
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
||||
@ -4543,6 +4559,10 @@ msgstr "Stages"
|
||||
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
|
||||
msgstr "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||
msgid "State"
|
||||
msgstr "State"
|
||||
@ -5014,6 +5034,10 @@ msgstr "TOTP Device"
|
||||
msgid "TOTP authenticator"
|
||||
msgstr "TOTP authenticator"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Taiwanese Mandarin"
|
||||
msgstr "Taiwanese Mandarin"
|
||||
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
msgid "Target"
|
||||
msgstr "Target"
|
||||
@ -5673,8 +5697,8 @@ msgid "Use the username and password below to authenticate. The password can be
|
||||
msgstr "Use the username and password below to authenticate. The password can be retrieved later on the Tokens page."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application."
|
||||
@ -6036,6 +6060,10 @@ msgstr "When enabled, the invitation will be deleted after usage."
|
||||
msgid "When enabled, user fields are matched regardless of their casing."
|
||||
msgstr "When enabled, user fields are matched regardless of their casing."
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
|
||||
msgstr "When multiple stages are selected, the user can choose which one they want to enroll."
|
||||
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||
msgstr "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||
|
@ -561,16 +561,16 @@ msgid "Background shown during execution."
|
||||
msgstr "Se muestra el fondo durante la ejecución."
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with errors."
|
||||
msgstr "La copia de seguridad terminó con errores."
|
||||
#~ msgid "Backup finished with errors."
|
||||
#~ msgstr "La copia de seguridad terminó con errores."
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with warnings/backup not supported."
|
||||
msgstr "La copia de seguridad finalizó con advertencias o copias de seguridad no compatibles"
|
||||
#~ msgid "Backup finished with warnings/backup not supported."
|
||||
#~ msgstr "La copia de seguridad finalizó con advertencias o copias de seguridad no compatibles"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Backup status"
|
||||
msgstr "Estado de respaldo"
|
||||
#~ msgid "Backup status"
|
||||
#~ msgstr "Estado de respaldo"
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||
#: src/pages/providers/ldap/LDAPProviderViewPage.ts
|
||||
@ -838,6 +838,14 @@ msgstr "Comprueba si la contraseña del usuario de la solicitud se ha cambiado e
|
||||
msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||
msgstr "Comprueba el valor de la solicitud de política en relación con varias reglas, que se utilizan principalmente para garantizar la seguridad de la contraseña."
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (simplified)"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (traditional)"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts
|
||||
msgid "Clear Flow cache"
|
||||
msgstr "Borrar caché de flujo"
|
||||
@ -942,8 +950,12 @@ msgid "Configuration flow"
|
||||
msgstr "Flujo de configuración"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stage"
|
||||
msgstr "Etapa de configuración"
|
||||
#~ msgid "Configuration stage"
|
||||
#~ msgstr "Etapa de configuración"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stages"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Configure WebAuthn"
|
||||
#~ msgstr "Configurar WebAuthn"
|
||||
@ -2166,6 +2178,10 @@ msgstr "Genérico"
|
||||
msgid "Generic OpenID Connect"
|
||||
msgstr "Conexión OpenID genérica"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "Get this value from https://console.twilio.com"
|
||||
@ -4391,8 +4407,8 @@ msgid "Stage type"
|
||||
msgstr "Tipo de escenario"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr "Etapa utilizada para configurar Authenticator cuando el usuario no tiene ningún dispositivo compatible. Una vez superada esta etapa de configuración, no se volverá a preguntar al usuario."
|
||||
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
#~ msgstr "Etapa utilizada para configurar Authenticator cuando el usuario no tiene ningún dispositivo compatible. Una vez superada esta etapa de configuración, no se volverá a preguntar al usuario."
|
||||
|
||||
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
||||
@ -4451,6 +4467,10 @@ msgstr "Etapas"
|
||||
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
|
||||
msgstr "Las etapas son pasos individuales de un flujo por los que se guía al usuario. Una etapa solo se puede ejecutar desde dentro de un flujo."
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||
msgid "State"
|
||||
msgstr "Estado"
|
||||
@ -4905,6 +4925,10 @@ msgstr "Dispositivo TOTP"
|
||||
msgid "TOTP authenticator"
|
||||
msgstr "Autenticador TOTP"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Taiwanese Mandarin"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
msgid "Target"
|
||||
msgstr "Objetivo"
|
||||
@ -5556,8 +5580,8 @@ msgid "Use the username and password below to authenticate. The password can be
|
||||
msgstr "Use el nombre de usuario y la contraseña a continuación para autenticarse. La contraseña se puede recuperar más adelante en la página Tokens."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr "Use este proveedor con auth_request de nginx o ForwardAuth de traefik. Cada aplicación/dominio necesita su propio proveedor. Además, en cada dominio, /akprox debe enrutarse al puesto avanzado (cuando se usa un puesto avanzado administrado, esto se hace por usted)."
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr "Use este proveedor con auth_request de nginx o ForwardAuth de traefik. Cada aplicación/dominio necesita su propio proveedor. Además, en cada dominio, /outpost.goauthentik.io debe enrutarse al puesto avanzado (cuando se usa un puesto avanzado administrado, esto se hace por usted)."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application."
|
||||
@ -5912,6 +5936,10 @@ msgstr "Cuando se habilita, la invitación se eliminará después de su uso."
|
||||
msgid "When enabled, user fields are matched regardless of their casing."
|
||||
msgstr "Cuando se habilita, los campos de usuario coinciden independientemente de su carcasa."
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||
msgstr "Cuando se selecciona, se muestra un campo de contraseña en la misma página en lugar de en una página separada. Esto evita ataques de enumeración de nombres de usuario."
|
||||
|
@ -567,16 +567,16 @@ msgid "Background shown during execution."
|
||||
msgstr "Arrière-plan utilisé durant l'exécution."
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with errors."
|
||||
msgstr "Sauvegarde terminée avec des erreurs."
|
||||
#~ msgid "Backup finished with errors."
|
||||
#~ msgstr "Sauvegarde terminée avec des erreurs."
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with warnings/backup not supported."
|
||||
msgstr "Sauvegarde terminée avec avertissements/sauvegarde non supportée."
|
||||
#~ msgid "Backup finished with warnings/backup not supported."
|
||||
#~ msgstr "Sauvegarde terminée avec avertissements/sauvegarde non supportée."
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Backup status"
|
||||
msgstr "État de la sauvegarde"
|
||||
#~ msgid "Backup status"
|
||||
#~ msgstr "État de la sauvegarde"
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||
#: src/pages/providers/ldap/LDAPProviderViewPage.ts
|
||||
@ -846,6 +846,14 @@ msgstr "Vérifie si le mot de passe de l'usager a été changé dans les X derni
|
||||
msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||
msgstr "Vérifie la valeur de la requête via plusieurs règles, principalement utilisé pour s'assurer de la robustesse des mots de passe."
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (simplified)"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (traditional)"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts
|
||||
msgid "Clear Flow cache"
|
||||
msgstr "Vider le cache des flux"
|
||||
@ -950,8 +958,12 @@ msgid "Configuration flow"
|
||||
msgstr "Flux de configuration"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stage"
|
||||
msgstr "Étape de configuration"
|
||||
#~ msgid "Configuration stage"
|
||||
#~ msgstr "Étape de configuration"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stages"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#~ msgid "Configure WebAuthn"
|
||||
@ -2191,6 +2203,10 @@ msgstr ""
|
||||
msgid "Generic OpenID Connect"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "Get this value from https://console.twilio.com"
|
||||
@ -4444,8 +4460,8 @@ msgid "Stage type"
|
||||
msgstr "Type d'étape"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr "Étape utilisée pour configurer l'Authenticator lorsqu'un utilisateur n'a pas d'appareil compatible. Une fois cette étape franchie, l'utilisateur ne sera plus sollicité."
|
||||
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
#~ msgstr "Étape utilisée pour configurer l'Authenticator lorsqu'un utilisateur n'a pas d'appareil compatible. Une fois cette étape franchie, l'utilisateur ne sera plus sollicité."
|
||||
|
||||
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
||||
@ -4504,6 +4520,10 @@ msgstr "Étapes"
|
||||
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
|
||||
msgstr "Les étapes sont des étapes simples d'un flux au travers duquel un utilisateur est guidé. Une étape peut être uniquement exécutée à l'intérieur d'un flux."
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||
msgid "State"
|
||||
msgstr "État"
|
||||
@ -4969,6 +4989,10 @@ msgstr ""
|
||||
msgid "TOTP authenticator"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Taiwanese Mandarin"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
msgid "Target"
|
||||
msgstr "Cible"
|
||||
@ -5614,8 +5638,8 @@ msgid "Use the username and password below to authenticate. The password can be
|
||||
msgstr "Utilisez le nom d'utilisateur et le mot de passe ci-dessous pour vous authentifier. Le mot de passe peut être récupéré plus tard sur la page Jetons."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr "Utilisez ce fournisseur avec auth_request de nginx ou forwardAuth de traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, /akprox doit être routé vers l'avant-poste (si vous utilisez un avant-poste géré, cela est fait pour vous)."
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr "Utilisez ce fournisseur avec auth_request de nginx ou forwardAuth de traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, /outpost.goauthentik.io doit être routé vers l'avant-poste (si vous utilisez un avant-poste géré, cela est fait pour vous)."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application."
|
||||
@ -5975,6 +5999,10 @@ msgstr "Si activée, l'invitation sera supprimée après utilisation."
|
||||
msgid "When enabled, user fields are matched regardless of their casing."
|
||||
msgstr "Si activé, les champs de l'utilisateur sont mis en correspondance en ignorant leur casse."
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||
msgstr "Si activée, un champ de mot de passe est affiché sur la même page au lieu d'une page séparée. Cela permet d'éviter les attaques par énumération de noms d'utilisateur."
|
||||
|
@ -560,17 +560,14 @@ msgstr "Obraz tła"
|
||||
msgid "Background shown during execution."
|
||||
msgstr "Tło pokazywane podczas wykonywania."
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with errors."
|
||||
msgstr "Kopia zapasowa zakończona z błędami."
|
||||
#~ msgid "Backup finished with errors."
|
||||
#~ msgstr "Kopia zapasowa zakończona z błędami."
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with warnings/backup not supported."
|
||||
msgstr "Tworzenie kopii zapasowej zakończone z ostrzeżeniami/kopia zapasowa nie jest obsługiwana."
|
||||
#~ msgid "Backup finished with warnings/backup not supported."
|
||||
#~ msgstr "Tworzenie kopii zapasowej zakończone z ostrzeżeniami/kopia zapasowa nie jest obsługiwana."
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Backup status"
|
||||
msgstr "Stan kopii zapasowej"
|
||||
#~ msgid "Backup status"
|
||||
#~ msgstr "Stan kopii zapasowej"
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||
#: src/pages/providers/ldap/LDAPProviderViewPage.ts
|
||||
@ -838,6 +835,14 @@ msgstr "Sprawdza, czy żądanego użytkownika hasło zostało zmienione w ciągu
|
||||
msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||
msgstr "Sprawdza wartość z żądania zasad pod kątem kilku reguł, używanych głównie w celu zapewnienia siły hasła."
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (simplified)"
|
||||
msgstr "Chiński (uproszczony)"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (traditional)"
|
||||
msgstr "Chiński (tradycyjny)"
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts
|
||||
msgid "Clear Flow cache"
|
||||
msgstr "Wyczyść pamięć podręczną przepływu"
|
||||
@ -942,8 +947,12 @@ msgid "Configuration flow"
|
||||
msgstr "Przepływ konfiguracji"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stage"
|
||||
msgstr "Etap konfiguracji"
|
||||
#~ msgid "Configuration stage"
|
||||
#~ msgstr "Etap konfiguracji"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stages"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Configure WebAuthn"
|
||||
#~ msgstr "Skonfiguruj WebAuthn"
|
||||
@ -2166,6 +2175,10 @@ msgstr "Ogólny"
|
||||
msgid "Generic OpenID Connect"
|
||||
msgstr "Ogólny OpenID Connect"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "Get this value from https://console.twilio.com"
|
||||
@ -4391,8 +4404,8 @@ msgid "Stage type"
|
||||
msgstr "Typ etapu"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr "Etap używany do konfiguracji uwierzytelniacza, gdy użytkownik nie ma żadnych kompatybilnych urządzeń. Po zakończeniu tego etapu konfiguracji użytkownik nie jest ponownie pytany."
|
||||
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
#~ msgstr "Etap używany do konfiguracji uwierzytelniacza, gdy użytkownik nie ma żadnych kompatybilnych urządzeń. Po zakończeniu tego etapu konfiguracji użytkownik nie jest ponownie pytany."
|
||||
|
||||
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
||||
@ -4451,6 +4464,10 @@ msgstr "Etapy"
|
||||
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
|
||||
msgstr "Etapy to pojedyncze kroki przepływu, przez które prowadzony jest użytkownik. Etap można wykonać tylko z przepływu."
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||
msgid "State"
|
||||
msgstr "Stan"
|
||||
@ -4905,6 +4922,10 @@ msgstr "Urządzenie TOTP"
|
||||
msgid "TOTP authenticator"
|
||||
msgstr "Uwierzytelniacz TOTP"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Taiwanese Mandarin"
|
||||
msgstr "Tajwański mandaryński"
|
||||
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
msgid "Target"
|
||||
msgstr "Cel"
|
||||
@ -5556,8 +5577,8 @@ msgid "Use the username and password below to authenticate. The password can be
|
||||
msgstr "Użyj poniższej nazwy użytkownika i hasła do uwierzytelnienia. Hasło można później odzyskać na stronie Tokeny."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr "Użyj tego dostawcy z auth_request nginx lub forwardAuth traefik. Każda aplikacja/domena potrzebuje własnego dostawcy. Dodatkowo w każdej domenie /akprox musi być przekierowany do placówki (w przypadku korzystania z zarządzanej placówki jest to zrobione za Ciebie)."
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr "Użyj tego dostawcy z auth_request nginx lub forwardAuth traefik. Każda aplikacja/domena potrzebuje własnego dostawcy. Dodatkowo w każdej domenie /outpost.goauthentik.io musi być przekierowany do placówki (w przypadku korzystania z zarządzanej placówki jest to zrobione za Ciebie)."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application."
|
||||
@ -5912,6 +5933,10 @@ msgstr "Po włączeniu zaproszenie zostanie usunięte po użyciu."
|
||||
msgid "When enabled, user fields are matched regardless of their casing."
|
||||
msgstr "Po włączeniu pola użytkownika są dopasowywane niezależnie od wielkości liter."
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||
msgstr "Po wybraniu pole hasła jest wyświetlane na tej samej stronie zamiast na osobnej stronie. Zapobiega to atakom polegającym na wyliczaniu nazw użytkowników."
|
||||
|
6648
web/src/locales/pl_PL.po
Normal file
6648
web/src/locales/pl_PL.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -559,16 +559,16 @@ msgid "Background shown during execution."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with errors."
|
||||
msgstr ""
|
||||
#~ msgid "Backup finished with errors."
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with warnings/backup not supported."
|
||||
msgstr ""
|
||||
#~ msgid "Backup finished with warnings/backup not supported."
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Backup status"
|
||||
msgstr ""
|
||||
#~ msgid "Backup status"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||
#: src/pages/providers/ldap/LDAPProviderViewPage.ts
|
||||
@ -838,6 +838,14 @@ msgstr ""
|
||||
msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (simplified)"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (traditional)"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts
|
||||
msgid "Clear Flow cache"
|
||||
msgstr ""
|
||||
@ -942,7 +950,11 @@ msgid "Configuration flow"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stage"
|
||||
#~ msgid "Configuration stage"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stages"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
@ -2197,6 +2209,10 @@ msgstr ""
|
||||
msgid "Generic OpenID Connect"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "Get this value from https://console.twilio.com"
|
||||
@ -4473,8 +4489,8 @@ msgid "Stage type"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr ""
|
||||
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
||||
@ -4533,6 +4549,10 @@ msgstr ""
|
||||
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||
msgid "State"
|
||||
msgstr ""
|
||||
@ -5004,6 +5024,10 @@ msgstr ""
|
||||
msgid "TOTP authenticator"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Taiwanese Mandarin"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
msgid "Target"
|
||||
msgstr ""
|
||||
@ -5653,7 +5677,7 @@ msgid "Use the username and password below to authenticate. The password can be
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
@ -6016,6 +6040,10 @@ msgstr ""
|
||||
msgid "When enabled, user fields are matched regardless of their casing."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||
msgstr ""
|
||||
|
@ -561,16 +561,16 @@ msgid "Background shown during execution."
|
||||
msgstr "Yürütme sırasında arka plan gösterilir."
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with errors."
|
||||
msgstr "Yedekleme hatalarla tamamlandı."
|
||||
#~ msgid "Backup finished with errors."
|
||||
#~ msgstr "Yedekleme hatalarla tamamlandı."
|
||||
|
||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||
msgid "Backup finished with warnings/backup not supported."
|
||||
msgstr "Yedekleme desteklenmeyen uyarılar/yedekleme ile tamamlandı."
|
||||
#~ msgid "Backup finished with warnings/backup not supported."
|
||||
#~ msgstr "Yedekleme desteklenmeyen uyarılar/yedekleme ile tamamlandı."
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Backup status"
|
||||
msgstr "Yedekleme durumu"
|
||||
#~ msgid "Backup status"
|
||||
#~ msgstr "Yedekleme durumu"
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||
#: src/pages/providers/ldap/LDAPProviderViewPage.ts
|
||||
@ -838,6 +838,14 @@ msgstr "İsteğin kullanıcı parolasının son x gün içinde değiştirilip de
|
||||
msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||
msgstr "İlke isteğindeki değeri, çoğunlukla parola gücünü sağlamak için kullanılan çeşitli kurallara göre denetler."
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (simplified)"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Chinese (traditional)"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts
|
||||
msgid "Clear Flow cache"
|
||||
msgstr "Akış önbelleğini temizleme"
|
||||
@ -942,8 +950,12 @@ msgid "Configuration flow"
|
||||
msgstr "Yapılandırma akışı"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stage"
|
||||
msgstr "Yapılandırma aşamasında"
|
||||
#~ msgid "Configuration stage"
|
||||
#~ msgstr "Yapılandırma aşamasında"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Configuration stages"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Configure WebAuthn"
|
||||
#~ msgstr "WebAuthn'i Yapılandır"
|
||||
@ -2166,6 +2178,10 @@ msgstr "Jenerik"
|
||||
msgid "Generic OpenID Connect"
|
||||
msgstr "Genel OpenID Connect"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||
msgid "Get this value from https://console.twilio.com"
|
||||
@ -4393,8 +4409,8 @@ msgid "Stage type"
|
||||
msgstr "Aşama türü"
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr "Kullanıcının uyumlu bir aygıtı olmadığında Kimlik Doğrulayıcısı'nı yapılandırmak için kullanılan Aşama. Bu yapılandırma Stage geçtikten sonra kullanıcıya yeniden istenmez."
|
||||
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
#~ msgstr "Kullanıcının uyumlu bir aygıtı olmadığında Kimlik Doğrulayıcısı'nı yapılandırmak için kullanılan Aşama. Bu yapılandırma Stage geçtikten sonra kullanıcıya yeniden istenmez."
|
||||
|
||||
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
||||
@ -4453,6 +4469,10 @@ msgstr "Aşamalar"
|
||||
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
|
||||
msgstr "Aşamalar, bir Akış'ın kullanıcının yönlendirildiği tek adımlardır. Bir aşama yalnızca bir akış içinden yürütülebilir."
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||
msgid "State"
|
||||
msgstr "Eyalet"
|
||||
@ -4907,6 +4927,10 @@ msgstr "TOTP Cihazı"
|
||||
msgid "TOTP authenticator"
|
||||
msgstr "TOTP kimlik doğrulayıcı"
|
||||
|
||||
#: src/interfaces/locale.ts
|
||||
msgid "Taiwanese Mandarin"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
msgid "Target"
|
||||
msgstr "Hedef"
|
||||
@ -5558,8 +5582,8 @@ msgid "Use the username and password below to authenticate. The password can be
|
||||
msgstr "Kimlik doğrulaması için aşağıdaki kullanıcı adı ve parolayı kullanın. Parola daha sonra Belirteçler sayfasından alınabilir."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr "Bu sağlayıcıyı nginx'in auth_request veya traefik's forwardAuth ile kullanın. Her uygulama/etki alanının kendi sağlayıcısına ihtiyacı vardır. Ayrıca, her etki alanında /akprox üsse yönlendirilmelidir (manged bir üs kullanırken, bu sizin için yapılır)."
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you)."
|
||||
msgstr "Bu sağlayıcıyı nginx'in auth_request veya traefik's forwardAuth ile kullanın. Her uygulama/etki alanının kendi sağlayıcısına ihtiyacı vardır. Ayrıca, her etki alanında /outpost.goauthentik.io üsse yönlendirilmelidir (manged bir üs kullanırken, bu sizin için yapılır)."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application."
|
||||
@ -5914,6 +5938,10 @@ msgstr "Etkinleştirildiğinde, davetiye kullanımdan sonra silinir."
|
||||
msgid "When enabled, user fields are matched regardless of their casing."
|
||||
msgstr "Etkinleştirildiğinde, kullanıcı alanları muhafazası ne olursa olsun eşleştirilir."
|
||||
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||
msgstr "Seçildiğinde, ayrı bir sayfa yerine aynı sayfada bir parola alanı gösterilir. Bu, kullanıcı adı numaralandırma saldırılarını engeller."
|
||||
|
6117
web/src/locales/zh-Hans.po
Normal file
6117
web/src/locales/zh-Hans.po
Normal file
File diff suppressed because it is too large
Load Diff
6117
web/src/locales/zh-Hant.po
Normal file
6117
web/src/locales/zh-Hant.po
Normal file
File diff suppressed because it is too large
Load Diff
6117
web/src/locales/zh_TW.po
Normal file
6117
web/src/locales/zh_TW.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,6 @@ import "../../elements/charts/AdminLoginsChart";
|
||||
import { paramURL } from "../../elements/router/RouterOutlet";
|
||||
import "./TopApplicationsTable";
|
||||
import "./cards/AdminStatusCard";
|
||||
import "./cards/BackupStatusCard";
|
||||
import "./cards/SystemStatusCard";
|
||||
import "./cards/VersionStatusCard";
|
||||
import "./cards/WorkerStatusCard";
|
||||
@ -166,7 +165,7 @@ export class AdminOverviewPage extends LitElement {
|
||||
</div>
|
||||
<!-- row 2 -->
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl card-container"
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container"
|
||||
>
|
||||
<ak-admin-status-system
|
||||
icon="pf-icon pf-icon-server"
|
||||
@ -175,7 +174,7 @@ export class AdminOverviewPage extends LitElement {
|
||||
</ak-admin-status-system>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-3-col-on-xl card-container"
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container"
|
||||
>
|
||||
<ak-admin-status-version
|
||||
icon="pf-icon pf-icon-bundle"
|
||||
@ -185,17 +184,7 @@ export class AdminOverviewPage extends LitElement {
|
||||
</ak-admin-status-version>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-2-col-on-md pf-m-3-col-on-xl card-container"
|
||||
>
|
||||
<ak-admin-status-card-backup
|
||||
icon="fa fa-database"
|
||||
header=${t`Backup status`}
|
||||
headerLink="#/administration/system-tasks"
|
||||
>
|
||||
</ak-admin-status-card-backup>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl card-container"
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container"
|
||||
>
|
||||
<ak-admin-status-card-workers
|
||||
icon="pf-icon pf-icon-server"
|
||||
|
@ -1,56 +0,0 @@
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { AdminApi, CapabilitiesEnum, StatusEnum } from "@goauthentik/api";
|
||||
|
||||
import { DEFAULT_CONFIG, config } from "../../../api/Config";
|
||||
import { convertToTitle } from "../../../utils";
|
||||
import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
|
||||
|
||||
@customElement("ak-admin-status-card-backup")
|
||||
export class BackupStatusCard extends AdminStatusCard<StatusEnum> {
|
||||
getPrimaryValue(): Promise<StatusEnum> {
|
||||
return new AdminApi(DEFAULT_CONFIG)
|
||||
.adminSystemTasksRetrieve({
|
||||
id: "backup_database",
|
||||
})
|
||||
.then((value) => {
|
||||
return value.status;
|
||||
})
|
||||
.catch(() => {
|
||||
// On error (probably 404), check the config and see if the server
|
||||
// can even backup
|
||||
return config().then((c) => {
|
||||
if (c.capabilities.includes(CapabilitiesEnum.Backup)) {
|
||||
return StatusEnum.Error;
|
||||
}
|
||||
return StatusEnum.Warning;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderValue(): TemplateResult {
|
||||
return html`${convertToTitle(this.value?.toString() || "")}`;
|
||||
}
|
||||
|
||||
getStatus(value: StatusEnum): Promise<AdminStatus> {
|
||||
switch (value) {
|
||||
case StatusEnum.Successful:
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-check-circle pf-m-success",
|
||||
});
|
||||
case StatusEnum.Error:
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-times-circle pf-m-danger",
|
||||
message: html`${t`Backup finished with errors.`}`,
|
||||
});
|
||||
default:
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-exclamation-triangle pf-m-warning",
|
||||
message: html`${t`Backup finished with warnings/backup not supported.`}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -214,7 +214,7 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
||||
</ak-form-element-horizontal>`;
|
||||
case ProxyMode.ForwardSingle:
|
||||
return html`<p class="pf-u-mb-xl">
|
||||
${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you).`}
|
||||
${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you).`}
|
||||
</p>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`External host`}
|
||||
|
@ -92,16 +92,19 @@ export class ProxyProviderViewPage extends LitElement {
|
||||
}
|
||||
|
||||
renderConfigTemplate(markdown: MarkdownDocument): MarkdownDocument {
|
||||
const extHost = new URL(this.provider?.externalHost || "http://a");
|
||||
// See website/docs/providers/proxy/forward_auth.mdx
|
||||
if (this.provider?.mode === ProxyMode.ForwardSingle) {
|
||||
markdown.html = markdown.html
|
||||
.replaceAll("authentik.company", window.location.hostname)
|
||||
.replaceAll("outpost.company", window.location.hostname)
|
||||
.replaceAll("app.company", this.provider?.externalHost || "");
|
||||
.replaceAll("http://outpost.company:9000", window.location.hostname)
|
||||
.replaceAll("https://app.company", extHost.toString())
|
||||
.replaceAll("app.company", extHost.hostname);
|
||||
} else if (this.provider?.mode == ProxyMode.ForwardDomain) {
|
||||
markdown.html = markdown.html
|
||||
.replaceAll("authentik.company", window.location.hostname)
|
||||
.replaceAll("outpost.company", this.provider?.externalHost || "");
|
||||
.replaceAll("https://app.company", extHost.hostname)
|
||||
.replaceAll("http://outpost.company:9000", extHost.toString());
|
||||
}
|
||||
return markdown;
|
||||
}
|
||||
|
@ -25,14 +25,14 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
||||
stageUuid: pk,
|
||||
})
|
||||
.then((stage) => {
|
||||
this.showConfigurationStage =
|
||||
this.showConfigurationStages =
|
||||
stage.notConfiguredAction === NotConfiguredActionEnum.Configure;
|
||||
return stage;
|
||||
});
|
||||
}
|
||||
|
||||
@property({ type: Boolean })
|
||||
showConfigurationStage = true;
|
||||
showConfigurationStages = true;
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
@ -136,9 +136,9 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
||||
target.selectedOptions[0].value ===
|
||||
NotConfiguredActionEnum.Configure
|
||||
) {
|
||||
this.showConfigurationStage = true;
|
||||
this.showConfigurationStages = true;
|
||||
} else {
|
||||
this.showConfigurationStage = false;
|
||||
this.showConfigurationStages = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
@ -165,21 +165,13 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
||||
</option>
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
${this.showConfigurationStage
|
||||
${this.showConfigurationStages
|
||||
? html`
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Configuration stage`}
|
||||
?required=${true}
|
||||
name="configurationStage"
|
||||
label=${t`Configuration stages`}
|
||||
name="configurationStages"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value=""
|
||||
?selected=${this.instance?.configurationStage ===
|
||||
undefined}
|
||||
>
|
||||
---------
|
||||
</option>
|
||||
<select class="pf-c-form-control" multiple>
|
||||
${until(
|
||||
new StagesApi(DEFAULT_CONFIG)
|
||||
.stagesAllList({
|
||||
@ -187,9 +179,11 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
||||
})
|
||||
.then((stages) => {
|
||||
return stages.results.map((stage) => {
|
||||
const selected =
|
||||
this.instance?.configurationStage ===
|
||||
stage.pk;
|
||||
const selected = Array.from(
|
||||
this.instance?.configurationStages || [],
|
||||
).some((su) => {
|
||||
return su == stage.pk;
|
||||
});
|
||||
return html`<option
|
||||
value=${ifDefined(stage.pk)}
|
||||
?selected=${selected}
|
||||
@ -202,7 +196,10 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.`}
|
||||
${t`Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`When multiple stages are selected, the user can choose which one they want to enroll.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
`
|
||||
|
@ -153,7 +153,7 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
|
||||
?required=${true}
|
||||
name="sources"
|
||||
>
|
||||
<select name="users" class="pf-c-form-control" multiple>
|
||||
<select class="pf-c-form-control" multiple>
|
||||
${until(
|
||||
new SourcesApi(DEFAULT_CONFIG)
|
||||
.sourcesAllList({})
|
||||
|
@ -9,7 +9,7 @@ Applications are used to configure and separate the authorization / access contr
|
||||
|
||||
## Authorization
|
||||
|
||||
Application access can be configured using (Policy) Bindings. You can use this to grant access to one or multiple users/groups, or dynamically give access using policies.
|
||||
Application access can be configured using (Policy) Bindings. Click on an application in the applications list, and select the *Policy / Group / User Bindings* tab. There you can bind users/groups/policies to grant them access. When nothing is bound, everyone has access. You can use this to grant access to one or multiple users/groups, or dynamically give access using policies.
|
||||
|
||||
By default, all users can access applications when no policies are bound.
|
||||
|
||||
|
@ -23,27 +23,6 @@ All of these variables can be set to values, but you can also use a URI-like for
|
||||
- `AUTHENTIK_POSTGRESQL__PORT`: Database port, defaults to 5432
|
||||
- `AUTHENTIK_POSTGRESQL__PASSWORD`: Database password, defaults to the environment variable `POSTGRES_PASSWORD`
|
||||
|
||||
### PostgreSQL Backup Settings
|
||||
|
||||
:::info
|
||||
The integrated backup is deprecated in 2022.1 and will be removed in a future version.
|
||||
:::
|
||||
|
||||
- `AUTHENTIK_POSTGRESQL__BACKUP__ENABLED`: Controls if the inbuilt backup-mechanism is enabled, defaults to false (new in 2021.10).
|
||||
|
||||
Optionally enable automated database backups to S3 or S3-compatible storages.
|
||||
|
||||
- `AUTHENTIK_POSTGRESQL__S3_BACKUP__ACCESS_KEY`: S3 Access Key
|
||||
- `AUTHENTIK_POSTGRESQL__S3_BACKUP__SECRET_KEY`: S3 Secret Key
|
||||
- `AUTHENTIK_POSTGRESQL__S3_BACKUP__BUCKET`: S3 Bucket
|
||||
- `AUTHENTIK_POSTGRESQL__S3_BACKUP__REGION`: S3 Region, defaults to `eu-central-1`
|
||||
- `AUTHENTIK_POSTGRESQL__S3_BACKUP__LOCATION`: Relative Location of the files to the bucket. Defaults to the root of the bucket.
|
||||
|
||||
To use an S3-compatible storage, set the following settings.
|
||||
|
||||
- `AUTHENTIK_POSTGRESQL__S3_BACKUP__HOST`: URL to the Service, for example `https://play.min.io`
|
||||
- `AUTHENTIK_POSTGRESQL__S3_BACKUP__INSECURE_SKIP_VERIFY`: Set to `true` to disable SSL Certificate verification.
|
||||
|
||||
## Redis Settings
|
||||
|
||||
- `AUTHENTIK_REDIS__HOST`: Hostname of your Redis Server
|
||||
@ -142,6 +121,16 @@ Disable the inbuilt update-checker. Defaults to `false`.
|
||||
|
||||
Placeholder for outpost docker images. Default: `ghcr.io/goauthentik/%(type)s:%(version)s`.
|
||||
|
||||
- `AUTHENTIK_OUTPOSTS__DISCOVER`
|
||||
|
||||
Configure the automatic discovery of integrations. Defaults to `true`.
|
||||
|
||||
By default, the following is discovered:
|
||||
|
||||
- Kubernetes in-cluster config
|
||||
- Kubeconfig
|
||||
- Existence of a docker socket
|
||||
|
||||
### AUTHENTIK_AVATARS
|
||||
|
||||
Configure how authentik should show avatars for users. Following values can be set:
|
||||
|
@ -21,8 +21,8 @@ If this is a fresh authentik install run the following commands to generate a pa
|
||||
sudo apt-get install -y pwgen
|
||||
# Because of a PostgreSQL limitation, only passwords up to 99 chars are supported
|
||||
# See https://www.postgresql.org/message-id/09512C4F-8CB9-4021-B455-EF4C4F0D55A0@amazon.com
|
||||
echo "PG_PASS=$(pwgen 40 1)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(pwgen 50 1)" >> .env
|
||||
echo "PG_PASS=$(pwgen -s 40 1)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(pwgen -s 50 1)" >> .env
|
||||
# Skip if you don't want to enable error reporting
|
||||
echo "AUTHENTIK_ERROR_REPORTING__ENABLED=true" >> .env
|
||||
```
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user