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]
|
[bumpversion]
|
||||||
current_version = 2022.1.5
|
current_version = 2022.2.1
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
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 authentik/lib/default.yml local.env.yml
|
||||||
cp -R .github ..
|
cp -R .github ..
|
||||||
cp -R scripts ..
|
cp -R scripts ..
|
||||||
cp -R poetry.lock pyproject.toml ..
|
|
||||||
git checkout $(git describe --abbrev=0 --match 'version/*')
|
git checkout $(git describe --abbrev=0 --match 'version/*')
|
||||||
rm -rf .github/ scripts/
|
rm -rf .github/ scripts/
|
||||||
mv ../.github ../scripts ../poetry.lock ../pyproject.toml .
|
mv ../.github ../scripts .
|
||||||
- name: prepare
|
- name: prepare
|
||||||
env:
|
env:
|
||||||
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
|
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:
|
with:
|
||||||
push: ${{ github.event_name == 'release' }}
|
push: ${{ github.event_name == 'release' }}
|
||||||
tags: |
|
tags: |
|
||||||
beryju/authentik:2022.1.5,
|
beryju/authentik:2022.2.1,
|
||||||
beryju/authentik:latest,
|
beryju/authentik:latest,
|
||||||
ghcr.io/goauthentik/server:2022.1.5,
|
ghcr.io/goauthentik/server:2022.2.1,
|
||||||
ghcr.io/goauthentik/server:latest
|
ghcr.io/goauthentik/server:latest
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
context: .
|
context: .
|
||||||
- name: Building Docker Image (stable)
|
- 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: |
|
run: |
|
||||||
docker pull beryju/authentik:latest
|
docker pull beryju/authentik:latest
|
||||||
docker tag beryju/authentik:latest beryju/authentik:stable
|
docker tag beryju/authentik:latest beryju/authentik:stable
|
||||||
@ -78,14 +78,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
push: ${{ github.event_name == 'release' }}
|
push: ${{ github.event_name == 'release' }}
|
||||||
tags: |
|
tags: |
|
||||||
beryju/authentik-${{ matrix.type }}:2022.1.5,
|
beryju/authentik-${{ matrix.type }}:2022.2.1,
|
||||||
beryju/authentik-${{ matrix.type }}:latest,
|
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
|
ghcr.io/goauthentik/${{ matrix.type }}:latest
|
||||||
file: ${{ matrix.type }}.Dockerfile
|
file: ${{ matrix.type }}.Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
- name: Building Docker Image (stable)
|
- 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: |
|
run: |
|
||||||
docker pull beryju/authentik-${{ matrix.type }}:latest
|
docker pull beryju/authentik-${{ matrix.type }}:latest
|
||||||
docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable
|
docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable
|
||||||
@ -170,7 +170,7 @@ jobs:
|
|||||||
SENTRY_PROJECT: authentik
|
SENTRY_PROJECT: authentik
|
||||||
SENTRY_URL: https://sentry.beryju.org
|
SENTRY_URL: https://sentry.beryju.org
|
||||||
with:
|
with:
|
||||||
version: authentik@2022.1.5
|
version: authentik@2022.2.1
|
||||||
environment: beryjuorg-prod
|
environment: beryjuorg-prod
|
||||||
sourcemaps: './web/dist'
|
sourcemaps: './web/dist'
|
||||||
url_prefix: '~/static/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
|
docker-compose run -u root server test
|
||||||
- name: Extract version number
|
- name: Extract version number
|
||||||
id: get_version
|
id: get_version
|
||||||
uses: actions/github-script@v5
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
@ -16,7 +16,7 @@ ENV NODE_ENV=production
|
|||||||
RUN cd /work/web && npm i && npm run build
|
RUN cd /work/web && npm i && npm run build
|
||||||
|
|
||||||
# Stage 3: Build go proxy
|
# 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
|
WORKDIR /work
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ---------- | ------------------ |
|
| ---------- | ------------------ |
|
||||||
| 2021.10.x | :white_check_mark: |
|
| 2022.1.x | :white_check_mark: |
|
||||||
| 2021.12.x | :white_check_mark: |
|
| 2022.2.x | :white_check_mark: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from os import environ
|
from os import environ
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
__version__ = "2022.1.5"
|
__version__ = "2022.2.1"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
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.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ViewSet
|
from rest_framework.viewsets import ViewSet
|
||||||
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
|
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class TaskSerializer(PassiveSerializer):
|
class TaskSerializer(PassiveSerializer):
|
||||||
"""Serialize TaskInfo and TaskResult"""
|
"""Serialize TaskInfo and TaskResult"""
|
||||||
@ -89,6 +92,7 @@ class TaskViewSet(ViewSet):
|
|||||||
try:
|
try:
|
||||||
task_module = import_module(task.task_call_module)
|
task_module = import_module(task.task_call_module)
|
||||||
task_func = getattr(task_module, task.task_call_func)
|
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)
|
task_func.delay(*task.task_call_args, **task.task_call_kwargs)
|
||||||
messages.success(
|
messages.success(
|
||||||
self.request,
|
self.request,
|
||||||
@ -96,6 +100,7 @@ class TaskViewSet(ViewSet):
|
|||||||
)
|
)
|
||||||
return Response(status=204)
|
return Response(status=204)
|
||||||
except (ImportError, AttributeError): # pragma: no cover
|
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
|
# if we get an import error, the module path has probably changed
|
||||||
task.delete()
|
task.delete()
|
||||||
return Response(status=500)
|
return Response(status=500)
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
"""core Configs API"""
|
"""core Configs API"""
|
||||||
from os import environ, path
|
from os import path
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from drf_spectacular.utils import extend_schema
|
from drf_spectacular.utils import extend_schema
|
||||||
from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
|
|
||||||
from rest_framework.fields import (
|
from rest_framework.fields import (
|
||||||
BooleanField,
|
BooleanField,
|
||||||
CharField,
|
CharField,
|
||||||
@ -28,7 +27,6 @@ class Capabilities(models.TextChoices):
|
|||||||
|
|
||||||
CAN_SAVE_MEDIA = "can_save_media"
|
CAN_SAVE_MEDIA = "can_save_media"
|
||||||
CAN_GEO_IP = "can_geo_ip"
|
CAN_GEO_IP = "can_geo_ip"
|
||||||
CAN_BACKUP = "can_backup"
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorReportingConfigSerializer(PassiveSerializer):
|
class ErrorReportingConfigSerializer(PassiveSerializer):
|
||||||
@ -65,13 +63,6 @@ class ConfigView(APIView):
|
|||||||
caps.append(Capabilities.CAN_SAVE_MEDIA)
|
caps.append(Capabilities.CAN_SAVE_MEDIA)
|
||||||
if GEOIP_READER.enabled:
|
if GEOIP_READER.enabled:
|
||||||
caps.append(Capabilities.CAN_GEO_IP)
|
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
|
return caps
|
||||||
|
|
||||||
@extend_schema(responses={200: ConfigSerializer(many=False)})
|
@extend_schema(responses={200: ConfigSerializer(many=False)})
|
||||||
|
@ -56,7 +56,11 @@ class ApplicationSerializer(ModelSerializer):
|
|||||||
if isinstance(user, SimpleLazyObject):
|
if isinstance(user, SimpleLazyObject):
|
||||||
user._setup()
|
user._setup()
|
||||||
user = user._wrapped
|
user = user._wrapped
|
||||||
|
try:
|
||||||
return url % user.__dict__
|
return url % user.__dict__
|
||||||
|
except ValueError as exc:
|
||||||
|
LOGGER.warning("Failed to format launch url", exc=exc)
|
||||||
|
return url
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
|
@ -1,17 +1,7 @@
|
|||||||
"""authentik core tasks"""
|
"""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.contrib.sessions.backends.cache import KEY_PREFIX
|
||||||
from django.core import management
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
|
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.models import AuthenticatedSession, ExpiringModel
|
from authentik.core.models import AuthenticatedSession, ExpiringModel
|
||||||
@ -21,7 +11,6 @@ from authentik.events.monitored_tasks import (
|
|||||||
TaskResultStatus,
|
TaskResultStatus,
|
||||||
prefill_task,
|
prefill_task,
|
||||||
)
|
)
|
||||||
from authentik.lib.config import CONFIG
|
|
||||||
from authentik.root.celery import CELERY_APP
|
from authentik.root.celery import CELERY_APP
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
@ -53,46 +42,3 @@ def clean_expired_models(self: MonitoredTask):
|
|||||||
LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount)
|
LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount)
|
||||||
messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}")
|
messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}")
|
||||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages))
|
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 %}
|
{% block head_before %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
<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>
|
<script src="{% static 'dist/poly.js' %}" type="module"></script>
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
"""events GeoIP Reader"""
|
"""events GeoIP Reader"""
|
||||||
from datetime import datetime
|
|
||||||
from os import stat
|
from os import stat
|
||||||
from time import time
|
|
||||||
from typing import Optional, TypedDict
|
from typing import Optional, TypedDict
|
||||||
|
|
||||||
from geoip2.database import Reader
|
from geoip2.database import Reader
|
||||||
@ -46,14 +44,18 @@ class GeoIPReader:
|
|||||||
LOGGER.warning("Failed to load GeoIP database", exc=exc)
|
LOGGER.warning("Failed to load GeoIP database", exc=exc)
|
||||||
|
|
||||||
def __check_expired(self):
|
def __check_expired(self):
|
||||||
"""Check if the geoip database has been opened longer than 8 hours,
|
"""Check if the modification date of the GeoIP database has
|
||||||
and re-open it, as it will probably will have been re-downloaded"""
|
changed, and reload it if so"""
|
||||||
now = time()
|
path = CONFIG.y("geoip")
|
||||||
diff = datetime.fromtimestamp(now) - datetime.fromtimestamp(self.__last_mtime)
|
try:
|
||||||
diff_hours = diff.total_seconds() // 3600
|
mtime = stat(path).st_mtime
|
||||||
if diff_hours >= 8:
|
diff = self.__last_mtime < mtime
|
||||||
LOGGER.info("GeoIP databased loaded too long, re-opening", diff=diff)
|
if diff > 0:
|
||||||
|
LOGGER.info("Found new GeoIP Database, reopening", diff=diff)
|
||||||
self.__open()
|
self.__open()
|
||||||
|
except OSError as exc:
|
||||||
|
LOGGER.warning("Failed to check GeoIP age", exc=exc)
|
||||||
|
return
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self) -> bool:
|
def enabled(self) -> bool:
|
||||||
|
@ -5,16 +5,6 @@ postgresql:
|
|||||||
user: authentik
|
user: authentik
|
||||||
port: 5432
|
port: 5432
|
||||||
password: 'env://POSTGRES_PASSWORD'
|
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:
|
web:
|
||||||
listen: 0.0.0.0:9000
|
listen: 0.0.0.0:9000
|
||||||
@ -65,6 +55,7 @@ outposts:
|
|||||||
# %(version)s: Current version; 2021.4.1
|
# %(version)s: Current version; 2021.4.1
|
||||||
# %(build_hash)s: Build hash if you're running a beta version
|
# %(build_hash)s: Build hash if you're running a beta version
|
||||||
container_image_base: ghcr.io/goauthentik/%(type)s:%(version)s
|
container_image_base: ghcr.io/goauthentik/%(type)s:%(version)s
|
||||||
|
discover: true
|
||||||
|
|
||||||
cookie_domain: null
|
cookie_domain: null
|
||||||
disable_update_check: false
|
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 aioredis.errors import ConnectionClosedError, ReplyError
|
||||||
from billiard.exceptions import SoftTimeLimitExceeded, WorkerLostError
|
from billiard.exceptions import SoftTimeLimitExceeded, WorkerLostError
|
||||||
from botocore.client import ClientError
|
|
||||||
from botocore.exceptions import BotoCoreError
|
|
||||||
from celery.exceptions import CeleryError
|
from celery.exceptions import CeleryError
|
||||||
from channels.middleware import BaseMiddleware
|
from channels.middleware import BaseMiddleware
|
||||||
from channels_redis.core import ChannelFull
|
from channels_redis.core import ChannelFull
|
||||||
@ -81,9 +79,6 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
|||||||
WorkerLostError,
|
WorkerLostError,
|
||||||
CeleryError,
|
CeleryError,
|
||||||
SoftTimeLimitExceeded,
|
SoftTimeLimitExceeded,
|
||||||
# S3 errors
|
|
||||||
BotoCoreError,
|
|
||||||
ClientError,
|
|
||||||
# custom baseclass
|
# custom baseclass
|
||||||
SentryIgnoredException,
|
SentryIgnoredException,
|
||||||
# ldap errors
|
# ldap errors
|
||||||
@ -101,8 +96,6 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
|||||||
return None
|
return None
|
||||||
if "logger" in event:
|
if "logger" in event:
|
||||||
if event["logger"] in [
|
if event["logger"] in [
|
||||||
"dbbackup",
|
|
||||||
"botocore",
|
|
||||||
"kombu",
|
"kombu",
|
||||||
"asyncio",
|
"asyncio",
|
||||||
"multiprocessing",
|
"multiprocessing",
|
||||||
|
@ -3,6 +3,8 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
|
|
||||||
|
from docker.errors import DockerException
|
||||||
|
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
|
|
||||||
HEADER = "### Managed by authentik"
|
HEADER = "### Managed by authentik"
|
||||||
@ -27,6 +29,8 @@ class DockerInlineSSH:
|
|||||||
def __init__(self, host: str, keypair: CertificateKeyPair) -> None:
|
def __init__(self, host: str, keypair: CertificateKeyPair) -> None:
|
||||||
self.host = host
|
self.host = host
|
||||||
self.keypair = keypair
|
self.keypair = keypair
|
||||||
|
if not self.keypair:
|
||||||
|
raise DockerException("keypair must be set for SSH connections")
|
||||||
self.config_path = Path("~/.ssh/config").expanduser()
|
self.config_path = Path("~/.ssh/config").expanduser()
|
||||||
self.header = f"{HEADER} - {self.host}\n"
|
self.header = f"{HEADER} - {self.host}\n"
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ from authentik.events.monitored_tasks import (
|
|||||||
TaskResultStatus,
|
TaskResultStatus,
|
||||||
prefill_task,
|
prefill_task,
|
||||||
)
|
)
|
||||||
|
from authentik.lib.config import CONFIG
|
||||||
from authentik.lib.utils.reflection import path_to_class
|
from authentik.lib.utils.reflection import path_to_class
|
||||||
from authentik.outposts.controllers.base import BaseController, ControllerException
|
from authentik.outposts.controllers.base import BaseController, ControllerException
|
||||||
from authentik.outposts.controllers.docker import DockerClient
|
from authentik.outposts.controllers.docker import DockerClient
|
||||||
@ -231,6 +232,9 @@ def _outpost_single_update(outpost: Outpost, layer=None):
|
|||||||
@CELERY_APP.task()
|
@CELERY_APP.task()
|
||||||
def outpost_local_connection():
|
def outpost_local_connection():
|
||||||
"""Checks the local environment and create Service connections."""
|
"""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
|
# Explicitly check against token filename, as that's
|
||||||
# only present when the integration is enabled
|
# only present when the integration is enabled
|
||||||
if Path(SERVICE_TOKEN_FILENAME).exists():
|
if Path(SERVICE_TOKEN_FILENAME).exists():
|
||||||
|
@ -45,6 +45,13 @@ class GrantTypes(models.TextChoices):
|
|||||||
HYBRID = "hybrid"
|
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):
|
class SubModes(models.TextChoices):
|
||||||
"""Mode after which 'sub' attribute is generateed, for compatibility reasons"""
|
"""Mode after which 'sub' attribute is generateed, for compatibility reasons"""
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
name="test",
|
name="test",
|
||||||
client_id="test",
|
client_id="test",
|
||||||
authorization_flow=create_test_flow(),
|
authorization_flow=create_test_flow(),
|
||||||
redirect_uris="http://local.invalid",
|
redirect_uris="http://local.invalid/Foo",
|
||||||
)
|
)
|
||||||
with self.assertRaises(AuthorizeError):
|
with self.assertRaises(AuthorizeError):
|
||||||
request = self.factory.get(
|
request = self.factory.get(
|
||||||
@ -51,7 +51,7 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
data={
|
data={
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
"client_id": "test",
|
"client_id": "test",
|
||||||
"redirect_uri": "http://local.invalid",
|
"redirect_uri": "http://local.invalid/Foo",
|
||||||
"request": "foo",
|
"request": "foo",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -105,26 +105,30 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
name="test",
|
name="test",
|
||||||
client_id="test",
|
client_id="test",
|
||||||
authorization_flow=create_test_flow(),
|
authorization_flow=create_test_flow(),
|
||||||
redirect_uris="http://local.invalid",
|
redirect_uris="http://local.invalid/Foo",
|
||||||
)
|
)
|
||||||
request = self.factory.get(
|
request = self.factory.get(
|
||||||
"/",
|
"/",
|
||||||
data={
|
data={
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
"client_id": "test",
|
"client_id": "test",
|
||||||
"redirect_uri": "http://local.invalid",
|
"redirect_uri": "http://local.invalid/Foo",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
OAuthAuthorizationParams.from_request(request).grant_type,
|
OAuthAuthorizationParams.from_request(request).grant_type,
|
||||||
GrantTypes.AUTHORIZATION_CODE,
|
GrantTypes.AUTHORIZATION_CODE,
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
OAuthAuthorizationParams.from_request(request).redirect_uri,
|
||||||
|
"http://local.invalid/Foo",
|
||||||
|
)
|
||||||
request = self.factory.get(
|
request = self.factory.get(
|
||||||
"/",
|
"/",
|
||||||
data={
|
data={
|
||||||
"response_type": "id_token",
|
"response_type": "id_token",
|
||||||
"client_id": "test",
|
"client_id": "test",
|
||||||
"redirect_uri": "http://local.invalid",
|
"redirect_uri": "http://local.invalid/Foo",
|
||||||
"scope": "openid",
|
"scope": "openid",
|
||||||
"state": "foo",
|
"state": "foo",
|
||||||
},
|
},
|
||||||
@ -140,7 +144,7 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
data={
|
data={
|
||||||
"response_type": "id_token",
|
"response_type": "id_token",
|
||||||
"client_id": "test",
|
"client_id": "test",
|
||||||
"redirect_uri": "http://local.invalid",
|
"redirect_uri": "http://local.invalid/Foo",
|
||||||
"state": "foo",
|
"state": "foo",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -153,7 +157,7 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
data={
|
data={
|
||||||
"response_type": "code token",
|
"response_type": "code token",
|
||||||
"client_id": "test",
|
"client_id": "test",
|
||||||
"redirect_uri": "http://local.invalid",
|
"redirect_uri": "http://local.invalid/Foo",
|
||||||
"scope": "openid",
|
"scope": "openid",
|
||||||
"state": "foo",
|
"state": "foo",
|
||||||
},
|
},
|
||||||
@ -167,7 +171,7 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
data={
|
data={
|
||||||
"response_type": "invalid",
|
"response_type": "invalid",
|
||||||
"client_id": "test",
|
"client_id": "test",
|
||||||
"redirect_uri": "http://local.invalid",
|
"redirect_uri": "http://local.invalid/Foo",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
OAuthAuthorizationParams.from_request(request)
|
OAuthAuthorizationParams.from_request(request)
|
||||||
|
@ -44,6 +44,7 @@ from authentik.providers.oauth2.models import (
|
|||||||
AuthorizationCode,
|
AuthorizationCode,
|
||||||
GrantTypes,
|
GrantTypes,
|
||||||
OAuth2Provider,
|
OAuth2Provider,
|
||||||
|
ResponseMode,
|
||||||
ResponseTypes,
|
ResponseTypes,
|
||||||
)
|
)
|
||||||
from authentik.providers.oauth2.utils import HttpResponseRedirectScheme
|
from authentik.providers.oauth2.utils import HttpResponseRedirectScheme
|
||||||
@ -99,7 +100,7 @@ class OAuthAuthorizationParams:
|
|||||||
# and POST request.
|
# and POST request.
|
||||||
query_dict = request.POST if request.method == "POST" else request.GET
|
query_dict = request.POST if request.method == "POST" else request.GET
|
||||||
state = query_dict.get("state")
|
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", "")
|
response_type = query_dict.get("response_type", "")
|
||||||
grant_type = None
|
grant_type = None
|
||||||
@ -153,7 +154,10 @@ class OAuthAuthorizationParams:
|
|||||||
def check_redirect_uri(self):
|
def check_redirect_uri(self):
|
||||||
"""Redirect URI validation."""
|
"""Redirect URI validation."""
|
||||||
allowed_redirect_urls = self.provider.redirect_uris.split()
|
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.")
|
LOGGER.warning("Missing redirect uri.")
|
||||||
raise RedirectUriError("", allowed_redirect_urls)
|
raise RedirectUriError("", allowed_redirect_urls)
|
||||||
|
|
||||||
@ -169,7 +173,7 @@ class OAuthAuthorizationParams:
|
|||||||
allow=self.redirect_uri,
|
allow=self.redirect_uri,
|
||||||
)
|
)
|
||||||
return
|
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(
|
LOGGER.warning(
|
||||||
"Invalid redirect uri",
|
"Invalid redirect uri",
|
||||||
redirect_uri=self.redirect_uri,
|
redirect_uri=self.redirect_uri,
|
||||||
@ -299,13 +303,23 @@ class OAuthFulfillmentStage(StageView):
|
|||||||
code = self.params.create_code(self.request)
|
code = self.params.create_code(self.request)
|
||||||
code.save(force_insert=True)
|
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["code"] = code.code
|
||||||
query_params["state"] = [str(self.params.state) if self.params.state else ""]
|
query_params["state"] = [str(self.params.state) if self.params.state else ""]
|
||||||
|
|
||||||
uri = uri._replace(query=urlencode(query_params, doseq=True))
|
uri = uri._replace(query=urlencode(query_params, doseq=True))
|
||||||
return urlunsplit(uri)
|
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)
|
query_fragment = self.create_implicit_response(code)
|
||||||
|
|
||||||
uri = uri._replace(
|
uri = uri._replace(
|
||||||
|
@ -12,4 +12,8 @@ class AuthentikProviderProxyConfig(AppConfig):
|
|||||||
verbose_name = "authentik Providers.Proxy"
|
verbose_name = "authentik Providers.Proxy"
|
||||||
|
|
||||||
def ready(self) -> None:
|
def ready(self) -> None:
|
||||||
|
from authentik.providers.proxy.tasks import proxy_set_defaults
|
||||||
|
|
||||||
import_module("authentik.providers.proxy.managed")
|
import_module("authentik.providers.proxy.managed")
|
||||||
|
|
||||||
|
proxy_set_defaults.delay()
|
||||||
|
@ -28,12 +28,12 @@ class ProxyDockerController(DockerController):
|
|||||||
labels["traefik.enable"] = "true"
|
labels["traefik.enable"] = "true"
|
||||||
labels[
|
labels[
|
||||||
f"traefik.http.routers.{traefik_name}-router.rule"
|
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.tls"] = "true"
|
||||||
labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service"
|
labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service"
|
||||||
labels[
|
labels[
|
||||||
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"
|
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"
|
||||||
] = "/akprox/ping"
|
] = "/outpost.goauthentik.io/ping"
|
||||||
labels[
|
labels[
|
||||||
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.port"
|
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.port"
|
||||||
] = "9300"
|
] = "9300"
|
||||||
|
@ -92,6 +92,8 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
|
|||||||
# Buffer sizes for large headers with JWTs
|
# Buffer sizes for large headers with JWTs
|
||||||
"nginx.ingress.kubernetes.io/proxy-buffers-number": "4",
|
"nginx.ingress.kubernetes.io/proxy-buffers-number": "4",
|
||||||
"nginx.ingress.kubernetes.io/proxy-buffer-size": "16k",
|
"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)
|
annotations.update(self.controller.outpost.config.kubernetes_ingress_annotations)
|
||||||
return annotations
|
return annotations
|
||||||
@ -126,7 +128,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
|
|||||||
port=V1ServiceBackendPort(name="http"),
|
port=V1ServiceBackendPort(name="http"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
path="/akprox",
|
path="/outpost.goauthentik.io",
|
||||||
path_type="ImplementationSpecific",
|
path_type="ImplementationSpecific",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -119,7 +119,10 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
|
|||||||
),
|
),
|
||||||
spec=TraefikMiddlewareSpec(
|
spec=TraefikMiddlewareSpec(
|
||||||
forwardAuth=TraefikMiddlewareSpecForwardAuth(
|
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=[
|
authResponseHeaders=[
|
||||||
"X-authentik-username",
|
"X-authentik-username",
|
||||||
"X-authentik-groups",
|
"X-authentik-groups",
|
||||||
|
@ -27,7 +27,7 @@ def get_cookie_secret():
|
|||||||
|
|
||||||
|
|
||||||
def _get_callback_url(uri: str) -> str:
|
def _get_callback_url(uri: str) -> str:
|
||||||
return urljoin(uri, "/akprox/callback")
|
return urljoin(uri, "outpost.goauthentik.io/callback")
|
||||||
|
|
||||||
|
|
||||||
class ProxyMode(models.TextChoices):
|
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
|
import sys
|
||||||
from hashlib import sha512
|
from hashlib import sha512
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from tempfile import gettempdir
|
|
||||||
from time import time
|
from time import time
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
@ -137,7 +136,6 @@ INSTALLED_APPS = [
|
|||||||
"guardian",
|
"guardian",
|
||||||
"django_prometheus",
|
"django_prometheus",
|
||||||
"channels",
|
"channels",
|
||||||
"dbbackup",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
GUARDIAN_MONKEY_PATCH = False
|
GUARDIAN_MONKEY_PATCH = False
|
||||||
@ -357,32 +355,6 @@ CELERY_RESULT_BACKEND = (
|
|||||||
f"{_redis_url}/{CONFIG.y('redis.message_queue_db')}{REDIS_CELERY_TLS_REQUIREMENTS}"
|
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 integration
|
||||||
SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
|
SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
|
||||||
|
|
||||||
@ -493,12 +465,9 @@ _LOGGING_HANDLER_MAP = {
|
|||||||
"urllib3": "WARNING",
|
"urllib3": "WARNING",
|
||||||
"websockets": "WARNING",
|
"websockets": "WARNING",
|
||||||
"daphne": "WARNING",
|
"daphne": "WARNING",
|
||||||
"dbbackup": "ERROR",
|
|
||||||
"kubernetes": "INFO",
|
"kubernetes": "INFO",
|
||||||
"asyncio": "WARNING",
|
"asyncio": "WARNING",
|
||||||
"aioredis": "WARNING",
|
"aioredis": "WARNING",
|
||||||
"s3transfer": "WARNING",
|
|
||||||
"botocore": "WARNING",
|
|
||||||
}
|
}
|
||||||
for handler_name, level in _LOGGING_HANDLER_MAP.items():
|
for handler_name, level in _LOGGING_HANDLER_MAP.items():
|
||||||
# pyright: reportGeneralTypeIssues=false
|
# pyright: reportGeneralTypeIssues=false
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
"""Sync LDAP Users and groups into authentik"""
|
"""Sync LDAP Users and groups into authentik"""
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from deepmerge import always_merger
|
|
||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from structlog.stdlib import BoundLogger, get_logger
|
from structlog.stdlib import BoundLogger, get_logger
|
||||||
|
|
||||||
from authentik.core.exceptions import PropertyMappingExpressionException
|
from authentik.core.exceptions import PropertyMappingExpressionException
|
||||||
from authentik.events.models import Event, EventAction
|
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.auth import LDAP_DISTINGUISHED_NAME
|
||||||
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
||||||
|
|
||||||
@ -123,8 +123,8 @@ class BaseLDAPSynchronizer:
|
|||||||
continue
|
continue
|
||||||
setattr(instance, key, value)
|
setattr(instance, key, value)
|
||||||
final_atttributes = {}
|
final_atttributes = {}
|
||||||
always_merger.merge(final_atttributes, instance.attributes)
|
MERGE_LIST_UNIQUE.merge(final_atttributes, instance.attributes)
|
||||||
always_merger.merge(final_atttributes, data.get("attributes", {}))
|
MERGE_LIST_UNIQUE.merge(final_atttributes, data.get("attributes", {}))
|
||||||
instance.attributes = final_atttributes
|
instance.attributes = final_atttributes
|
||||||
instance.save()
|
instance.save()
|
||||||
return (instance, False)
|
return (instance, False)
|
||||||
|
@ -13,8 +13,8 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
|
|||||||
|
|
||||||
def validate_not_configured_action(self, value):
|
def validate_not_configured_action(self, value):
|
||||||
"""Ensure that a configuration stage is set when not_configured_action is configure"""
|
"""Ensure that a configuration stage is set when not_configured_action is configure"""
|
||||||
configuration_stage = self.initial_data.get("configuration_stage")
|
configuration_stages = self.initial_data.get("configuration_stages")
|
||||||
if value == NotConfiguredAction.CONFIGURE and configuration_stage is None:
|
if value == NotConfiguredAction.CONFIGURE and configuration_stages is None:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
(
|
(
|
||||||
'When "Not configured action" is set to "Configure", '
|
'When "Not configured action" is set to "Configure", '
|
||||||
@ -29,7 +29,7 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
|
|||||||
fields = StageSerializer.Meta.fields + [
|
fields = StageSerializer.Meta.fields + [
|
||||||
"not_configured_action",
|
"not_configured_action",
|
||||||
"device_classes",
|
"device_classes",
|
||||||
"configuration_stage",
|
"configuration_stages",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -38,5 +38,5 @@ class AuthenticatorValidateStageViewSet(UsedByMixin, ModelViewSet):
|
|||||||
|
|
||||||
queryset = AuthenticatorValidateStage.objects.all()
|
queryset = AuthenticatorValidateStage.objects.all()
|
||||||
serializer_class = AuthenticatorValidateStageSerializer
|
serializer_class = AuthenticatorValidateStageSerializer
|
||||||
filterset_fields = ["name", "not_configured_action", "configuration_stage"]
|
filterset_fields = ["name", "not_configured_action", "configuration_stages"]
|
||||||
ordering = ["name"]
|
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
|
choices=NotConfiguredAction.choices, default=NotConfiguredAction.SKIP
|
||||||
)
|
)
|
||||||
|
|
||||||
configuration_stage = models.ForeignKey(
|
configuration_stages = models.ManyToManyField(
|
||||||
Stage,
|
Stage,
|
||||||
null=True,
|
|
||||||
blank=True,
|
blank=True,
|
||||||
default=None,
|
default=None,
|
||||||
on_delete=models.SET_DEFAULT,
|
|
||||||
related_name="+",
|
related_name="+",
|
||||||
help_text=_(
|
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."
|
"devices. After this configuration Stage passes, the user is not prompted again."
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
"""Authenticator Validation"""
|
"""Authenticator Validation"""
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django_otp import devices_for_user
|
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 rest_framework.serializers import ValidationError
|
||||||
from structlog.stdlib import get_logger
|
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.models import Event, EventAction
|
||||||
from authentik.events.utils import cleanse_dict, sanitize_dict
|
from authentik.events.utils import cleanse_dict, sanitize_dict
|
||||||
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
|
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
|
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
|
||||||
|
|
||||||
LOGGER = get_logger()
|
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):
|
class AuthenticatorValidationChallenge(WithUserInfoChallenge):
|
||||||
@ -33,12 +47,14 @@ class AuthenticatorValidationChallenge(WithUserInfoChallenge):
|
|||||||
|
|
||||||
device_challenges = ListField(child=DeviceChallenge())
|
device_challenges = ListField(child=DeviceChallenge())
|
||||||
component = CharField(default="ak-stage-authenticator-validate")
|
component = CharField(default="ak-stage-authenticator-validate")
|
||||||
|
configuration_stages = ListField(child=SelectableStageSerializer())
|
||||||
|
|
||||||
|
|
||||||
class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
||||||
"""Challenge used for Code-based and WebAuthn authenticators"""
|
"""Challenge used for Code-based and WebAuthn authenticators"""
|
||||||
|
|
||||||
selected_challenge = DeviceChallenge(required=False)
|
selected_challenge = DeviceChallenge(required=False)
|
||||||
|
selected_stage = CharField(required=False)
|
||||||
|
|
||||||
code = CharField(required=False)
|
code = CharField(required=False)
|
||||||
webauthn = JSONField(required=False)
|
webauthn = JSONField(required=False)
|
||||||
@ -46,7 +62,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
|||||||
component = CharField(default="ak-stage-authenticator-validate")
|
component = CharField(default="ak-stage-authenticator-validate")
|
||||||
|
|
||||||
def _challenge_allowed(self, classes: list):
|
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):
|
if not any(x["device_class"] in classes for x in device_challenges):
|
||||||
raise ValidationError("No compatible device class allowed")
|
raise ValidationError("No compatible device class allowed")
|
||||||
|
|
||||||
@ -71,7 +87,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
|||||||
def validate_selected_challenge(self, challenge: dict) -> dict:
|
def validate_selected_challenge(self, challenge: dict) -> dict:
|
||||||
"""Check which challenge the user has selected. Actual logic only used for SMS stage."""
|
"""Check which challenge the user has selected. Actual logic only used for SMS stage."""
|
||||||
# First check if the challenge is valid
|
# 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", ""):
|
if device_challenge.get("device_class", "") != challenge.get("device_class", ""):
|
||||||
raise ValidationError("invalid challenge selected")
|
raise ValidationError("invalid challenge selected")
|
||||||
if device_challenge.get("device_uid", "") != challenge.get("device_uid", ""):
|
if device_challenge.get("device_uid", "") != challenge.get("device_uid", ""):
|
||||||
@ -84,6 +100,15 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
|||||||
select_challenge(self.stage.request, devices.first())
|
select_challenge(self.stage.request, devices.first())
|
||||||
return challenge
|
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):
|
def validate(self, attrs: dict):
|
||||||
# Checking if the given data is from a valid device class is done above
|
# 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
|
# Here we only check if the any data was sent at all
|
||||||
@ -164,7 +189,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||||||
else:
|
else:
|
||||||
LOGGER.debug("No pending user, continuing")
|
LOGGER.debug("No pending user, continuing")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
self.request.session["device_challenges"] = challenges
|
self.request.session[SESSION_DEVICE_CHALLENGES] = challenges
|
||||||
|
|
||||||
# No allowed devices
|
# No allowed devices
|
||||||
if len(challenges) < 1:
|
if len(challenges) < 1:
|
||||||
@ -175,7 +200,16 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||||||
LOGGER.debug("Authenticator not configured, denying")
|
LOGGER.debug("Authenticator not configured, denying")
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
if stage.not_configured_action == NotConfiguredAction.CONFIGURE:
|
if stage.not_configured_action == NotConfiguredAction.CONFIGURE:
|
||||||
if not stage.configuration_stage:
|
LOGGER.debug("Authenticator not configured, forcing configure")
|
||||||
|
return self.prepare_stages(user)
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
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(
|
Event.new(
|
||||||
EventAction.CONFIGURATION_ERROR,
|
EventAction.CONFIGURATION_ERROR,
|
||||||
message=(
|
message=(
|
||||||
@ -185,25 +219,55 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||||||
stage=self,
|
stage=self,
|
||||||
).from_http(self.request).set_user(user).save()
|
).from_http(self.request).set_user(user).save()
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
LOGGER.debug("Authenticator not configured, sending user to configure")
|
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
|
# Because the foreign key to stage.configuration_stage points to
|
||||||
# a base stage class, we need to do another lookup
|
# a base stage class, we need to do another lookup
|
||||||
stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk)
|
stage = Stage.objects.get_subclass(pk=stage_pk)
|
||||||
# plan.insert inserts at 1 index, so when stage_ok pops 0,
|
# plan.insert inserts at 1 index, so when stage_ok pops 0,
|
||||||
# the configuration stage is next
|
# the configuration stage is next
|
||||||
self.executor.plan.insert_stage(stage)
|
self.executor.plan.insert_stage(stage)
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
return super().get(request, *args, **kwargs)
|
return res
|
||||||
|
|
||||||
def get_challenge(self) -> AuthenticatorValidationChallenge:
|
def get_challenge(self) -> AuthenticatorValidationChallenge:
|
||||||
challenges = self.request.session.get("device_challenges")
|
challenges = self.request.session.get(SESSION_DEVICE_CHALLENGES, [])
|
||||||
if not challenges:
|
stages = self.request.session.get(SESSION_STAGES, [])
|
||||||
LOGGER.debug("Authenticator Validation stage ran without challenges")
|
stage_challenges = []
|
||||||
return self.executor.stage_invalid()
|
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(
|
return AuthenticatorValidationChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
"type": ChallengeTypes.NATIVE.value,
|
||||||
"device_challenges": challenges,
|
"device_challenges": challenges,
|
||||||
|
"configuration_stages": stage_challenges,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,8 +43,8 @@ class AuthenticatorValidateStageTests(FlowTestCase):
|
|||||||
stage = AuthenticatorValidateStage.objects.create(
|
stage = AuthenticatorValidateStage.objects.create(
|
||||||
name="foo",
|
name="foo",
|
||||||
not_configured_action=NotConfiguredAction.CONFIGURE,
|
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")
|
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=conf_stage, order=0)
|
||||||
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
|
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
|
||||||
|
@ -17,7 +17,7 @@ services:
|
|||||||
image: redis:alpine
|
image: redis:alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
server:
|
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
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
@ -38,7 +38,7 @@ services:
|
|||||||
- "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000"
|
- "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000"
|
||||||
- "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
|
- "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
|
||||||
worker:
|
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
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
8
go.mod
8
go.mod
@ -8,16 +8,16 @@ require (
|
|||||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||||
github.com/garyburd/redigo v1.6.2 // indirect
|
github.com/garyburd/redigo v1.6.2 // indirect
|
||||||
github.com/getsentry/sentry-go v0.12.0
|
github.com/getsentry/sentry-go v0.12.0
|
||||||
github.com/go-ldap/ldap/v3 v3.4.1
|
github.com/go-ldap/ldap/v3 v3.4.2
|
||||||
github.com/go-openapi/runtime v0.22.0
|
github.com/go-openapi/runtime v0.23.0
|
||||||
github.com/go-openapi/strfmt v0.21.1
|
github.com/go-openapi/strfmt v0.21.2
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/gorilla/securecookie v1.1.1
|
github.com/gorilla/securecookie v1.1.1
|
||||||
github.com/gorilla/sessions v1.2.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/imdario/mergo v0.3.12
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
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.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.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-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.2 h1:zFZKcXKLqZpFMrMQGHeHWKXbDTdNCmhGY9AK41zPh+8=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.1/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
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.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.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
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.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.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.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
|
||||||
github.com/go-openapi/runtime v0.22.0 h1:vY2D0u807kkcwidaj0YJuq4zyAWQnjLNDpJcVBrUFNs=
|
github.com/go-openapi/runtime v0.23.0 h1:HX6ET2sHCIvaKeDDQoU01CtO1ekg5EkekHSkLTtWXH0=
|
||||||
github.com/go-openapi/runtime v0.22.0/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs=
|
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.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.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
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.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc=
|
||||||
github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk=
|
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.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.2 h1:5NDNgadiX1Vhemth/TH4gCGopWSTdDjxl60H3B7f+os=
|
||||||
github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
|
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.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.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
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 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
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.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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/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.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/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())
|
return fmt.Sprintf("authentik-outpost@%s", FullVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2022.1.5"
|
const VERSION = "2022.2.1"
|
||||||
|
@ -25,7 +25,7 @@ var (
|
|||||||
func RunServer() {
|
func RunServer() {
|
||||||
m := mux.NewRouter()
|
m := mux.NewRouter()
|
||||||
l := log.WithField("logger", "authentik.outpost.metrics")
|
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)
|
rw.WriteHeader(204)
|
||||||
})
|
})
|
||||||
m.Path("/metrics").Handler(promhttp.Handler())
|
m.Path("/metrics").Handler(promhttp.Handler())
|
||||||
|
@ -78,7 +78,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
|||||||
oauth2Config := oauth2.Config{
|
oauth2Config := oauth2.Config{
|
||||||
ClientID: *p.ClientId,
|
ClientID: *p.ClientId,
|
||||||
ClientSecret: *p.ClientSecret,
|
ClientSecret: *p.ClientSecret,
|
||||||
RedirectURL: urlJoin(p.ExternalHost, "/akprox/callback"),
|
RedirectURL: urlJoin(p.ExternalHost, "/outpost.goauthentik.io/callback"),
|
||||||
Endpoint: endpoint.Endpoint,
|
Endpoint: endpoint.Endpoint,
|
||||||
Scopes: p.ScopesToRequest,
|
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)
|
mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
|
||||||
|
|
||||||
// Support /start and /sign_in for backwards compatibility
|
// Support /start and /sign_in for backwards compatibility
|
||||||
mux.HandleFunc("/akprox/start", a.handleRedirect)
|
mux.HandleFunc("/outpost.goauthentik.io/start", a.handleRedirect)
|
||||||
mux.HandleFunc("/akprox/sign_in", a.handleRedirect)
|
mux.HandleFunc("/outpost.goauthentik.io/sign_in", a.handleRedirect)
|
||||||
mux.HandleFunc("/akprox/callback", a.handleCallback)
|
mux.HandleFunc("/outpost.goauthentik.io/callback", a.handleCallback)
|
||||||
mux.HandleFunc("/akprox/sign_out", a.handleSignOut)
|
mux.HandleFunc("/outpost.goauthentik.io/sign_out", a.handleSignOut)
|
||||||
switch *p.Mode {
|
switch *p.Mode {
|
||||||
case api.PROXYMODE_PROXY:
|
case api.PROXYMODE_PROXY:
|
||||||
err = a.configureProxy()
|
err = a.configureProxy()
|
||||||
|
@ -18,7 +18,7 @@ func (a *Application) ErrorPage(rw http.ResponseWriter, r *http.Request, err str
|
|||||||
data := ErrorPageData{
|
data := ErrorPageData{
|
||||||
Title: "Bad Gateway",
|
Title: "Bad Gateway",
|
||||||
Message: "Error proxying to upstream server",
|
Message: "Error proxying to upstream server",
|
||||||
ProxyPrefix: "/akprox",
|
ProxyPrefix: "/outpost.goauthentik.io",
|
||||||
}
|
}
|
||||||
if claims != nil && len(err) > 0 {
|
if claims != nil && len(err) > 0 {
|
||||||
data.Message = err
|
data.Message = err
|
||||||
|
@ -12,15 +12,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (a *Application) configureForward() error {
|
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 {
|
if _, ok := r.URL.Query()["traefik"]; ok {
|
||||||
a.forwardHandleTraefik(rw, r)
|
a.forwardHandleTraefik(rw, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.forwardHandleNginx(rw, r)
|
a.forwardHandleNginx(rw, r)
|
||||||
})
|
})
|
||||||
a.mux.HandleFunc("/akprox/auth/traefik", a.forwardHandleTraefik)
|
a.mux.HandleFunc("/outpost.goauthentik.io/auth/traefik", a.forwardHandleTraefik)
|
||||||
a.mux.HandleFunc("/akprox/auth/nginx", a.forwardHandleNginx)
|
a.mux.HandleFunc("/outpost.goauthentik.io/auth/nginx", a.forwardHandleNginx)
|
||||||
return nil
|
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")
|
a.log.Trace("path can be accessed without authentication")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(r.Header.Get("X-Forwarded-Uri"), "/akprox") {
|
if strings.HasPrefix(r.Header.Get("X-Forwarded-Uri"), "/outpost.goauthentik.io") {
|
||||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /akprox, allowing access")
|
a.log.WithField("url", r.URL.String()).Trace("path begins with /outpost.goauthentik.io, allowing access")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
host := ""
|
host := ""
|
||||||
@ -80,7 +80,7 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
|
|||||||
if proto != "" {
|
if proto != "" {
|
||||||
proto = 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")
|
a.log.WithField("url", rdFinal).Debug("Redirecting to login")
|
||||||
http.Redirect(rw, r, rdFinal, http.StatusTemporaryRedirect)
|
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 fwd.String() != r.URL.String() {
|
||||||
if strings.HasPrefix(fwd.Path, "/akprox") {
|
if strings.HasPrefix(fwd.Path, "/outpost.goauthentik.io") {
|
||||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /akprox, allowing access")
|
a.log.WithField("url", r.URL.String()).Trace("path begins with /outpost.goauthentik.io, allowing access")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
func TestForwardHandleNginx_Single_Blank(t *testing.T) {
|
func TestForwardHandleNginx_Single_Blank(t *testing.T) {
|
||||||
a := newTestApplication()
|
a := newTestApplication()
|
||||||
req, _ := http.NewRequest("GET", "/akprox/auth/nginx", nil)
|
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleNginx(rr, req)
|
a.forwardHandleNginx(rr, req)
|
||||||
@ -22,7 +22,7 @@ func TestForwardHandleNginx_Single_Blank(t *testing.T) {
|
|||||||
|
|
||||||
func TestForwardHandleNginx_Single_Skip(t *testing.T) {
|
func TestForwardHandleNginx_Single_Skip(t *testing.T) {
|
||||||
a := newTestApplication()
|
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")
|
req.Header.Set("X-Original-URL", "http://test.goauthentik.io/skip")
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
@ -33,7 +33,7 @@ func TestForwardHandleNginx_Single_Skip(t *testing.T) {
|
|||||||
|
|
||||||
func TestForwardHandleNginx_Single_Headers(t *testing.T) {
|
func TestForwardHandleNginx_Single_Headers(t *testing.T) {
|
||||||
a := newTestApplication()
|
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")
|
req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app")
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
@ -47,7 +47,7 @@ func TestForwardHandleNginx_Single_Headers(t *testing.T) {
|
|||||||
|
|
||||||
func TestForwardHandleNginx_Single_URI(t *testing.T) {
|
func TestForwardHandleNginx_Single_URI(t *testing.T) {
|
||||||
a := newTestApplication()
|
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")
|
req.Header.Set("X-Original-URI", "/app")
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
@ -61,7 +61,7 @@ func TestForwardHandleNginx_Single_URI(t *testing.T) {
|
|||||||
|
|
||||||
func TestForwardHandleNginx_Single_Claims(t *testing.T) {
|
func TestForwardHandleNginx_Single_Claims(t *testing.T) {
|
||||||
a := newTestApplication()
|
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", "/")
|
req.Header.Set("X-Original-URI", "/")
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
@ -108,7 +108,7 @@ func TestForwardHandleNginx_Domain_Blank(t *testing.T) {
|
|||||||
a := newTestApplication()
|
a := newTestApplication()
|
||||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
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()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleNginx(rr, req)
|
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.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||||
a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io"
|
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")
|
req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app")
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
func TestForwardHandleTraefik_Single_Blank(t *testing.T) {
|
func TestForwardHandleTraefik_Single_Blank(t *testing.T) {
|
||||||
a := newTestApplication()
|
a := newTestApplication()
|
||||||
req, _ := http.NewRequest("GET", "/akprox/auth/traefik", nil)
|
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil)
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleTraefik(rr, req)
|
a.forwardHandleTraefik(rr, req)
|
||||||
@ -22,7 +22,7 @@ func TestForwardHandleTraefik_Single_Blank(t *testing.T) {
|
|||||||
|
|
||||||
func TestForwardHandleTraefik_Single_Skip(t *testing.T) {
|
func TestForwardHandleTraefik_Single_Skip(t *testing.T) {
|
||||||
a := newTestApplication()
|
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-Proto", "http")
|
||||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||||
req.Header.Set("X-Forwarded-Uri", "/skip")
|
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) {
|
func TestForwardHandleTraefik_Single_Headers(t *testing.T) {
|
||||||
a := newTestApplication()
|
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-Proto", "http")
|
||||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||||
req.Header.Set("X-Forwarded-Uri", "/app")
|
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)
|
assert.Equal(t, rr.Code, http.StatusTemporaryRedirect)
|
||||||
loc, _ := rr.Result().Location()
|
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)
|
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
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) {
|
func TestForwardHandleTraefik_Single_Claims(t *testing.T) {
|
||||||
a := newTestApplication()
|
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-Proto", "http")
|
||||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||||
req.Header.Set("X-Forwarded-Uri", "/app")
|
req.Header.Set("X-Forwarded-Uri", "/app")
|
||||||
@ -102,7 +102,7 @@ func TestForwardHandleTraefik_Domain_Blank(t *testing.T) {
|
|||||||
a := newTestApplication()
|
a := newTestApplication()
|
||||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
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()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleTraefik(rr, req)
|
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.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||||
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
a.proxyConfig.CookieDomain = api.PtrString("foo")
|
||||||
a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io"
|
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-Proto", "http")
|
||||||
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
req.Header.Set("X-Forwarded-Host", "test.goauthentik.io")
|
||||||
req.Header.Set("X-Forwarded-Uri", "/app")
|
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)
|
assert.Equal(t, http.StatusTemporaryRedirect, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
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)
|
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
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) {
|
func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) {
|
||||||
return func(r *http.Request) {
|
return func(r *http.Request) {
|
||||||
|
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||||
claims, _ := a.getClaims(r)
|
claims, _ := a.getClaims(r)
|
||||||
r.URL.Scheme = ou.Scheme
|
r.URL.Scheme = ou.Scheme
|
||||||
r.URL.Host = ou.Host
|
r.URL.Host = ou.Host
|
||||||
r.Host = ou.Host
|
|
||||||
if claims != nil && claims.Proxy != nil && claims.Proxy.BackendOverride != "" {
|
if claims != nil && claims.Proxy != nil && claims.Proxy.BackendOverride != "" {
|
||||||
u, err := url.Parse(claims.Proxy.BackendOverride)
|
u, err := url.Parse(claims.Proxy.BackendOverride)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -84,7 +84,6 @@ func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
r.URL.Scheme = u.Scheme
|
r.URL.Scheme = u.Scheme
|
||||||
r.URL.Host = u.Host
|
r.URL.Host = u.Host
|
||||||
r.Host = u.Host
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.log.WithField("upstream_url", r.URL.String()).Trace("final upstream url")
|
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)
|
a.proxyModifyRequest(u)(req)
|
||||||
|
|
||||||
|
assert.Equal(t, "frontend", req.Header.Get("X-Forwarded-Host"))
|
||||||
assert.Equal(t, "/foo", req.URL.Path)
|
assert.Equal(t, "/foo", req.URL.Path)
|
||||||
assert.Equal(t, "backend:8012", req.URL.Host)
|
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) {
|
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, "/foo", req.URL.Path)
|
||||||
assert.Equal(t, "other-backend:8123", req.URL.Host)
|
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) {
|
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, "/foo", req.URL.Path)
|
||||||
assert.Equal(t, "backend:8012", req.URL.Host)
|
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 (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/securecookie"
|
"github.com/gorilla/securecookie"
|
||||||
|
"goauthentik.io/api"
|
||||||
"goauthentik.io/internal/outpost/proxyv2/constants"
|
"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) {
|
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)
|
s, err := a.sessions.Get(r, constants.SeesionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Values[constants.SessionOAuthState] = []string{}
|
s.Values[constants.SessionOAuthState] = []string{}
|
||||||
@ -20,6 +52,11 @@ func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) {
|
|||||||
s.Values[constants.SessionOAuthState] = []string{}
|
s.Values[constants.SessionOAuthState] = []string{}
|
||||||
state = []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)
|
s.Values[constants.SessionOAuthState] = append(state, newState)
|
||||||
err = s.Save(r, rw)
|
err = s.Save(r, rw)
|
||||||
if err != nil {
|
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) {
|
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]
|
state, ok := s.Values[constants.SessionOAuthState]
|
||||||
if !ok {
|
if !ok {
|
||||||
a.log.Warning("No state saved in session")
|
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
|
redirect := a.proxyConfig.ExternalHost
|
||||||
redirectR, ok := s.Values[constants.SessionRedirect]
|
redirectR, ok := s.Values[constants.SessionRedirect]
|
||||||
if ok {
|
if ok {
|
||||||
a.log.WithField("redirect", redirectR).Trace("got final redirect from session")
|
|
||||||
redirect = redirectR.(string)
|
redirect = redirectR.(string)
|
||||||
}
|
}
|
||||||
|
a.log.WithField("redirect", redirect).Trace("final redirect")
|
||||||
http.Redirect(rw, r, redirect, http.StatusFound)
|
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()),
|
ClientId: api.PtrString(ak.TestSecret()),
|
||||||
ClientSecret: api.PtrString(ak.TestSecret()),
|
ClientSecret: api.PtrString(ak.TestSecret()),
|
||||||
CookieSecret: api.PtrString(ak.TestSecret()),
|
CookieSecret: api.PtrString(ak.TestSecret()),
|
||||||
|
ExternalHost: "https://ext.t.goauthentik.io",
|
||||||
CookieDomain: api.PtrString(""),
|
CookieDomain: api.PtrString(""),
|
||||||
Mode: api.PROXYMODE_FORWARD_SINGLE.Ptr(),
|
Mode: api.PROXYMODE_FORWARD_SINGLE.Ptr(),
|
||||||
SkipPathRegex: api.PtrString("/skip.*"),
|
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")
|
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)
|
http.Redirect(rw, r, authUrl, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ func TestRedirectToStart_Proxy(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, http.StatusFound, rr.Code)
|
assert.Equal(t, http.StatusFound, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
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)
|
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||||
assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect])
|
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)
|
assert.Equal(t, http.StatusFound, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
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)
|
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||||
assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect])
|
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)
|
assert.Equal(t, http.StatusFound, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
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)
|
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||||
assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect])
|
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)
|
assert.Equal(t, http.StatusFound, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
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)
|
s, _ := a.sessions.Get(req, constants.SeesionName)
|
||||||
assert.Equal(t, "https://test.goauthentik.io", s.Values[constants.SessionRedirect])
|
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) {
|
func (ps *ProxyServer) HandleStatic(rw http.ResponseWriter, r *http.Request) {
|
||||||
before := time.Now()
|
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)
|
after := time.Since(before)
|
||||||
metrics.Requests.With(prometheus.Labels{
|
metrics.Requests.With(prometheus.Labels{
|
||||||
"outpost_name": ps.akAPI.Outpost.Name,
|
"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) {
|
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)
|
ps.HandleStatic(rw, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(r.URL.Path, "/akprox/ping") {
|
if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io/ping") {
|
||||||
ps.HandlePing(rw, r)
|
ps.HandlePing(rw, r)
|
||||||
return
|
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")
|
ps.log.WithField("host", host).Warning("no app for hostname")
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
@ -25,7 +25,7 @@ var (
|
|||||||
func RunServer() {
|
func RunServer() {
|
||||||
m := mux.NewRouter()
|
m := mux.NewRouter()
|
||||||
l := log.WithField("logger", "authentik.outpost.metrics")
|
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)
|
rw.WriteHeader(204)
|
||||||
})
|
})
|
||||||
m.Path("/metrics").Handler(promhttp.Handler())
|
m.Path("/metrics").Handler(promhttp.Handler())
|
||||||
|
@ -64,8 +64,8 @@ func NewProxyServer(ac *ak.APIController, portOffset int) *ProxyServer {
|
|||||||
akAPI: ac,
|
akAPI: ac,
|
||||||
defaultCert: defaultCert,
|
defaultCert: defaultCert,
|
||||||
}
|
}
|
||||||
globalMux.PathPrefix("/akprox/static").HandlerFunc(s.HandleStatic)
|
globalMux.PathPrefix("/outpost.goauthentik.io/static").HandlerFunc(s.HandleStatic)
|
||||||
globalMux.Path("/akprox/ping").HandlerFunc(s.HandlePing)
|
globalMux.Path("/outpost.goauthentik.io/ping").HandlerFunc(s.HandlePing)
|
||||||
rootMux.PathPrefix("/").HandlerFunc(s.Handle)
|
rootMux.PathPrefix("/").HandlerFunc(s.Handle)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,13 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
<title>{{.Title}}</title>
|
<title>{{.Title}}</title>
|
||||||
<link rel="shortcut icon" type="image/png" href="/akprox/static/dist/assets/icons/icon.png">
|
<link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/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="/outpost.goauthentik.io/static/dist/patternfly.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/akprox/static/dist/authentik.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>
|
<style>
|
||||||
.pf-c-background-image::before {
|
.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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@ -32,7 +33,7 @@
|
|||||||
<div class="ak-login-container">
|
<div class="ak-login-container">
|
||||||
<header class="pf-c-login__header">
|
<header class="pf-c-login__header">
|
||||||
<div class="pf-c-brand ak-brand">
|
<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>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="pf-c-login__main">
|
<main class="pf-c-login__main">
|
||||||
|
@ -3,7 +3,8 @@ package templates
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed error.html
|
//go:embed error.html
|
||||||
|
@ -30,7 +30,7 @@ func (ws *WebServer) configureProxy() {
|
|||||||
rp := &httputil.ReverseProxy{Director: director}
|
rp := &httputil.ReverseProxy{Director: director}
|
||||||
rp.ErrorHandler = ws.proxyErrorHandler
|
rp.ErrorHandler = ws.proxyErrorHandler
|
||||||
rp.ModifyResponse = ws.proxyModifyResponse
|
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 {
|
if ws.ProxyServer != nil {
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
ws.ProxyServer.Handle(rw, r)
|
ws.ProxyServer.Handle(rw, r)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Stage 1: Build
|
# 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
|
WORKDIR /go/src/goauthentik.io
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
|||||||
|
|
||||||
COPY --from=builder /go/ldap /
|
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
|
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
|
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"
|
MODE_FILE="/tmp/authentik-mode"
|
||||||
|
|
||||||
if [[ "$1" == "server" ]]; then
|
if [[ "$1" == "server" ]]; then
|
||||||
@ -75,12 +51,6 @@ elif [[ "$1" == "worker" ]]; then
|
|||||||
elif [[ "$1" == "flower" ]]; then
|
elif [[ "$1" == "flower" ]]; then
|
||||||
echo "flower" > $MODE_FILE
|
echo "flower" > $MODE_FILE
|
||||||
celery -A authentik.root.celery flower
|
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
|
elif [[ "$1" == "bash" ]]; then
|
||||||
/bin/bash
|
/bin/bash
|
||||||
elif [[ "$1" == "test" ]]; then
|
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."
|
"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()
|
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
|
RUN cd /static && npm i && npm run build-proxy
|
||||||
|
|
||||||
# Stage 2: Build
|
# 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
|
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/dist/ /web/dist/
|
||||||
COPY --from=web-builder /static/authentik/ /web/authentik/
|
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
|
EXPOSE 9000 9300 9443
|
||||||
|
|
||||||
|
@ -92,12 +92,11 @@ addopts = "-p no:celery --junitxml=unittest.xml"
|
|||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "authentik"
|
name = "authentik"
|
||||||
version = "2022.1.5"
|
version = "2022.2.1"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Jens Langhammer <jens.langhammer@beryju.org>"]
|
authors = ["Jens Langhammer <jens.langhammer@beryju.org>"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
boto3 = "*"
|
|
||||||
celery = "*"
|
celery = "*"
|
||||||
channels = "*"
|
channels = "*"
|
||||||
channels-redis = "*"
|
channels-redis = "*"
|
||||||
@ -107,14 +106,12 @@ dacite = "*"
|
|||||||
deepmerge = "*"
|
deepmerge = "*"
|
||||||
defusedxml = "*"
|
defusedxml = "*"
|
||||||
django = "*"
|
django = "*"
|
||||||
django-dbbackup = "=4.0.0b0"
|
|
||||||
django-filter = "*"
|
django-filter = "*"
|
||||||
django-guardian = "*"
|
django-guardian = "*"
|
||||||
django-model-utils = "*"
|
django-model-utils = "*"
|
||||||
django-otp = "*"
|
django-otp = "*"
|
||||||
django-prometheus = "*"
|
django-prometheus = "*"
|
||||||
django-redis = "*"
|
django-redis = "*"
|
||||||
django-storages = "*"
|
|
||||||
djangorestframework = "*"
|
djangorestframework = "*"
|
||||||
djangorestframework-guardian = "*"
|
djangorestframework-guardian = "*"
|
||||||
docker = "*"
|
docker = "*"
|
||||||
|
56
schema.yml
56
schema.yml
@ -1,7 +1,7 @@
|
|||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: authentik
|
title: authentik
|
||||||
version: 2022.1.5
|
version: 2022.2.1
|
||||||
description: Making authentication simple.
|
description: Making authentication simple.
|
||||||
contact:
|
contact:
|
||||||
email: hello@beryju.org
|
email: hello@beryju.org
|
||||||
@ -15045,10 +15045,14 @@ paths:
|
|||||||
description: AuthenticatorValidateStage Viewset
|
description: AuthenticatorValidateStage Viewset
|
||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- in: query
|
||||||
name: configuration_stage
|
name: configuration_stages
|
||||||
schema:
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
explode: true
|
||||||
|
style: form
|
||||||
- in: query
|
- in: query
|
||||||
name: name
|
name: name
|
||||||
schema:
|
schema:
|
||||||
@ -19826,11 +19830,12 @@ components:
|
|||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/DeviceClassesEnum'
|
$ref: '#/components/schemas/DeviceClassesEnum'
|
||||||
description: Device classes which can be used to authenticate
|
description: Device classes which can be used to authenticate
|
||||||
configuration_stage:
|
configuration_stages:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
description: Stages used to configure Authenticator when user doesn't have
|
||||||
description: Stage used to configure Authenticator when user doesn't have
|
|
||||||
any compatible devices. After this configuration Stage passes, the user
|
any compatible devices. After this configuration Stage passes, the user
|
||||||
is not prompted again.
|
is not prompted again.
|
||||||
required:
|
required:
|
||||||
@ -19858,11 +19863,12 @@ components:
|
|||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/DeviceClassesEnum'
|
$ref: '#/components/schemas/DeviceClassesEnum'
|
||||||
description: Device classes which can be used to authenticate
|
description: Device classes which can be used to authenticate
|
||||||
configuration_stage:
|
configuration_stages:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
description: Stages used to configure Authenticator when user doesn't have
|
||||||
description: Stage used to configure Authenticator when user doesn't have
|
|
||||||
any compatible devices. After this configuration Stage passes, the user
|
any compatible devices. After this configuration Stage passes, the user
|
||||||
is not prompted again.
|
is not prompted again.
|
||||||
required:
|
required:
|
||||||
@ -19892,7 +19898,12 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/DeviceChallenge'
|
$ref: '#/components/schemas/DeviceChallenge'
|
||||||
|
configuration_stages:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/SelectableStage'
|
||||||
required:
|
required:
|
||||||
|
- configuration_stages
|
||||||
- device_challenges
|
- device_challenges
|
||||||
- pending_user
|
- pending_user
|
||||||
- pending_user_avatar
|
- pending_user_avatar
|
||||||
@ -19907,6 +19918,9 @@ components:
|
|||||||
default: ak-stage-authenticator-validate
|
default: ak-stage-authenticator-validate
|
||||||
selected_challenge:
|
selected_challenge:
|
||||||
$ref: '#/components/schemas/DeviceChallengeRequest'
|
$ref: '#/components/schemas/DeviceChallengeRequest'
|
||||||
|
selected_stage:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
code:
|
code:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
@ -20017,7 +20031,6 @@ components:
|
|||||||
enum:
|
enum:
|
||||||
- can_save_media
|
- can_save_media
|
||||||
- can_geo_ip
|
- can_geo_ip
|
||||||
- can_backup
|
|
||||||
type: string
|
type: string
|
||||||
CaptchaChallenge:
|
CaptchaChallenge:
|
||||||
type: object
|
type: object
|
||||||
@ -26678,11 +26691,12 @@ components:
|
|||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/DeviceClassesEnum'
|
$ref: '#/components/schemas/DeviceClassesEnum'
|
||||||
description: Device classes which can be used to authenticate
|
description: Device classes which can be used to authenticate
|
||||||
configuration_stage:
|
configuration_stages:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
description: Stages used to configure Authenticator when user doesn't have
|
||||||
description: Stage used to configure Authenticator when user doesn't have
|
|
||||||
any compatible devices. After this configuration Stage passes, the user
|
any compatible devices. After this configuration Stage passes, the user
|
||||||
is not prompted again.
|
is not prompted again.
|
||||||
PatchedCaptchaStageRequest:
|
PatchedCaptchaStageRequest:
|
||||||
@ -30018,6 +30032,24 @@ components:
|
|||||||
- direct
|
- direct
|
||||||
- cached
|
- cached
|
||||||
type: string
|
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:
|
ServiceConnection:
|
||||||
type: object
|
type: object
|
||||||
description: ServiceConnection Serializer
|
description: ServiceConnection Serializer
|
||||||
|
@ -105,7 +105,7 @@ class TestProviderProxy(SeleniumTestCase):
|
|||||||
self.assertIn(f"X-Authentik-Username: {self.user.username}", full_body_text)
|
self.assertIn(f"X-Authentik-Username: {self.user.username}", full_body_text)
|
||||||
self.assertIn("X-Foo: bar", 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)
|
sleep(2)
|
||||||
full_body_text = self.driver.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text
|
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)
|
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",
|
"fr_FR",
|
||||||
"tr",
|
"tr",
|
||||||
"es",
|
"es",
|
||||||
"pl"
|
"pl",
|
||||||
|
"zh_TW",
|
||||||
|
"zh-Hans",
|
||||||
|
"zh-Hant",
|
||||||
|
"de"
|
||||||
],
|
],
|
||||||
"formatOptions": {
|
"formatOptions": {
|
||||||
"lineNumbers": false
|
"lineNumbers": false
|
||||||
@ -48,14 +52,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.16.12",
|
"@babel/core": "^7.17.4",
|
||||||
"@babel/plugin-proposal-decorators": "^7.16.7",
|
"@babel/plugin-proposal-decorators": "^7.17.2",
|
||||||
"@babel/plugin-transform-runtime": "^7.16.10",
|
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||||
"@babel/preset-env": "^7.16.11",
|
"@babel/preset-env": "^7.16.11",
|
||||||
"@babel/preset-typescript": "^7.16.7",
|
"@babel/preset-typescript": "^7.16.7",
|
||||||
"@formatjs/intl-listformat": "^6.5.1",
|
"@formatjs/intl-listformat": "^6.5.2",
|
||||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
"@fortawesome/fontawesome-free": "^6.0.0",
|
||||||
"@goauthentik/api": "^2022.1.3-1643236150",
|
"@goauthentik/api": "^2022.1.5-1644681372",
|
||||||
"@jackfranklin/rollup-plugin-markdown": "^0.3.0",
|
"@jackfranklin/rollup-plugin-markdown": "^0.3.0",
|
||||||
"@lingui/cli": "^3.13.2",
|
"@lingui/cli": "^3.13.2",
|
||||||
"@lingui/core": "^3.13.2",
|
"@lingui/core": "^3.13.2",
|
||||||
@ -67,36 +71,36 @@
|
|||||||
"@rollup/plugin-babel": "^5.3.0",
|
"@rollup/plugin-babel": "^5.3.0",
|
||||||
"@rollup/plugin-commonjs": "^21.0.1",
|
"@rollup/plugin-commonjs": "^21.0.1",
|
||||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
"@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",
|
"@rollup/plugin-typescript": "^8.3.0",
|
||||||
"@sentry/browser": "^6.17.3",
|
"@sentry/browser": "^6.17.8",
|
||||||
"@sentry/tracing": "^6.17.3",
|
"@sentry/tracing": "^6.17.8",
|
||||||
"@squoosh/cli": "^0.7.2",
|
"@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/chart.js": "^2.9.35",
|
||||||
"@types/codemirror": "5.60.5",
|
"@types/codemirror": "5.60.5",
|
||||||
"@types/grecaptcha": "^3.0.3",
|
"@types/grecaptcha": "^3.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
||||||
"@typescript-eslint/parser": "^5.10.2",
|
"@typescript-eslint/parser": "^5.12.0",
|
||||||
"@webcomponents/webcomponentsjs": "^2.6.0",
|
"@webcomponents/webcomponentsjs": "^2.6.0",
|
||||||
"babel-plugin-macros": "^3.1.0",
|
"babel-plugin-macros": "^3.1.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"chart.js": "^3.7.0",
|
"chart.js": "^3.7.1",
|
||||||
"chartjs-adapter-moment": "^1.0.0",
|
"chartjs-adapter-moment": "^1.0.0",
|
||||||
"codemirror": "^5.65.1",
|
"codemirror": "^5.65.1",
|
||||||
"construct-style-sheets-polyfill": "^3.1.0",
|
"construct-style-sheets-polyfill": "^3.1.0",
|
||||||
"country-flag-icons": "^1.4.20",
|
"country-flag-icons": "^1.4.21",
|
||||||
"eslint": "^8.8.0",
|
"eslint": "^8.9.0",
|
||||||
"eslint-config-google": "^0.14.0",
|
"eslint-config-google": "^0.14.0",
|
||||||
"eslint-plugin-custom-elements": "0.0.4",
|
"eslint-plugin-custom-elements": "0.0.4",
|
||||||
"eslint-plugin-lit": "^1.6.1",
|
"eslint-plugin-lit": "^1.6.1",
|
||||||
"flowchart.js": "^1.17.0",
|
"flowchart.js": "^1.17.1",
|
||||||
"fuse.js": "^6.5.3",
|
"fuse.js": "^6.5.3",
|
||||||
"lit": "^2.1.2",
|
"lit": "^2.1.4",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"rapidoc": "^9.1.4",
|
"rapidoc": "^9.1.4",
|
||||||
"rollup": "^2.66.1",
|
"rollup": "^2.67.2",
|
||||||
"rollup-plugin-copy": "^3.4.0",
|
"rollup-plugin-copy": "^3.4.0",
|
||||||
"rollup-plugin-cssimport": "^1.0.2",
|
"rollup-plugin-cssimport": "^1.0.2",
|
||||||
"rollup-plugin-minify-html-literals": "^1.2.6",
|
"rollup-plugin-minify-html-literals": "^1.2.6",
|
||||||
|
@ -34,6 +34,7 @@ export const resources = [
|
|||||||
dest: "dist/",
|
dest: "dist/",
|
||||||
},
|
},
|
||||||
{ src: "src/authentik.css", dest: "dist/" },
|
{ src: "src/authentik.css", dest: "dist/" },
|
||||||
|
{ src: "src/custom.css", dest: "dist/" },
|
||||||
|
|
||||||
{
|
{
|
||||||
src: "node_modules/@patternfly/patternfly/assets/*",
|
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 ERROR_CLASS = "pf-m-danger";
|
||||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||||
export const CURRENT_CLASS = "pf-m-current";
|
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 TITLE_DEFAULT = "authentik";
|
||||||
export const ROUTE_SEPARATOR = ";";
|
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`
|
? html`
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a href="https://unsplash.com/@trime"
|
||||||
href="https://unsplash.com/@kimonmaritz"
|
|
||||||
>${t`Background image`}</a
|
>${t`Background image`}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
@ -67,7 +67,7 @@ export class AuthenticatorValidateStage
|
|||||||
return this._selectedDeviceChallenge;
|
return this._selectedDeviceChallenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<void> {
|
submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<boolean> {
|
||||||
return this.host?.submit(payload) || Promise.resolve();
|
return this.host?.submit(payload) || Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +157,30 @@ export class AuthenticatorValidateStage
|
|||||||
</ul>`;
|
</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 {
|
renderDeviceChallenge(): TemplateResult {
|
||||||
if (!this.selectedDeviceChallenge) {
|
if (!this.selectedDeviceChallenge) {
|
||||||
return html``;
|
return html``;
|
||||||
@ -242,6 +266,9 @@ export class AuthenticatorValidateStage
|
|||||||
${this.selectedDeviceChallenge
|
${this.selectedDeviceChallenge
|
||||||
? ""
|
? ""
|
||||||
: html`<p>${t`Select an authentication method.`}</p>`}
|
: html`<p>${t`Select an authentication method.`}</p>`}
|
||||||
|
${this.challenge.configurationStages.length > 0
|
||||||
|
? this.renderStagePicker()
|
||||||
|
: html``}
|
||||||
</form>
|
</form>
|
||||||
${this.renderDevicePicker()}
|
${this.renderDevicePicker()}
|
||||||
</div>
|
</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 { Messages, i18n } from "@lingui/core";
|
||||||
import { detect, fromNavigator, fromStorage, fromUrl } from "@lingui/detect-locale";
|
import { detect, fromNavigator, fromStorage, fromUrl } from "@lingui/detect-locale";
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
|
|
||||||
|
import { messages as localeDE } from "../locales/de";
|
||||||
import { messages as localeEN } from "../locales/en";
|
import { messages as localeEN } from "../locales/en";
|
||||||
import { messages as localeES } from "../locales/es";
|
import { messages as localeES } from "../locales/es";
|
||||||
import { messages as localeFR_FR } from "../locales/fr_FR";
|
import { messages as localeFR_FR } from "../locales/fr_FR";
|
||||||
import { messages as localePL } from "../locales/pl";
|
import { messages as localePL } from "../locales/pl";
|
||||||
import { messages as localeDEBUG } from "../locales/pseudo-LOCALE";
|
import { messages as localeDEBUG } from "../locales/pseudo-LOCALE";
|
||||||
import { messages as localeTR } from "../locales/tr";
|
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: {
|
export const LOCALES: {
|
||||||
code: string;
|
code: string;
|
||||||
@ -54,6 +58,30 @@ export const LOCALES: {
|
|||||||
label: t`Polish`,
|
label: t`Polish`,
|
||||||
locale: localePL,
|
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) => {
|
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."
|
msgstr "Background shown during execution."
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||||
msgid "Backup finished with errors."
|
#~ msgid "Backup finished with errors."
|
||||||
msgstr "Backup finished with errors."
|
#~ msgstr "Backup finished with errors."
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||||
msgid "Backup finished with warnings/backup not supported."
|
#~ msgid "Backup finished with warnings/backup not supported."
|
||||||
msgstr "Backup finished with warnings/backup not supported."
|
#~ msgstr "Backup finished with warnings/backup not supported."
|
||||||
|
|
||||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||||
msgid "Backup status"
|
#~ msgid "Backup status"
|
||||||
msgstr "Backup status"
|
#~ msgstr "Backup status"
|
||||||
|
|
||||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||||
#: src/pages/providers/ldap/LDAPProviderViewPage.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."
|
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."
|
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
|
#: src/pages/flows/FlowListPage.ts
|
||||||
msgid "Clear Flow cache"
|
msgid "Clear Flow cache"
|
||||||
msgstr "Clear Flow cache"
|
msgstr "Clear Flow cache"
|
||||||
@ -948,8 +956,12 @@ msgid "Configuration flow"
|
|||||||
msgstr "Configuration flow"
|
msgstr "Configuration flow"
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||||
msgid "Configuration stage"
|
#~ msgid "Configuration stage"
|
||||||
msgstr "Configuration stage"
|
#~ msgstr "Configuration stage"
|
||||||
|
|
||||||
|
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||||
|
msgid "Configuration stages"
|
||||||
|
msgstr "Configuration stages"
|
||||||
|
|
||||||
#:
|
#:
|
||||||
#~ msgid "Configure WebAuthn"
|
#~ msgid "Configure WebAuthn"
|
||||||
@ -2205,6 +2217,10 @@ msgstr "Generic"
|
|||||||
msgid "Generic OpenID Connect"
|
msgid "Generic OpenID Connect"
|
||||||
msgstr "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
|
||||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||||
msgid "Get this value from https://console.twilio.com"
|
msgid "Get this value from https://console.twilio.com"
|
||||||
@ -4483,8 +4499,8 @@ msgid "Stage type"
|
|||||||
msgstr "Stage type"
|
msgstr "Stage type"
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: 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."
|
#~ 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."
|
#~ 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
|
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
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."
|
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."
|
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
|
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "State"
|
msgstr "State"
|
||||||
@ -5014,6 +5034,10 @@ msgstr "TOTP Device"
|
|||||||
msgid "TOTP authenticator"
|
msgid "TOTP authenticator"
|
||||||
msgstr "TOTP authenticator"
|
msgstr "TOTP authenticator"
|
||||||
|
|
||||||
|
#: src/interfaces/locale.ts
|
||||||
|
msgid "Taiwanese Mandarin"
|
||||||
|
msgstr "Taiwanese Mandarin"
|
||||||
|
|
||||||
#: src/pages/flows/StageBindingForm.ts
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
msgid "Target"
|
msgid "Target"
|
||||||
msgstr "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."
|
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
|
#: 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 "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, /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
|
#: 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."
|
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."
|
msgid "When enabled, user fields are matched regardless of their casing."
|
||||||
msgstr "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
|
#: 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."
|
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."
|
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."
|
msgstr "Se muestra el fondo durante la ejecución."
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||||
msgid "Backup finished with errors."
|
#~ msgid "Backup finished with errors."
|
||||||
msgstr "La copia de seguridad terminó con errores."
|
#~ msgstr "La copia de seguridad terminó con errores."
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||||
msgid "Backup finished with warnings/backup not supported."
|
#~ msgid "Backup finished with warnings/backup not supported."
|
||||||
msgstr "La copia de seguridad finalizó con advertencias o copias de seguridad no compatibles"
|
#~ msgstr "La copia de seguridad finalizó con advertencias o copias de seguridad no compatibles"
|
||||||
|
|
||||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||||
msgid "Backup status"
|
#~ msgid "Backup status"
|
||||||
msgstr "Estado de respaldo"
|
#~ msgstr "Estado de respaldo"
|
||||||
|
|
||||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||||
#: src/pages/providers/ldap/LDAPProviderViewPage.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."
|
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."
|
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
|
#: src/pages/flows/FlowListPage.ts
|
||||||
msgid "Clear Flow cache"
|
msgid "Clear Flow cache"
|
||||||
msgstr "Borrar caché de flujo"
|
msgstr "Borrar caché de flujo"
|
||||||
@ -942,8 +950,12 @@ msgid "Configuration flow"
|
|||||||
msgstr "Flujo de configuración"
|
msgstr "Flujo de configuración"
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||||
msgid "Configuration stage"
|
#~ msgid "Configuration stage"
|
||||||
msgstr "Etapa de configuración"
|
#~ msgstr "Etapa de configuración"
|
||||||
|
|
||||||
|
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||||
|
msgid "Configuration stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#~ msgid "Configure WebAuthn"
|
#~ msgid "Configure WebAuthn"
|
||||||
#~ msgstr "Configurar WebAuthn"
|
#~ msgstr "Configurar WebAuthn"
|
||||||
@ -2166,6 +2178,10 @@ msgstr "Genérico"
|
|||||||
msgid "Generic OpenID Connect"
|
msgid "Generic OpenID Connect"
|
||||||
msgstr "Conexión OpenID genérica"
|
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
|
||||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||||
msgid "Get this value from https://console.twilio.com"
|
msgid "Get this value from https://console.twilio.com"
|
||||||
@ -4391,8 +4407,8 @@ msgid "Stage type"
|
|||||||
msgstr "Tipo de escenario"
|
msgstr "Tipo de escenario"
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: 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."
|
#~ 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."
|
#~ 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
|
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
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."
|
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."
|
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
|
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Estado"
|
msgstr "Estado"
|
||||||
@ -4905,6 +4925,10 @@ msgstr "Dispositivo TOTP"
|
|||||||
msgid "TOTP authenticator"
|
msgid "TOTP authenticator"
|
||||||
msgstr "Autenticador TOTP"
|
msgstr "Autenticador TOTP"
|
||||||
|
|
||||||
|
#: src/interfaces/locale.ts
|
||||||
|
msgid "Taiwanese Mandarin"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/flows/StageBindingForm.ts
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
msgid "Target"
|
msgid "Target"
|
||||||
msgstr "Objetivo"
|
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."
|
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
|
#: 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 "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)."
|
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
|
#: 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."
|
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."
|
msgid "When enabled, user fields are matched regardless of their casing."
|
||||||
msgstr "Cuando se habilita, los campos de usuario coinciden independientemente de su carcasa."
|
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
|
#: 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."
|
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."
|
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."
|
msgstr "Arrière-plan utilisé durant l'exécution."
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||||
msgid "Backup finished with errors."
|
#~ msgid "Backup finished with errors."
|
||||||
msgstr "Sauvegarde terminée avec des erreurs."
|
#~ msgstr "Sauvegarde terminée avec des erreurs."
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||||
msgid "Backup finished with warnings/backup not supported."
|
#~ msgid "Backup finished with warnings/backup not supported."
|
||||||
msgstr "Sauvegarde terminée avec avertissements/sauvegarde non supportée."
|
#~ msgstr "Sauvegarde terminée avec avertissements/sauvegarde non supportée."
|
||||||
|
|
||||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||||
msgid "Backup status"
|
#~ msgid "Backup status"
|
||||||
msgstr "État de la sauvegarde"
|
#~ msgstr "État de la sauvegarde"
|
||||||
|
|
||||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||||
#: src/pages/providers/ldap/LDAPProviderViewPage.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."
|
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."
|
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
|
#: src/pages/flows/FlowListPage.ts
|
||||||
msgid "Clear Flow cache"
|
msgid "Clear Flow cache"
|
||||||
msgstr "Vider le cache des flux"
|
msgstr "Vider le cache des flux"
|
||||||
@ -950,8 +958,12 @@ msgid "Configuration flow"
|
|||||||
msgstr "Flux de configuration"
|
msgstr "Flux de configuration"
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||||
msgid "Configuration stage"
|
#~ msgid "Configuration stage"
|
||||||
msgstr "Étape de configuration"
|
#~ msgstr "Étape de configuration"
|
||||||
|
|
||||||
|
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||||
|
msgid "Configuration stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#:
|
#:
|
||||||
#~ msgid "Configure WebAuthn"
|
#~ msgid "Configure WebAuthn"
|
||||||
@ -2191,6 +2203,10 @@ msgstr ""
|
|||||||
msgid "Generic OpenID Connect"
|
msgid "Generic OpenID Connect"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/interfaces/locale.ts
|
||||||
|
msgid "German"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||||
msgid "Get this value from https://console.twilio.com"
|
msgid "Get this value from https://console.twilio.com"
|
||||||
@ -4444,8 +4460,8 @@ msgid "Stage type"
|
|||||||
msgstr "Type d'étape"
|
msgstr "Type d'étape"
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: 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."
|
#~ 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é."
|
#~ 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
|
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
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."
|
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."
|
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
|
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "État"
|
msgstr "État"
|
||||||
@ -4969,6 +4989,10 @@ msgstr ""
|
|||||||
msgid "TOTP authenticator"
|
msgid "TOTP authenticator"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/interfaces/locale.ts
|
||||||
|
msgid "Taiwanese Mandarin"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/flows/StageBindingForm.ts
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
msgid "Target"
|
msgid "Target"
|
||||||
msgstr "Cible"
|
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."
|
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
|
#: 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 "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)."
|
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
|
#: 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."
|
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."
|
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."
|
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
|
#: 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."
|
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."
|
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."
|
msgid "Background shown during execution."
|
||||||
msgstr "Tło pokazywane podczas wykonywania."
|
msgstr "Tło pokazywane podczas wykonywania."
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#~ msgid "Backup finished with errors."
|
||||||
msgid "Backup finished with errors."
|
#~ msgstr "Kopia zapasowa zakończona z błędami."
|
||||||
msgstr "Kopia zapasowa zakończona z błędami."
|
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#~ msgid "Backup finished with warnings/backup not supported."
|
||||||
msgid "Backup finished with warnings/backup not supported."
|
#~ msgstr "Tworzenie kopii zapasowej zakończone z ostrzeżeniami/kopia zapasowa nie jest obsługiwana."
|
||||||
msgstr "Tworzenie kopii zapasowej zakończone z ostrzeżeniami/kopia zapasowa nie jest obsługiwana."
|
|
||||||
|
|
||||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
#~ msgid "Backup status"
|
||||||
msgid "Backup status"
|
#~ msgstr "Stan kopii zapasowej"
|
||||||
msgstr "Stan kopii zapasowej"
|
|
||||||
|
|
||||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||||
#: src/pages/providers/ldap/LDAPProviderViewPage.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."
|
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."
|
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
|
#: src/pages/flows/FlowListPage.ts
|
||||||
msgid "Clear Flow cache"
|
msgid "Clear Flow cache"
|
||||||
msgstr "Wyczyść pamięć podręczną przepływu"
|
msgstr "Wyczyść pamięć podręczną przepływu"
|
||||||
@ -942,8 +947,12 @@ msgid "Configuration flow"
|
|||||||
msgstr "Przepływ konfiguracji"
|
msgstr "Przepływ konfiguracji"
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||||
msgid "Configuration stage"
|
#~ msgid "Configuration stage"
|
||||||
msgstr "Etap konfiguracji"
|
#~ msgstr "Etap konfiguracji"
|
||||||
|
|
||||||
|
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||||
|
msgid "Configuration stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#~ msgid "Configure WebAuthn"
|
#~ msgid "Configure WebAuthn"
|
||||||
#~ msgstr "Skonfiguruj WebAuthn"
|
#~ msgstr "Skonfiguruj WebAuthn"
|
||||||
@ -2166,6 +2175,10 @@ msgstr "Ogólny"
|
|||||||
msgid "Generic OpenID Connect"
|
msgid "Generic OpenID Connect"
|
||||||
msgstr "Ogólny 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
|
||||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||||
msgid "Get this value from https://console.twilio.com"
|
msgid "Get this value from https://console.twilio.com"
|
||||||
@ -4391,8 +4404,8 @@ msgid "Stage type"
|
|||||||
msgstr "Typ etapu"
|
msgstr "Typ etapu"
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: 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."
|
#~ 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."
|
#~ 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
|
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
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."
|
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."
|
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
|
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Stan"
|
msgstr "Stan"
|
||||||
@ -4905,6 +4922,10 @@ msgstr "Urządzenie TOTP"
|
|||||||
msgid "TOTP authenticator"
|
msgid "TOTP authenticator"
|
||||||
msgstr "Uwierzytelniacz TOTP"
|
msgstr "Uwierzytelniacz TOTP"
|
||||||
|
|
||||||
|
#: src/interfaces/locale.ts
|
||||||
|
msgid "Taiwanese Mandarin"
|
||||||
|
msgstr "Tajwański mandaryński"
|
||||||
|
|
||||||
#: src/pages/flows/StageBindingForm.ts
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
msgid "Target"
|
msgid "Target"
|
||||||
msgstr "Cel"
|
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."
|
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
|
#: 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 "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)."
|
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
|
#: 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."
|
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."
|
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."
|
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
|
#: 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."
|
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."
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||||
msgid "Backup finished with errors."
|
#~ msgid "Backup finished with errors."
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||||
msgid "Backup finished with warnings/backup not supported."
|
#~ msgid "Backup finished with warnings/backup not supported."
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||||
msgid "Backup status"
|
#~ msgid "Backup status"
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||||
#: src/pages/providers/ldap/LDAPProviderViewPage.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."
|
msgid "Checks the value from the policy request against several rules, mostly used to ensure password strength."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/interfaces/locale.ts
|
||||||
|
msgid "Chinese (simplified)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/interfaces/locale.ts
|
||||||
|
msgid "Chinese (traditional)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/flows/FlowListPage.ts
|
#: src/pages/flows/FlowListPage.ts
|
||||||
msgid "Clear Flow cache"
|
msgid "Clear Flow cache"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -942,7 +950,11 @@ msgid "Configuration flow"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#:
|
#:
|
||||||
@ -2197,6 +2209,10 @@ msgstr ""
|
|||||||
msgid "Generic OpenID Connect"
|
msgid "Generic OpenID Connect"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/interfaces/locale.ts
|
||||||
|
msgid "German"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||||
msgid "Get this value from https://console.twilio.com"
|
msgid "Get this value from https://console.twilio.com"
|
||||||
@ -4473,8 +4489,8 @@ msgid "Stage type"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: 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."
|
#~ 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 ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
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."
|
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 ""
|
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
|
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5004,6 +5024,10 @@ msgstr ""
|
|||||||
msgid "TOTP authenticator"
|
msgid "TOTP authenticator"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/interfaces/locale.ts
|
||||||
|
msgid "Taiwanese Mandarin"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/flows/StageBindingForm.ts
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
msgid "Target"
|
msgid "Target"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5653,7 +5677,7 @@ msgid "Use the username and password below to authenticate. The password can be
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
#: 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||||
@ -6016,6 +6040,10 @@ msgstr ""
|
|||||||
msgid "When enabled, user fields are matched regardless of their casing."
|
msgid "When enabled, user fields are matched regardless of their casing."
|
||||||
msgstr ""
|
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
|
#: 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."
|
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -561,16 +561,16 @@ msgid "Background shown during execution."
|
|||||||
msgstr "Yürütme sırasında arka plan gösterilir."
|
msgstr "Yürütme sırasında arka plan gösterilir."
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||||
msgid "Backup finished with errors."
|
#~ msgid "Backup finished with errors."
|
||||||
msgstr "Yedekleme hatalarla tamamlandı."
|
#~ msgstr "Yedekleme hatalarla tamamlandı."
|
||||||
|
|
||||||
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
#: src/pages/admin-overview/cards/BackupStatusCard.ts
|
||||||
msgid "Backup finished with warnings/backup not supported."
|
#~ msgid "Backup finished with warnings/backup not supported."
|
||||||
msgstr "Yedekleme desteklenmeyen uyarılar/yedekleme ile tamamlandı."
|
#~ msgstr "Yedekleme desteklenmeyen uyarılar/yedekleme ile tamamlandı."
|
||||||
|
|
||||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||||
msgid "Backup status"
|
#~ msgid "Backup status"
|
||||||
msgstr "Yedekleme durumu"
|
#~ msgstr "Yedekleme durumu"
|
||||||
|
|
||||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||||
#: src/pages/providers/ldap/LDAPProviderViewPage.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."
|
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."
|
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
|
#: src/pages/flows/FlowListPage.ts
|
||||||
msgid "Clear Flow cache"
|
msgid "Clear Flow cache"
|
||||||
msgstr "Akış önbelleğini temizleme"
|
msgstr "Akış önbelleğini temizleme"
|
||||||
@ -942,8 +950,12 @@ msgid "Configuration flow"
|
|||||||
msgstr "Yapılandırma akışı"
|
msgstr "Yapılandırma akışı"
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||||
msgid "Configuration stage"
|
#~ msgid "Configuration stage"
|
||||||
msgstr "Yapılandırma aşamasında"
|
#~ msgstr "Yapılandırma aşamasında"
|
||||||
|
|
||||||
|
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||||
|
msgid "Configuration stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#~ msgid "Configure WebAuthn"
|
#~ msgid "Configure WebAuthn"
|
||||||
#~ msgstr "WebAuthn'i Yapılandır"
|
#~ msgstr "WebAuthn'i Yapılandır"
|
||||||
@ -2166,6 +2178,10 @@ msgstr "Jenerik"
|
|||||||
msgid "Generic OpenID Connect"
|
msgid "Generic OpenID Connect"
|
||||||
msgstr "Genel 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
|
||||||
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
|
||||||
msgid "Get this value from https://console.twilio.com"
|
msgid "Get this value from https://console.twilio.com"
|
||||||
@ -4393,8 +4409,8 @@ msgid "Stage type"
|
|||||||
msgstr "Aşama türü"
|
msgstr "Aşama türü"
|
||||||
|
|
||||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
#: 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."
|
#~ 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."
|
#~ 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
|
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||||
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
|
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."
|
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."
|
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
|
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Eyalet"
|
msgstr "Eyalet"
|
||||||
@ -4907,6 +4927,10 @@ msgstr "TOTP Cihazı"
|
|||||||
msgid "TOTP authenticator"
|
msgid "TOTP authenticator"
|
||||||
msgstr "TOTP kimlik doğrulayıcı"
|
msgstr "TOTP kimlik doğrulayıcı"
|
||||||
|
|
||||||
|
#: src/interfaces/locale.ts
|
||||||
|
msgid "Taiwanese Mandarin"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/flows/StageBindingForm.ts
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
msgid "Target"
|
msgid "Target"
|
||||||
msgstr "Hedef"
|
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."
|
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
|
#: 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 "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)."
|
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
|
#: 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."
|
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."
|
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."
|
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
|
#: 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."
|
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."
|
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 { paramURL } from "../../elements/router/RouterOutlet";
|
||||||
import "./TopApplicationsTable";
|
import "./TopApplicationsTable";
|
||||||
import "./cards/AdminStatusCard";
|
import "./cards/AdminStatusCard";
|
||||||
import "./cards/BackupStatusCard";
|
|
||||||
import "./cards/SystemStatusCard";
|
import "./cards/SystemStatusCard";
|
||||||
import "./cards/VersionStatusCard";
|
import "./cards/VersionStatusCard";
|
||||||
import "./cards/WorkerStatusCard";
|
import "./cards/WorkerStatusCard";
|
||||||
@ -166,7 +165,7 @@ export class AdminOverviewPage extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
<!-- row 2 -->
|
<!-- row 2 -->
|
||||||
<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-system
|
<ak-admin-status-system
|
||||||
icon="pf-icon pf-icon-server"
|
icon="pf-icon pf-icon-server"
|
||||||
@ -175,7 +174,7 @@ export class AdminOverviewPage extends LitElement {
|
|||||||
</ak-admin-status-system>
|
</ak-admin-status-system>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<ak-admin-status-version
|
||||||
icon="pf-icon pf-icon-bundle"
|
icon="pf-icon pf-icon-bundle"
|
||||||
@ -185,17 +184,7 @@ export class AdminOverviewPage extends LitElement {
|
|||||||
</ak-admin-status-version>
|
</ak-admin-status-version>
|
||||||
</div>
|
</div>
|
||||||
<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"
|
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-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"
|
|
||||||
>
|
>
|
||||||
<ak-admin-status-card-workers
|
<ak-admin-status-card-workers
|
||||||
icon="pf-icon pf-icon-server"
|
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>`;
|
</ak-form-element-horizontal>`;
|
||||||
case ProxyMode.ForwardSingle:
|
case ProxyMode.ForwardSingle:
|
||||||
return html`<p class="pf-u-mb-xl">
|
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>
|
</p>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${t`External host`}
|
label=${t`External host`}
|
||||||
|
@ -92,16 +92,19 @@ export class ProxyProviderViewPage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderConfigTemplate(markdown: MarkdownDocument): MarkdownDocument {
|
renderConfigTemplate(markdown: MarkdownDocument): MarkdownDocument {
|
||||||
|
const extHost = new URL(this.provider?.externalHost || "http://a");
|
||||||
// See website/docs/providers/proxy/forward_auth.mdx
|
// See website/docs/providers/proxy/forward_auth.mdx
|
||||||
if (this.provider?.mode === ProxyMode.ForwardSingle) {
|
if (this.provider?.mode === ProxyMode.ForwardSingle) {
|
||||||
markdown.html = markdown.html
|
markdown.html = markdown.html
|
||||||
.replaceAll("authentik.company", window.location.hostname)
|
.replaceAll("authentik.company", window.location.hostname)
|
||||||
.replaceAll("outpost.company", window.location.hostname)
|
.replaceAll("http://outpost.company:9000", window.location.hostname)
|
||||||
.replaceAll("app.company", this.provider?.externalHost || "");
|
.replaceAll("https://app.company", extHost.toString())
|
||||||
|
.replaceAll("app.company", extHost.hostname);
|
||||||
} else if (this.provider?.mode == ProxyMode.ForwardDomain) {
|
} else if (this.provider?.mode == ProxyMode.ForwardDomain) {
|
||||||
markdown.html = markdown.html
|
markdown.html = markdown.html
|
||||||
.replaceAll("authentik.company", window.location.hostname)
|
.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;
|
return markdown;
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,14 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
|||||||
stageUuid: pk,
|
stageUuid: pk,
|
||||||
})
|
})
|
||||||
.then((stage) => {
|
.then((stage) => {
|
||||||
this.showConfigurationStage =
|
this.showConfigurationStages =
|
||||||
stage.notConfiguredAction === NotConfiguredActionEnum.Configure;
|
stage.notConfiguredAction === NotConfiguredActionEnum.Configure;
|
||||||
return stage;
|
return stage;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
showConfigurationStage = true;
|
showConfigurationStages = true;
|
||||||
|
|
||||||
getSuccessMessage(): string {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
@ -136,9 +136,9 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
|||||||
target.selectedOptions[0].value ===
|
target.selectedOptions[0].value ===
|
||||||
NotConfiguredActionEnum.Configure
|
NotConfiguredActionEnum.Configure
|
||||||
) {
|
) {
|
||||||
this.showConfigurationStage = true;
|
this.showConfigurationStages = true;
|
||||||
} else {
|
} else {
|
||||||
this.showConfigurationStage = false;
|
this.showConfigurationStages = false;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -165,21 +165,13 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
${this.showConfigurationStage
|
${this.showConfigurationStages
|
||||||
? html`
|
? html`
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${t`Configuration stage`}
|
label=${t`Configuration stages`}
|
||||||
?required=${true}
|
name="configurationStages"
|
||||||
name="configurationStage"
|
|
||||||
>
|
>
|
||||||
<select class="pf-c-form-control">
|
<select class="pf-c-form-control" multiple>
|
||||||
<option
|
|
||||||
value=""
|
|
||||||
?selected=${this.instance?.configurationStage ===
|
|
||||||
undefined}
|
|
||||||
>
|
|
||||||
---------
|
|
||||||
</option>
|
|
||||||
${until(
|
${until(
|
||||||
new StagesApi(DEFAULT_CONFIG)
|
new StagesApi(DEFAULT_CONFIG)
|
||||||
.stagesAllList({
|
.stagesAllList({
|
||||||
@ -187,9 +179,11 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
|||||||
})
|
})
|
||||||
.then((stages) => {
|
.then((stages) => {
|
||||||
return stages.results.map((stage) => {
|
return stages.results.map((stage) => {
|
||||||
const selected =
|
const selected = Array.from(
|
||||||
this.instance?.configurationStage ===
|
this.instance?.configurationStages || [],
|
||||||
stage.pk;
|
).some((su) => {
|
||||||
|
return su == stage.pk;
|
||||||
|
});
|
||||||
return html`<option
|
return html`<option
|
||||||
value=${ifDefined(stage.pk)}
|
value=${ifDefined(stage.pk)}
|
||||||
?selected=${selected}
|
?selected=${selected}
|
||||||
@ -202,7 +196,10 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
|||||||
)}
|
)}
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<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>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
`
|
`
|
||||||
|
@ -153,7 +153,7 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
|
|||||||
?required=${true}
|
?required=${true}
|
||||||
name="sources"
|
name="sources"
|
||||||
>
|
>
|
||||||
<select name="users" class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${until(
|
||||||
new SourcesApi(DEFAULT_CONFIG)
|
new SourcesApi(DEFAULT_CONFIG)
|
||||||
.sourcesAllList({})
|
.sourcesAllList({})
|
||||||
|
@ -9,7 +9,7 @@ Applications are used to configure and separate the authorization / access contr
|
|||||||
|
|
||||||
## Authorization
|
## 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.
|
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__PORT`: Database port, defaults to 5432
|
||||||
- `AUTHENTIK_POSTGRESQL__PASSWORD`: Database password, defaults to the environment variable `POSTGRES_PASSWORD`
|
- `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
|
## Redis Settings
|
||||||
|
|
||||||
- `AUTHENTIK_REDIS__HOST`: Hostname of your Redis Server
|
- `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`.
|
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
|
### AUTHENTIK_AVATARS
|
||||||
|
|
||||||
Configure how authentik should show avatars for users. Following values can be set:
|
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
|
sudo apt-get install -y pwgen
|
||||||
# Because of a PostgreSQL limitation, only passwords up to 99 chars are supported
|
# 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
|
# See https://www.postgresql.org/message-id/09512C4F-8CB9-4021-B455-EF4C4F0D55A0@amazon.com
|
||||||
echo "PG_PASS=$(pwgen 40 1)" >> .env
|
echo "PG_PASS=$(pwgen -s 40 1)" >> .env
|
||||||
echo "AUTHENTIK_SECRET_KEY=$(pwgen 50 1)" >> .env
|
echo "AUTHENTIK_SECRET_KEY=$(pwgen -s 50 1)" >> .env
|
||||||
# Skip if you don't want to enable error reporting
|
# Skip if you don't want to enable error reporting
|
||||||
echo "AUTHENTIK_ERROR_REPORTING__ENABLED=true" >> .env
|
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