Compare commits
52 Commits
version/20
...
version/20
Author | SHA1 | Date | |
---|---|---|---|
ac52667327 | |||
0d7c5c2108 | |||
73e3d19384 | |||
f6e0f0282d | |||
3f42067a8f | |||
ed6f5b98df | |||
dd290e264c | |||
c85484fc00 | |||
663dffd8be | |||
c15d0c3d17 | |||
bf09a54f35 | |||
930dd51663 | |||
12a523c7aa | |||
ea9a6d57dd | |||
91958e1232 | |||
8925afb089 | |||
ccafe7be4f | |||
8279690a8f | |||
763d3ae76a | |||
b775e7f4d3 | |||
3d8d93ece5 | |||
06af306e8a | |||
9257f3c919 | |||
2fe7f4cf04 | |||
04399bc8bb | |||
fcbcfbc3c0 | |||
3e4ce62dfe | |||
d8292151e6 | |||
3d01a59b34 | |||
5df15c4105 | |||
75d695105d | |||
28189bdddf | |||
f6885c7cf8 | |||
2c43f0824e | |||
13e2eea72f | |||
9441be1ee2 | |||
d7ab2a362a | |||
e920be3a72 | |||
f771383c4b | |||
65c75f085a | |||
17503365f7 | |||
ebf9f0ca63 | |||
ae26d2756f | |||
124071f9be | |||
471f7d9c62 | |||
a6a6b3bd06 | |||
48ad3dccda | |||
341c58a722 | |||
9b04f2da48 | |||
f7a296544f | |||
78641a57ad | |||
a77ff5ffec |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2021.9.1-rc1
|
||||
current_version = 2021.9.1-rc3
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -27,7 +27,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
Output of docker-compose logs or kubectl logs respectively
|
||||
|
||||
**Version and Deployment (please complete the following information):**
|
||||
- authentik version: [e.g. 0.10.0-stable]
|
||||
- authentik version: [e.g. 2021.8.5]
|
||||
- Deployment: [e.g. docker-compose, helm]
|
||||
|
||||
**Additional context**
|
||||
|
2
.github/ISSUE_TEMPLATE/question.md
vendored
2
.github/ISSUE_TEMPLATE/question.md
vendored
@ -20,7 +20,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
Output of docker-compose logs or kubectl logs respectively
|
||||
|
||||
**Version and Deployment (please complete the following information):**
|
||||
- authentik version: [e.g. 0.10.0-stable]
|
||||
- authentik version: [e.g. 2021.8.5]
|
||||
- Deployment: [e.g. docker-compose, helm]
|
||||
|
||||
**Additional context**
|
||||
|
128
.github/workflows/ci-main.yml
vendored
128
.github/workflows/ci-main.yml
vendored
@ -25,14 +25,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run pylint
|
||||
run: pipenv run pylint authentik tests lifecycle
|
||||
@ -43,14 +43,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run black
|
||||
run: pipenv run black --check authentik tests lifecycle
|
||||
@ -61,14 +61,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run isort
|
||||
run: pipenv run isort --check authentik tests lifecycle
|
||||
@ -79,14 +79,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run bandit
|
||||
run: pipenv run bandit -r authentik tests lifecycle
|
||||
@ -113,14 +113,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run migrations
|
||||
run: pipenv run python -m lifecycle.migrate
|
||||
@ -128,6 +128,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
@ -136,14 +138,14 @@ jobs:
|
||||
# Copy current, latest config to local
|
||||
cp authentik/lib/default.yml local.env.yml
|
||||
git checkout $(git describe --abbrev=0 --match 'version/*')
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- name: run migrations to stable
|
||||
run: pipenv run python -m lifecycle.migrate
|
||||
@ -166,14 +168,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- uses: testspace-com/setup-testspace@v1
|
||||
with:
|
||||
@ -195,14 +197,14 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: scripts/ci_prepare.sh
|
||||
- uses: testspace-com/setup-testspace@v1
|
||||
with:
|
||||
@ -234,14 +236,14 @@ jobs:
|
||||
- uses: testspace-com/setup-testspace@v1
|
||||
with:
|
||||
domain: ${{github.repository_owner}}
|
||||
- id: cache-pipenv
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
|
||||
# - id: cache-pipenv
|
||||
# uses: actions/cache@v2.1.6
|
||||
# with:
|
||||
# path: ~/.local/share/virtualenvs
|
||||
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
|
||||
- name: prepare
|
||||
env:
|
||||
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
# env:
|
||||
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
|
||||
run: |
|
||||
scripts/ci_prepare.sh
|
||||
docker-compose -f tests/e2e/ci.docker-compose.yml up -d
|
||||
|
20
.github/workflows/release-publish.yml
vendored
20
.github/workflows/release-publish.yml
vendored
@ -33,14 +33,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik:2021.9.1-rc1,
|
||||
beryju/authentik:2021.9.1-rc3,
|
||||
beryju/authentik:latest,
|
||||
ghcr.io/goauthentik/server:2021.9.1-rc1,
|
||||
ghcr.io/goauthentik/server:2021.9.1-rc3,
|
||||
ghcr.io/goauthentik/server:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.1-rc1', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.1-rc3', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik:latest
|
||||
docker tag beryju/authentik:latest beryju/authentik:stable
|
||||
@ -75,14 +75,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-proxy:2021.9.1-rc1,
|
||||
beryju/authentik-proxy:2021.9.1-rc3,
|
||||
beryju/authentik-proxy:latest,
|
||||
ghcr.io/goauthentik/proxy:2021.9.1-rc1,
|
||||
ghcr.io/goauthentik/proxy:2021.9.1-rc3,
|
||||
ghcr.io/goauthentik/proxy:latest
|
||||
file: proxy.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.1-rc1', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.1-rc3', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik-proxy:latest
|
||||
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
|
||||
@ -117,14 +117,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-ldap:2021.9.1-rc1,
|
||||
beryju/authentik-ldap:2021.9.1-rc3,
|
||||
beryju/authentik-ldap:latest,
|
||||
ghcr.io/goauthentik/ldap:2021.9.1-rc1,
|
||||
ghcr.io/goauthentik/ldap:2021.9.1-rc3,
|
||||
ghcr.io/goauthentik/ldap:latest
|
||||
file: ldap.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.1-rc1', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.1-rc3', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik-ldap:latest
|
||||
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
|
||||
@ -175,7 +175,7 @@ jobs:
|
||||
SENTRY_PROJECT: authentik
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
with:
|
||||
version: authentik@2021.9.1-rc1
|
||||
version: authentik@2021.9.1-rc3
|
||||
environment: beryjuorg-prod
|
||||
sourcemaps: './web/dist'
|
||||
url_prefix: '~/static/dist'
|
||||
|
3
Pipfile
3
Pipfile
@ -49,9 +49,6 @@ ua-parser = "*"
|
||||
deepmerge = "*"
|
||||
colorama = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
||||
|
||||
[dev-packages]
|
||||
bandit = "*"
|
||||
black = "==21.5b1"
|
||||
|
40
Pipfile.lock
generated
40
Pipfile.lock
generated
@ -1,12 +1,10 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "f0befa9b3dacc1c3363b9442fa7a43f6be2c46a8fcb80a994230d288a384e54d"
|
||||
"sha256": "19d5324fd1a4af125ed57a683030ca14ee2d3648117748e4b32656875484728e"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.9"
|
||||
},
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
@ -122,19 +120,19 @@
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:63b9846c26e0905f4e9e39d6b59f152330c53a926d693439161c43dcf9779365",
|
||||
"sha256:a9232185d8e7e2fd2b166c0ebee5d7b1f787fdb3093f33bbf5aa932c08f0ccac"
|
||||
"sha256:9b6679e3c54f8c32c09872948450ece87473cbc830cfd6c84dad58eb014329ba",
|
||||
"sha256:caa96b7c2be2168b6efc25ab1fb61c996174bcfbcab21b5f642608185daa6403"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.18.42"
|
||||
"version": "==1.18.43"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:0952d1200968365b440045efe8e45bbae38cf603fee12bcfc3d7b5f963cbfa18",
|
||||
"sha256:6de4fec4ee10987e4dea96f289553c2f45109fcaafcb74a5baee1221926e1306"
|
||||
"sha256:b74d0a5fe0f7b73fa4b5670eaa9ff456b0bce70966d478dcd631c91458916eb6",
|
||||
"sha256:de7bf9c9098578d386b785b5b6eab954acccd3f79fe3e2eb971da608c967082b"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.21.42"
|
||||
"version": "==1.21.43"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
@ -1412,11 +1410,11 @@
|
||||
},
|
||||
"astroid": {
|
||||
"hashes": [
|
||||
"sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c",
|
||||
"sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e"
|
||||
"sha256:dcc06f6165f415220013801642bd6c9808a02967070919c4b746c6864c205471",
|
||||
"sha256:fe81f80c0b35264acb5653302ffbd935d394f1775c5e4487df745bf9c2442708"
|
||||
],
|
||||
"markers": "python_version ~= '3.6'",
|
||||
"version": "==2.7.3"
|
||||
"version": "==2.8.0"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
@ -1574,7 +1572,7 @@
|
||||
"sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899",
|
||||
"sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"
|
||||
],
|
||||
"markers": "python_version < '4' and python_full_version >= '3.6.1'",
|
||||
"markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
|
||||
"version": "==5.9.3"
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
@ -1668,11 +1666,11 @@
|
||||
},
|
||||
"pylint": {
|
||||
"hashes": [
|
||||
"sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1",
|
||||
"sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852"
|
||||
"sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126",
|
||||
"sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.10.2"
|
||||
"version": "==2.11.1"
|
||||
},
|
||||
"pylint-django": {
|
||||
"hashes": [
|
||||
@ -1850,6 +1848,14 @@
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
|
||||
"sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
|
||||
"sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
|
||||
],
|
||||
"version": "==3.10.0.2"
|
||||
},
|
||||
"urllib3": {
|
||||
"extras": [
|
||||
"secure"
|
||||
|
@ -1,3 +1,3 @@
|
||||
"""authentik"""
|
||||
__version__ = "2021.9.1-rc1"
|
||||
__version__ = "2021.9.1-rc3"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
@ -33,3 +33,12 @@ class OwnerPermissions(BasePermission):
|
||||
if owner != request.user:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class OwnerSuperuserPermissions(OwnerPermissions):
|
||||
"""Similar to OwnerPermissions, except always allow access for superusers"""
|
||||
|
||||
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
return super().has_object_permission(request, view, obj)
|
||||
|
@ -5,6 +5,9 @@ from typing import Callable, Optional
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
def permission_required(perm: Optional[str] = None, other_perms: Optional[list[str]] = None):
|
||||
@ -18,10 +21,12 @@ def permission_required(perm: Optional[str] = None, other_perms: Optional[list[s
|
||||
if perm:
|
||||
obj = self.get_object()
|
||||
if not request.user.has_perm(perm, obj):
|
||||
LOGGER.debug("denying access for object", user=request.user, perm=perm, obj=obj)
|
||||
return self.permission_denied(request)
|
||||
if other_perms:
|
||||
for other_perm in other_perms:
|
||||
if not request.user.has_perm(other_perm):
|
||||
LOGGER.debug("denying access for other", user=request.user, perm=perm)
|
||||
return self.permission_denied(request)
|
||||
return func(self, request, *args, **kwargs)
|
||||
|
||||
|
@ -11,6 +11,7 @@ from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from ua_parser import user_agent_parser
|
||||
|
||||
from authentik.api.authorization import OwnerSuperuserPermissions
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.models import AuthenticatedSession
|
||||
from authentik.events.geo import GEOIP_READER, GeoIPDict
|
||||
@ -102,11 +103,8 @@ class AuthenticatedSessionViewSet(
|
||||
search_fields = ["user__username", "last_ip", "last_user_agent"]
|
||||
filterset_fields = ["user__username", "last_ip", "last_user_agent"]
|
||||
ordering = ["user__username"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
permission_classes = [OwnerSuperuserPermissions]
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
|
@ -95,7 +95,9 @@ class SourceViewSet(
|
||||
@action(detail=False, pagination_class=None, filter_backends=[])
|
||||
def user_settings(self, request: Request) -> Response:
|
||||
"""Get all sources the user can configure"""
|
||||
_all_sources: Iterable[Source] = Source.objects.filter(enabled=True).select_subclasses()
|
||||
_all_sources: Iterable[Source] = (
|
||||
Source.objects.filter(enabled=True).select_subclasses().order_by("name")
|
||||
)
|
||||
matching_sources: list[UserSettingSerializer] = []
|
||||
for source in _all_sources:
|
||||
user_settings = source.ui_user_settings
|
||||
|
@ -2,15 +2,19 @@
|
||||
from typing import Any
|
||||
|
||||
from django.http.response import Http404
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.api.authorization import OwnerSuperuserPermissions
|
||||
from authentik.api.decorators import permission_required
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.users import UserSerializer
|
||||
@ -79,13 +83,23 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
|
||||
"expires",
|
||||
"expiring",
|
||||
]
|
||||
ordering = ["expires"]
|
||||
ordering = ["identifier", "expires"]
|
||||
permission_classes = [OwnerSuperuserPermissions]
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
|
||||
def perform_create(self, serializer: TokenSerializer):
|
||||
serializer.save(
|
||||
user=self.request.user,
|
||||
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
|
||||
)
|
||||
if not self.request.user.is_superuser:
|
||||
return serializer.save(
|
||||
user=self.request.user,
|
||||
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
|
||||
)
|
||||
return super().perform_create(serializer)
|
||||
|
||||
@permission_required("authentik_core.view_token_key")
|
||||
@extend_schema(
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""User API Views"""
|
||||
from datetime import timedelta
|
||||
from json import loads
|
||||
from typing import Optional
|
||||
|
||||
@ -7,6 +8,7 @@ from django.db.transaction import atomic
|
||||
from django.db.utils import IntegrityError
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _
|
||||
from django_filters.filters import BooleanFilter, CharFilter, ModelMultipleChoiceFilter
|
||||
from django_filters.filterset import FilterSet
|
||||
@ -274,6 +276,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
identifier=f"service-account-{username}-password",
|
||||
intent=TokenIntents.INTENT_APP_PASSWORD,
|
||||
user=user,
|
||||
expires=now() + timedelta(days=360),
|
||||
)
|
||||
return Response({"username": user.username, "token": token.key})
|
||||
except (IntegrityError) as exc:
|
||||
|
@ -184,7 +184,7 @@ class SourceFlowManager:
|
||||
# Ensure redirect is carried through when user was trying to
|
||||
# authorize application
|
||||
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
||||
NEXT_ARG_NAME, "authentik_core:if-admin"
|
||||
NEXT_ARG_NAME, "authentik_core:if-user"
|
||||
)
|
||||
kwargs.update(
|
||||
{
|
||||
@ -243,9 +243,9 @@ class SourceFlowManager:
|
||||
return self.handle_auth_user(connection)
|
||||
return redirect(
|
||||
reverse(
|
||||
"authentik_core:if-admin",
|
||||
"authentik_core:if-user",
|
||||
)
|
||||
+ f"#/user;page-{self.source.slug}"
|
||||
+ f"#/settings;page-{self.source.slug}"
|
||||
)
|
||||
|
||||
def handle_enroll(
|
||||
|
@ -17,7 +17,6 @@
|
||||
{% endblock %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
||||
<script src="{% static 'dist/poly.js' %}" type="module"></script>
|
||||
<script>window["polymerSkipLoadingFontRoboto"] = true;</script>
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
@ -21,7 +21,7 @@ You've logged out of {{ application }}.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<a id="ak-back-home" href="{% url 'authentik_core:if-admin' %}" class="pf-c-button pf-m-primary">{% trans 'Go back to overview' %}</a>
|
||||
<a id="ak-back-home" href="{% url 'authentik_core:root-redirect' %}" class="pf-c-button pf-m-primary">{% trans 'Go back to overview' %}</a>
|
||||
|
||||
<a id="logout" href="{% url 'authentik_flows:default-invalidation' %}" class="pf-c-button pf-m-secondary">{% trans 'Log out of authentik' %}</a>
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_before %}
|
||||
{{ block.super }}
|
||||
{% if flow.compatibility_mode %}
|
||||
<script>ShadyDOM = { force: !navigator.webdriver };</script>
|
||||
{% endif %}
|
||||
|
28
authentik/core/templates/if/user.html
Normal file
28
authentik/core/templates/if/user.html
Normal file
@ -0,0 +1,28 @@
|
||||
{% extends "base/skeleton.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block head %}
|
||||
<script src="{% static 'dist/UserInterface.js' %}" type="module"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<ak-message-container></ak-message-container>
|
||||
<ak-interface-user>
|
||||
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
|
||||
<div class="pf-c-empty-state" style="height: 100vh;">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}">
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
<h1 class="pf-c-title pf-m-lg">
|
||||
{% trans "Loading..." %}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</ak-interface-user>
|
||||
{% endblock %}
|
@ -58,4 +58,4 @@ class TestImpersonation(TestCase):
|
||||
self.client.force_login(self.other_user)
|
||||
|
||||
response = self.client.get(reverse("authentik_core:impersonate-end"))
|
||||
self.assertRedirects(response, reverse("authentik_core:if-admin"))
|
||||
self.assertRedirects(response, reverse("authentik_core:if-user"))
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""Test token API"""
|
||||
from json import loads
|
||||
|
||||
from django.urls.base import reverse
|
||||
from django.utils.timezone import now
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
@ -13,7 +15,8 @@ class TestTokenAPI(APITestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.user = User.objects.create(username="testuser")
|
||||
self.admin = User.objects.get(username="akadmin")
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_token_create(self):
|
||||
@ -55,3 +58,29 @@ class TestTokenAPI(APITestCase):
|
||||
clean_expired_models.delay().get()
|
||||
token.refresh_from_db()
|
||||
self.assertNotEqual(key, token.key)
|
||||
|
||||
def test_list(self):
|
||||
"""Test Token List (Test normal authentication)"""
|
||||
token_should: Token = Token.objects.create(
|
||||
identifier="test", expiring=False, user=self.user
|
||||
)
|
||||
Token.objects.create(identifier="test-2", expiring=False, user=get_anonymous_user())
|
||||
response = self.client.get(reverse(("authentik_api:token-list")))
|
||||
body = loads(response.content)
|
||||
self.assertEqual(len(body["results"]), 1)
|
||||
self.assertEqual(body["results"][0]["identifier"], token_should.identifier)
|
||||
|
||||
def test_list_admin(self):
|
||||
"""Test Token List (Test with admin auth)"""
|
||||
self.client.force_login(self.admin)
|
||||
token_should: Token = Token.objects.create(
|
||||
identifier="test", expiring=False, user=self.user
|
||||
)
|
||||
token_should_not: Token = Token.objects.create(
|
||||
identifier="test-2", expiring=False, user=get_anonymous_user()
|
||||
)
|
||||
response = self.client.get(reverse(("authentik_api:token-list")))
|
||||
body = loads(response.content)
|
||||
self.assertEqual(len(body["results"]), 2)
|
||||
self.assertEqual(body["results"][0]["identifier"], token_should.identifier)
|
||||
self.assertEqual(body["results"][1]["identifier"], token_should_not.identifier)
|
||||
|
@ -12,7 +12,7 @@ from authentik.core.views.session import EndSessionView
|
||||
urlpatterns = [
|
||||
path(
|
||||
"",
|
||||
login_required(RedirectView.as_view(pattern_name="authentik_core:if-admin")),
|
||||
login_required(RedirectView.as_view(pattern_name="authentik_core:if-user")),
|
||||
name="root-redirect",
|
||||
),
|
||||
# Impersonation
|
||||
@ -32,6 +32,11 @@ urlpatterns = [
|
||||
ensure_csrf_cookie(TemplateView.as_view(template_name="if/admin.html")),
|
||||
name="if-admin",
|
||||
),
|
||||
path(
|
||||
"if/user/",
|
||||
ensure_csrf_cookie(TemplateView.as_view(template_name="if/user.html")),
|
||||
name="if-user",
|
||||
),
|
||||
path(
|
||||
"if/flow/<slug:flow_slug>/",
|
||||
ensure_csrf_cookie(FlowInterfaceView.as_view()),
|
||||
|
@ -28,7 +28,7 @@ class ImpersonateInitView(View):
|
||||
|
||||
Event.new(EventAction.IMPERSONATION_STARTED).from_http(request, user_to_be)
|
||||
|
||||
return redirect("authentik_core:if-admin")
|
||||
return redirect("authentik_core:if-user")
|
||||
|
||||
|
||||
class ImpersonateEndView(View):
|
||||
@ -41,7 +41,7 @@ class ImpersonateEndView(View):
|
||||
or SESSION_IMPERSONATE_ORIGINAL_USER not in request.session
|
||||
):
|
||||
LOGGER.debug("Can't end impersonation", user=request.user)
|
||||
return redirect("authentik_core:if-admin")
|
||||
return redirect("authentik_core:if-user")
|
||||
|
||||
original_user = request.session[SESSION_IMPERSONATE_ORIGINAL_USER]
|
||||
|
||||
|
@ -3,7 +3,6 @@ from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from timeit import default_timer
|
||||
from traceback import format_tb
|
||||
from typing import Any, Optional
|
||||
|
||||
from celery import Task
|
||||
@ -42,8 +41,7 @@ class TaskResult:
|
||||
|
||||
def with_error(self, exc: Exception) -> "TaskResult":
|
||||
"""Since errors might not always be pickle-able, set the traceback"""
|
||||
self.messages.extend(format_tb(exc.__traceback__))
|
||||
self.messages.append(str(exc))
|
||||
self.messages.extend(exception_to_string(exc).splitlines())
|
||||
return self
|
||||
|
||||
|
||||
|
@ -86,7 +86,7 @@ class StageViewSet(
|
||||
@action(detail=False, pagination_class=None, filter_backends=[])
|
||||
def user_settings(self, request: Request) -> Response:
|
||||
"""Get all stages the user can configure"""
|
||||
_all_stages: Iterable[Stage] = Stage.objects.all().select_subclasses()
|
||||
_all_stages: Iterable[Stage] = Stage.objects.all().select_subclasses().order_by("name")
|
||||
matching_stages: list[dict] = []
|
||||
for stage in _all_stages:
|
||||
user_settings = stage.ui_user_settings
|
||||
|
@ -93,6 +93,7 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
if "exc_info" in hint:
|
||||
_, exc_value, _ = hint["exc_info"]
|
||||
if isinstance(exc_value, ignored_classes):
|
||||
LOGGER.debug("dropping exception", exception=exc_value)
|
||||
return None
|
||||
if "logger" in event:
|
||||
if event["logger"] in [
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""authentik lib reflection utilities"""
|
||||
from importlib import import_module
|
||||
from typing import Union
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
@ -19,12 +20,12 @@ def all_subclasses(cls, sort=True):
|
||||
return classes
|
||||
|
||||
|
||||
def class_to_path(cls):
|
||||
def class_to_path(cls: type) -> str:
|
||||
"""Turn Class (Class or instance) into module path"""
|
||||
return f"{cls.__module__}.{cls.__name__}"
|
||||
|
||||
|
||||
def path_to_class(path):
|
||||
def path_to_class(path: Union[str, None]) -> Union[type, None]:
|
||||
"""Import module and return class"""
|
||||
if not path:
|
||||
return None
|
||||
|
@ -81,11 +81,11 @@ class PolicyEngine:
|
||||
.iterator()
|
||||
)
|
||||
|
||||
def _check_policy_type(self, policy: Policy):
|
||||
def _check_policy_type(self, binding: PolicyBinding):
|
||||
"""Check policy type, make sure it's not the root class as that has no logic implemented"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
if policy.__class__ == Policy:
|
||||
raise TypeError(f"Policy '{policy}' is root type")
|
||||
if binding.policy is not None and binding.policy.__class__ == Policy:
|
||||
raise TypeError(f"Policy '{binding.policy}' is root type")
|
||||
|
||||
def build(self) -> "PolicyEngine":
|
||||
"""Build wrapper which monitors performance"""
|
||||
@ -102,7 +102,7 @@ class PolicyEngine:
|
||||
for binding in self._iter_bindings():
|
||||
self.__expected_result_count += 1
|
||||
|
||||
self._check_policy_type(binding.policy)
|
||||
self._check_policy_type(binding)
|
||||
key = cache_key(binding, self.request)
|
||||
cached_policy = cache.get(key, None)
|
||||
if cached_policy and self.use_cache:
|
||||
|
@ -238,6 +238,10 @@ class OAuthFulfillmentStage(StageView):
|
||||
parsed = urlparse(uri)
|
||||
return HttpResponseRedirectScheme(uri, allowed_schemes=[parsed.scheme])
|
||||
|
||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""Wrapper when this stage gets hit with a post request"""
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""final Stage of an OAuth2 Flow"""
|
||||
|
@ -59,6 +59,10 @@ class AuthNRequestParser:
|
||||
) -> AuthNRequest:
|
||||
root = ElementTree.fromstring(decoded_xml)
|
||||
|
||||
if "AssertionConsumerServiceURL" not in root.attrib:
|
||||
msg = "Missing 'AssertionConsumerServiceURL' attribute"
|
||||
LOGGER.warning(msg)
|
||||
raise CannotHandleAssertion(msg)
|
||||
request_acs_url = root.attrib["AssertionConsumerServiceURL"]
|
||||
|
||||
if self.provider.acs_url.lower() != request_acs_url.lower():
|
||||
@ -66,7 +70,7 @@ class AuthNRequestParser:
|
||||
f"ACS URL of {request_acs_url} doesn't match Provider "
|
||||
f"ACS URL of {self.provider.acs_url}."
|
||||
)
|
||||
LOGGER.info(msg)
|
||||
LOGGER.warning(msg)
|
||||
raise CannotHandleAssertion(msg)
|
||||
|
||||
auth_n_request = AuthNRequest(id=root.attrib["ID"], relay_state=relay_state)
|
||||
|
@ -22,4 +22,4 @@ class UseTokenView(View):
|
||||
login(request, token.user, backend=BACKEND_INBUILT)
|
||||
token.delete()
|
||||
messages.warning(request, _("Used recovery-link to authenticate."))
|
||||
return redirect("authentik_core:if-admin")
|
||||
return redirect("authentik_core:if-user")
|
||||
|
@ -3,10 +3,20 @@ import os
|
||||
from logging.config import dictConfig
|
||||
|
||||
from celery import Celery
|
||||
from celery.signals import after_task_publish, setup_logging, task_postrun, task_prerun
|
||||
from celery.signals import (
|
||||
after_task_publish,
|
||||
setup_logging,
|
||||
task_failure,
|
||||
task_internal_error,
|
||||
task_postrun,
|
||||
task_prerun,
|
||||
)
|
||||
from django.conf import settings
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.sentry import before_send
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
|
||||
# set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
|
||||
|
||||
@ -43,6 +53,18 @@ def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs):
|
||||
LOGGER.debug("Task finished", task_id=task_id, task_name=task.__name__, state=state)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@task_failure.connect
|
||||
@task_internal_error.connect
|
||||
def task_error_hook(task_id, exception: Exception, traceback, *args, **kwargs):
|
||||
"""Create system event for failed task"""
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
||||
LOGGER.warning("Task failure", exception=exception)
|
||||
if before_send({}, {"exc_info": (None, exception, None)}) is not None:
|
||||
Event.new(EventAction.SYSTEM_EXCEPTION, message=exception_to_string(exception)).save()
|
||||
|
||||
|
||||
# Using a string here means the worker doesn't have to serialize
|
||||
# the configuration object to child processes.
|
||||
# - namespace='CELERY' means all celery-related configuration keys
|
||||
|
@ -529,7 +529,7 @@ for _app in INSTALLED_APPS:
|
||||
if "apps" in _app:
|
||||
_app = ".".join(_app.split(".")[:-2])
|
||||
try:
|
||||
app_settings = importlib.import_module("%s.settings" % _app)
|
||||
app_settings = importlib.import_module(f"{_app}.settings")
|
||||
INSTALLED_APPS.extend(getattr(app_settings, "INSTALLED_APPS", []))
|
||||
MIDDLEWARE.extend(getattr(app_settings, "MIDDLEWARE", []))
|
||||
AUTHENTICATION_BACKENDS.extend(getattr(app_settings, "AUTHENTICATION_BACKENDS", []))
|
||||
|
@ -1,12 +1,11 @@
|
||||
"""Source API Views"""
|
||||
from typing import Any
|
||||
|
||||
from django.http.response import Http404
|
||||
from django.utils.text import slugify
|
||||
from django_filters.filters import AllValuesMultipleFilter
|
||||
from django_filters.filterset import FilterSet
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_field
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_field
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.request import Request
|
||||
@ -19,6 +18,9 @@ from authentik.core.api.sources import SourceSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.events.monitored_tasks import TaskInfo
|
||||
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
|
||||
|
||||
|
||||
class LDAPSourceSerializer(SourceSerializer):
|
||||
@ -95,19 +97,24 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
|
||||
@extend_schema(
|
||||
responses={
|
||||
200: TaskSerializer(many=False),
|
||||
404: OpenApiResponse(description="Task not found"),
|
||||
200: TaskSerializer(many=True),
|
||||
}
|
||||
)
|
||||
@action(methods=["GET"], detail=True)
|
||||
@action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[])
|
||||
# pylint: disable=unused-argument
|
||||
def sync_status(self, request: Request, slug: str) -> Response:
|
||||
"""Get source's sync status"""
|
||||
source = self.get_object()
|
||||
task = TaskInfo.by_name(f"ldap_sync_{slugify(source.name)}")
|
||||
if not task:
|
||||
raise Http404
|
||||
return Response(TaskSerializer(task, many=False).data)
|
||||
results = []
|
||||
for sync_class in [
|
||||
UserLDAPSynchronizer,
|
||||
GroupLDAPSynchronizer,
|
||||
MembershipLDAPSynchronizer,
|
||||
]:
|
||||
task = TaskInfo.by_name(f"ldap_sync_{slugify(source.name)}-{sync_class.__name__}")
|
||||
if task:
|
||||
results.append(task)
|
||||
return Response(TaskSerializer(results, many=True).data)
|
||||
|
||||
|
||||
class LDAPPropertyMappingSerializer(PropertyMappingSerializer):
|
||||
|
@ -4,7 +4,7 @@ from celery.schedules import crontab
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"sources_ldap_sync": {
|
||||
"task": "authentik.sources.ldap.tasks.ldap_sync_all",
|
||||
"schedule": crontab(minute="*/60"), # Run every hour
|
||||
"schedule": crontab(minute="*/120"), # Run every other hour
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,12 @@ from authentik.core.models import User
|
||||
from authentik.core.signals import password_changed
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||
from authentik.lib.utils.reflection import class_to_path
|
||||
from authentik.sources.ldap.models import LDAPSource
|
||||
from authentik.sources.ldap.password import LDAPPasswordChanger
|
||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
|
||||
from authentik.sources.ldap.tasks import ldap_sync
|
||||
from authentik.stages.prompt.signals import password_validate
|
||||
|
||||
@ -22,7 +26,12 @@ from authentik.stages.prompt.signals import password_validate
|
||||
def sync_ldap_source_on_save(sender, instance: LDAPSource, **_):
|
||||
"""Ensure that source is synced on save (if enabled)"""
|
||||
if instance.enabled:
|
||||
ldap_sync.delay(instance.pk)
|
||||
for sync_class in [
|
||||
UserLDAPSynchronizer,
|
||||
GroupLDAPSynchronizer,
|
||||
MembershipLDAPSynchronizer,
|
||||
]:
|
||||
ldap_sync.delay(instance.pk, class_to_path(sync_class))
|
||||
|
||||
|
||||
@receiver(password_validate)
|
||||
|
@ -17,11 +17,18 @@ class BaseLDAPSynchronizer:
|
||||
|
||||
_source: LDAPSource
|
||||
_logger: BoundLogger
|
||||
_messages: list[str]
|
||||
|
||||
def __init__(self, source: LDAPSource):
|
||||
self._source = source
|
||||
self._messages = []
|
||||
self._logger = get_logger().bind(source=source, syncer=self.__class__.__name__)
|
||||
|
||||
@property
|
||||
def messages(self) -> list[str]:
|
||||
"""Get all UI messages"""
|
||||
return self._messages
|
||||
|
||||
@property
|
||||
def base_dn_users(self) -> str:
|
||||
"""Shortcut to get full base_dn for user lookups"""
|
||||
@ -36,6 +43,11 @@ class BaseLDAPSynchronizer:
|
||||
return f"{self._source.additional_group_dn},{self._source.base_dn}"
|
||||
return self._source.base_dn
|
||||
|
||||
def message(self, *args, **kwargs):
|
||||
"""Add message that is later added to the System Task and shown to the user"""
|
||||
self._messages.append(" ".join(args))
|
||||
self._logger.warning(*args, **kwargs)
|
||||
|
||||
def sync(self) -> int:
|
||||
"""Sync function, implemented in subclass"""
|
||||
raise NotImplementedError()
|
||||
|
@ -15,7 +15,7 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
def sync(self) -> int:
|
||||
"""Iterate over all LDAP Groups and create authentik_core.Group instances"""
|
||||
if not self._source.sync_groups:
|
||||
self._logger.warning("Group syncing is disabled for this Source")
|
||||
self.message("Group syncing is disabled for this Source")
|
||||
return -1
|
||||
groups = self._source.connection.extend.standard.paged_search(
|
||||
search_base=self.base_dn_groups,
|
||||
@ -28,8 +28,8 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
attributes = group.get("attributes", {})
|
||||
group_dn = self._flatten(self._flatten(group.get("entryDN", group.get("dn"))))
|
||||
if self._source.object_uniqueness_field not in attributes:
|
||||
self._logger.warning(
|
||||
"Cannot find uniqueness Field in attributes",
|
||||
self.message(
|
||||
f"Cannot find uniqueness field in attributes: '{group_dn}",
|
||||
attributes=attributes.keys(),
|
||||
dn=group_dn,
|
||||
)
|
||||
|
@ -62,8 +62,8 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
# group_uniq might be a single string or an array with (hopefully) a single string
|
||||
if isinstance(group_uniq, list):
|
||||
if len(group_uniq) < 1:
|
||||
self._logger.warning(
|
||||
"Group does not have a uniqueness attribute.",
|
||||
self.message(
|
||||
f"Group does not have a uniqueness attribute: '{group_dn}'",
|
||||
group=group_dn,
|
||||
)
|
||||
return None
|
||||
@ -71,8 +71,8 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
if group_uniq not in self.group_cache:
|
||||
groups = Group.objects.filter(**{f"attributes__{LDAP_UNIQUENESS}": group_uniq})
|
||||
if not groups.exists():
|
||||
self._logger.warning(
|
||||
"Group does not exist in our DB yet, run sync_groups first.",
|
||||
self.message(
|
||||
f"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'",
|
||||
group=group_dn,
|
||||
)
|
||||
return None
|
||||
|
@ -18,7 +18,7 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
def sync(self) -> int:
|
||||
"""Iterate over all LDAP Users and create authentik_core.User instances"""
|
||||
if not self._source.sync_users:
|
||||
self._logger.warning("User syncing is disabled for this Source")
|
||||
self.message("User syncing is disabled for this Source")
|
||||
return -1
|
||||
users = self._source.connection.extend.standard.paged_search(
|
||||
search_base=self.base_dn_users,
|
||||
@ -31,8 +31,8 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
attributes = user.get("attributes", {})
|
||||
user_dn = self._flatten(user.get("entryDN", user.get("dn")))
|
||||
if self._source.object_uniqueness_field not in attributes:
|
||||
self._logger.warning(
|
||||
"Cannot find uniqueness Field in attributes",
|
||||
self.message(
|
||||
f"Cannot find uniqueness field in attributes: '{user_dn}",
|
||||
attributes=attributes.keys(),
|
||||
dn=user_dn,
|
||||
)
|
||||
@ -66,6 +66,7 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
pwd_last_set: datetime = attributes.get("pwdLastSet", datetime.now())
|
||||
pwd_last_set = pwd_last_set.replace(tzinfo=UTC)
|
||||
if created or pwd_last_set >= ak_user.password_change_date:
|
||||
self.message(f"'{ak_user.username}': Reset user's password")
|
||||
self._logger.debug(
|
||||
"Reset user's password",
|
||||
user=ak_user.username,
|
||||
|
@ -1,9 +1,12 @@
|
||||
"""LDAP Sync tasks"""
|
||||
from typing import Optional
|
||||
|
||||
from django.utils.text import slugify
|
||||
from ldap3.core.exceptions import LDAPException
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
|
||||
from authentik.lib.utils.reflection import class_to_path, path_to_class
|
||||
from authentik.root.celery import CELERY_APP
|
||||
from authentik.sources.ldap.models import LDAPSource
|
||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
||||
@ -17,11 +20,19 @@ LOGGER = get_logger()
|
||||
def ldap_sync_all():
|
||||
"""Sync all sources"""
|
||||
for source in LDAPSource.objects.filter(enabled=True):
|
||||
ldap_sync.delay(source.pk)
|
||||
for sync_class in [
|
||||
UserLDAPSynchronizer,
|
||||
GroupLDAPSynchronizer,
|
||||
MembershipLDAPSynchronizer,
|
||||
]:
|
||||
ldap_sync.delay(source.pk, class_to_path(sync_class))
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
def ldap_sync(self: MonitoredTask, source_pk: str):
|
||||
@CELERY_APP.task(
|
||||
bind=True, base=MonitoredTask, soft_time_limit=60 * 60 * 2, task_time_limit=60 * 60 * 2
|
||||
)
|
||||
# TODO: remove Optional[str] in 2021.10
|
||||
def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: Optional[str] = None):
|
||||
"""Synchronization of an LDAP Source"""
|
||||
self.result_timeout_hours = 2
|
||||
try:
|
||||
@ -30,17 +41,15 @@ def ldap_sync(self: MonitoredTask, source_pk: str):
|
||||
# Because the source couldn't be found, we don't have a UID
|
||||
# to set the state with
|
||||
return
|
||||
self.set_uid(slugify(source.name))
|
||||
if not sync_class:
|
||||
return
|
||||
sync = path_to_class(sync_class)
|
||||
self.set_uid(f"{slugify(source.name)}-{sync.__name__}")
|
||||
try:
|
||||
messages = []
|
||||
for sync_class in [
|
||||
UserLDAPSynchronizer,
|
||||
GroupLDAPSynchronizer,
|
||||
MembershipLDAPSynchronizer,
|
||||
]:
|
||||
sync_inst = sync_class(source)
|
||||
count = sync_inst.sync()
|
||||
messages.append(f"Synced {count} objects from {sync_class.__name__}")
|
||||
sync_inst = sync(source)
|
||||
count = sync_inst.sync()
|
||||
messages = sync_inst.messages
|
||||
messages.append(f"Synced {count} objects.")
|
||||
self.set_status(
|
||||
TaskResult(
|
||||
TaskResultStatus.SUCCESSFUL,
|
||||
|
@ -190,7 +190,7 @@ class ResponseProcessor:
|
||||
# Ensure redirect is carried through when user was trying to
|
||||
# authorize application
|
||||
final_redirect = self._http_request.session.get(SESSION_KEY_GET, {}).get(
|
||||
NEXT_ARG_NAME, "authentik_core:if-admin"
|
||||
NEXT_ARG_NAME, "authentik_core:if-user"
|
||||
)
|
||||
if matching_users.exists():
|
||||
# User exists already, switch to authentication flow
|
||||
|
@ -71,7 +71,7 @@ class InitiateView(View):
|
||||
# Ensure redirect is carried through when user was trying to
|
||||
# authorize application
|
||||
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
||||
NEXT_ARG_NAME, "authentik_core:if-admin"
|
||||
NEXT_ARG_NAME, "authentik_core:if-user"
|
||||
)
|
||||
kwargs.update(
|
||||
{
|
||||
|
@ -44,8 +44,10 @@ class TestConsentStage(APITestCase):
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||
{},
|
||||
)
|
||||
# pylint: disable=no-member
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertJSONEqual(
|
||||
# pylint: disable=no-member
|
||||
force_str(response.content),
|
||||
{
|
||||
"component": "xak-flow-redirect",
|
||||
|
@ -16,7 +16,11 @@ class Command(BaseCommand): # pragma: no cover
|
||||
"""Send a test-email with global settings"""
|
||||
delete_stage = False
|
||||
if options["stage"]:
|
||||
stage = EmailStage.objects.get(name=options["stage"])
|
||||
stages = EmailStage.objects.filter(name=options["stage"])
|
||||
if not stages.exists():
|
||||
print(f"Stage '{options['stage']}' does not exist")
|
||||
return
|
||||
stage = stages.first()
|
||||
else:
|
||||
stage = EmailStage.objects.create(
|
||||
name=f"temp-global-stage-{uuid4()}", use_global_settings=True
|
||||
|
@ -5,7 +5,6 @@ from django.db.models import F, Q
|
||||
from django.db.models import Value as V
|
||||
from django.http.request import HttpRequest
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
@ -31,6 +30,5 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
|
||||
tenant = getattr(request, "tenant", DEFAULT_TENANT)
|
||||
return {
|
||||
"tenant": tenant,
|
||||
"ak_version": __version__,
|
||||
"footer_links": CONFIG.y("footer_links"),
|
||||
}
|
||||
|
@ -106,6 +106,7 @@ func attemptProxyStart(ws *web.WebServer, u *url.URL) {
|
||||
log.WithField("logger", "authentik").Debug("attempting to start outpost")
|
||||
err := ac.StartBackgorundTasks()
|
||||
if err != nil {
|
||||
log.WithField("logger", "authentik").WithError(err).Warning("outpost failed to start")
|
||||
attempt += 1
|
||||
time.Sleep(15 * time.Second)
|
||||
if attempt > maxTries {
|
||||
|
@ -21,7 +21,7 @@ services:
|
||||
networks:
|
||||
- internal
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.1-rc1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.1-rc3}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -44,7 +44,7 @@ services:
|
||||
- "0.0.0.0:9000:9000"
|
||||
- "0.0.0.0:9443:9443"
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.1-rc1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.1-rc3}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
networks:
|
||||
|
@ -17,4 +17,4 @@ func OutpostUserAgent() string {
|
||||
return fmt.Sprintf("authentik-outpost@%s (build=%s)", VERSION, BUILD())
|
||||
}
|
||||
|
||||
const VERSION = "2021.9.1-rc1"
|
||||
const VERSION = "2021.9.1-rc3"
|
||||
|
@ -87,15 +87,8 @@ func NewAPIController(akURL url.URL, token string) *APIController {
|
||||
instanceUUID: uuid.New(),
|
||||
Outpost: outpost,
|
||||
}
|
||||
ac.logger.Debugf("HA Reload offset: %s", ac.reloadOffset)
|
||||
ac.logger.WithField("offset", ac.reloadOffset).Debug("HA Reload offset")
|
||||
ac.initWS(akURL, strfmt.UUID(outpost.Pk))
|
||||
|
||||
OutpostInfo.With(prometheus.Labels{
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
"name": outpost.Name,
|
||||
"version": constants.VERSION,
|
||||
"build": constants.BUILD(),
|
||||
}).Set(1)
|
||||
return ac
|
||||
}
|
||||
|
||||
@ -131,15 +124,23 @@ func (a *APIController) OnRefresh() error {
|
||||
}
|
||||
|
||||
func (a *APIController) StartBackgorundTasks() error {
|
||||
OutpostInfo.With(prometheus.Labels{
|
||||
"outpost_name": a.Outpost.Name,
|
||||
"outpost_type": a.Server.Type(),
|
||||
"uuid": a.instanceUUID.String(),
|
||||
"version": constants.VERSION,
|
||||
"build": constants.BUILD(),
|
||||
}).Set(1)
|
||||
err := a.OnRefresh()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to run initial refresh")
|
||||
} else {
|
||||
LastUpdate.With(prometheus.Labels{
|
||||
"uuid": a.instanceUUID.String(),
|
||||
"name": a.Outpost.Name,
|
||||
"version": constants.VERSION,
|
||||
"build": constants.BUILD(),
|
||||
"uuid": a.instanceUUID.String(),
|
||||
"outpost_name": a.Outpost.Name,
|
||||
"outpost_type": a.Server.Type(),
|
||||
"version": constants.VERSION,
|
||||
"build": constants.BUILD(),
|
||||
}).SetToCurrentTime()
|
||||
}
|
||||
go func() {
|
||||
|
@ -76,8 +76,9 @@ func (ac *APIController) startWSHandler() {
|
||||
err := ac.wsConn.ReadJSON(&wsMsg)
|
||||
if err != nil {
|
||||
ConnectionStatus.With(prometheus.Labels{
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
"name": ac.Outpost.Name,
|
||||
"outpost_name": ac.Outpost.Name,
|
||||
"outpost_type": ac.Server.Type(),
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
}).Set(0)
|
||||
logger.WithError(err).Warning("ws write error, reconnecting")
|
||||
ac.wsConn.CloseAndReconnect()
|
||||
@ -85,8 +86,9 @@ func (ac *APIController) startWSHandler() {
|
||||
continue
|
||||
}
|
||||
ConnectionStatus.With(prometheus.Labels{
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
"name": ac.Outpost.Name,
|
||||
"outpost_name": ac.Outpost.Name,
|
||||
"outpost_type": ac.Server.Type(),
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
}).Set(1)
|
||||
if wsMsg.Instruction == WebsocketInstructionTriggerUpdate {
|
||||
time.Sleep(ac.reloadOffset)
|
||||
@ -96,10 +98,11 @@ func (ac *APIController) startWSHandler() {
|
||||
logger.WithError(err).Debug("Failed to update")
|
||||
} else {
|
||||
LastUpdate.With(prometheus.Labels{
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
"name": ac.Outpost.Name,
|
||||
"version": constants.VERSION,
|
||||
"build": constants.BUILD(),
|
||||
"outpost_name": ac.Outpost.Name,
|
||||
"outpost_type": ac.Server.Type(),
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
"version": constants.VERSION,
|
||||
"build": constants.BUILD(),
|
||||
}).SetToCurrentTime()
|
||||
}
|
||||
}
|
||||
@ -128,8 +131,9 @@ func (ac *APIController) startWSHealth() {
|
||||
continue
|
||||
} else {
|
||||
ConnectionStatus.With(prometheus.Labels{
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
"name": ac.Outpost.Name,
|
||||
"outpost_name": ac.Outpost.Name,
|
||||
"outpost_type": ac.Server.Type(),
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
}).Set(1)
|
||||
}
|
||||
}
|
||||
@ -144,10 +148,11 @@ func (ac *APIController) startIntervalUpdater() {
|
||||
logger.WithError(err).Debug("Failed to update")
|
||||
} else {
|
||||
LastUpdate.With(prometheus.Labels{
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
"name": ac.Outpost.Name,
|
||||
"version": constants.VERSION,
|
||||
"build": constants.BUILD(),
|
||||
"outpost_name": ac.Outpost.Name,
|
||||
"outpost_type": ac.Server.Type(),
|
||||
"uuid": ac.instanceUUID.String(),
|
||||
"version": constants.VERSION,
|
||||
"build": constants.BUILD(),
|
||||
}).SetToCurrentTime()
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,13 @@ var (
|
||||
OutpostInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "authentik_outpost_info",
|
||||
Help: "Outpost info",
|
||||
}, []string{"uuid", "name", "version", "build"})
|
||||
}, []string{"outpost_name", "outpost_type", "uuid", "version", "build"})
|
||||
LastUpdate = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "authentik_outpost_last_update",
|
||||
Help: "Time of last update",
|
||||
}, []string{"uuid", "name", "version", "build"})
|
||||
}, []string{"outpost_name", "outpost_type", "uuid", "version", "build"})
|
||||
ConnectionStatus = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "authentik_outpost_connection",
|
||||
Help: "Connection status",
|
||||
}, []string{"uuid", "name"})
|
||||
}, []string{"outpost_name", "outpost_type", "uuid"})
|
||||
)
|
||||
|
@ -4,4 +4,5 @@ type Outpost interface {
|
||||
Start() error
|
||||
Refresh() error
|
||||
TimerFlowCacheExpiry()
|
||||
Type() string
|
||||
}
|
||||
|
@ -42,10 +42,11 @@ func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LD
|
||||
defer func() {
|
||||
span.Finish()
|
||||
metrics.Requests.With(prometheus.Labels{
|
||||
"type": "bind",
|
||||
"filter": "",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": ls.ac.Outpost.Name,
|
||||
"type": "bind",
|
||||
"filter": "",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Observe(float64(span.EndTime.Sub(span.StartTime)))
|
||||
req.log.WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Bind request")
|
||||
}()
|
||||
@ -59,10 +60,11 @@ func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LD
|
||||
}
|
||||
req.log.WithField("request", "bind").Warning("No provider found for request")
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "bind",
|
||||
"reason": "no_provider",
|
||||
"dn": bindDN,
|
||||
"client": utils.GetIP(conn.RemoteAddr()),
|
||||
"outpost_name": ls.ac.Outpost.Name,
|
||||
"type": "bind",
|
||||
"reason": "no_provider",
|
||||
"dn": bindDN,
|
||||
"client": utils.GetIP(conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
}
|
||||
|
@ -51,19 +51,21 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
|
||||
passed, err := fe.Execute()
|
||||
if !passed {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "bind",
|
||||
"reason": "invalid_credentials",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "bind",
|
||||
"reason": "invalid_credentials",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
if err != nil {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "bind",
|
||||
"reason": "flow_error",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "bind",
|
||||
"reason": "flow_error",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
req.log.WithError(err).Warning("failed to execute flow")
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
@ -73,19 +75,21 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
|
||||
if !access {
|
||||
req.log.Info("Access denied for user")
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "bind",
|
||||
"reason": "access_denied",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "bind",
|
||||
"reason": "access_denied",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||
}
|
||||
if err != nil {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "bind",
|
||||
"reason": "access_check_fail",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "bind",
|
||||
"reason": "access_check_fail",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
req.log.WithError(err).Warning("failed to check access")
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
@ -96,10 +100,11 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
|
||||
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(context.Background()).Execute()
|
||||
if err != nil {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "bind",
|
||||
"reason": "user_info_fail",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "bind",
|
||||
"reason": "user_info_fail",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
req.log.WithError(err).Warning("failed to get user info")
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
|
@ -36,28 +36,31 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
|
||||
filterEntity, err := ldap.GetFilterObjectClass(req.Filter)
|
||||
if err != nil {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "search",
|
||||
"reason": "filter_parse_fail",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "search",
|
||||
"reason": "filter_parse_fail",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", req.Filter)
|
||||
}
|
||||
if len(req.BindDN) < 1 {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "search",
|
||||
"reason": "empty_bind_dn",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "search",
|
||||
"reason": "empty_bind_dn",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN)
|
||||
}
|
||||
if !strings.HasSuffix(req.BindDN, baseDN) {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "search",
|
||||
"reason": "invalid_bind_dn",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "search",
|
||||
"reason": "invalid_bind_dn",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: BindDN %s not in our BaseDN %s", req.BindDN, pi.BaseDN)
|
||||
}
|
||||
@ -68,10 +71,11 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
|
||||
if !ok {
|
||||
pi.log.Debug("User info not cached")
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "search",
|
||||
"reason": "user_info_not_cached",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "search",
|
||||
"reason": "user_info_not_cached",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied")
|
||||
}
|
||||
@ -84,10 +88,11 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
|
||||
parsedFilter, err := ldap.CompileFilter(req.Filter)
|
||||
if err != nil {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "search",
|
||||
"reason": "filter_parse_fail",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "search",
|
||||
"reason": "filter_parse_fail",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", req.Filter)
|
||||
}
|
||||
@ -99,10 +104,11 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
|
||||
switch filterEntity {
|
||||
default:
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"type": "search",
|
||||
"reason": "unhandled_filter_type",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": pi.outpostName,
|
||||
"type": "search",
|
||||
"reason": "unhandled_filter_type",
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, req.Filter)
|
||||
case GroupObjectClass:
|
||||
|
@ -29,9 +29,9 @@ type ProviderInstance struct {
|
||||
s *LDAPServer
|
||||
log *log.Entry
|
||||
|
||||
tlsServerName *string
|
||||
cert *tls.Certificate
|
||||
|
||||
tlsServerName *string
|
||||
cert *tls.Certificate
|
||||
outpostName string
|
||||
searchAllowedGroups []*strfmt.UUID
|
||||
boundUsersMutex sync.RWMutex
|
||||
boundUsers map[string]UserFlags
|
||||
@ -86,3 +86,7 @@ func NewServer(ac *ak.APIController) *LDAPServer {
|
||||
s.CloseFunc("", ls)
|
||||
return ls
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) Type() string {
|
||||
return "ldap"
|
||||
}
|
||||
|
@ -13,11 +13,11 @@ var (
|
||||
Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "authentik_outpost_ldap_requests",
|
||||
Help: "The total number of configured providers",
|
||||
}, []string{"type", "dn", "filter", "client"})
|
||||
}, []string{"outpost_name", "type", "dn", "filter", "client"})
|
||||
RequestsRejected = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "authentik_outpost_ldap_requests_rejected",
|
||||
Help: "Total number of rejected requests",
|
||||
}, []string{"type", "reason", "dn", "client"})
|
||||
}, []string{"outpost_name", "type", "reason", "dn", "client"})
|
||||
)
|
||||
|
||||
func RunServer() {
|
||||
|
@ -50,6 +50,7 @@ func (ls *LDAPServer) Refresh() error {
|
||||
tlsServerName: provider.TlsServerName,
|
||||
uidStartNumber: *provider.UidStartNumber,
|
||||
gidStartNumber: *provider.GidStartNumber,
|
||||
outpostName: ls.ac.Outpost.Name,
|
||||
}
|
||||
if provider.Certificate.Get() != nil {
|
||||
kp := provider.Certificate.Get()
|
@ -46,10 +46,11 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
|
||||
defer func() {
|
||||
span.Finish()
|
||||
metrics.Requests.With(prometheus.Labels{
|
||||
"type": "search",
|
||||
"filter": req.Filter,
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
"outpost_name": ls.ac.Outpost.Name,
|
||||
"type": "search",
|
||||
"filter": req.Filter,
|
||||
"dn": req.BindDN,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Observe(float64(span.EndTime.Sub(span.StartTime)))
|
||||
req.log.WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Search request")
|
||||
}()
|
||||
|
@ -1,7 +1,6 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/gob"
|
||||
"net/http"
|
||||
@ -32,6 +31,7 @@ type Application struct {
|
||||
endpint OIDCEndpoint
|
||||
oauthConfig oauth2.Config
|
||||
tokenVerifier *oidc.IDTokenVerifier
|
||||
outpostName string
|
||||
|
||||
sessions sessions.Store
|
||||
proxyConfig api.ProxyOutpostConfig
|
||||
@ -41,7 +41,7 @@ type Application struct {
|
||||
mux *mux.Router
|
||||
}
|
||||
|
||||
func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore, akHost string) *Application {
|
||||
func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore, ak *ak.APIController) *Application {
|
||||
gob.Register(Claims{})
|
||||
|
||||
externalHost, err := url.Parse(p.ExternalHost)
|
||||
@ -49,15 +49,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
||||
log.WithError(err).Warning("Failed to parse URL, skipping provider")
|
||||
}
|
||||
|
||||
// Support for RS256, new proxy providers will use HS256 but old ones
|
||||
// might not, and this makes testing easier
|
||||
var ks oidc.KeySet
|
||||
if contains(p.OidcConfiguration.IdTokenSigningAlgValuesSupported, "HS256") {
|
||||
ks = hs256.NewKeySet(*p.ClientSecret)
|
||||
} else {
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, c)
|
||||
oidc.NewRemoteKeySet(ctx, p.OidcConfiguration.JwksUri)
|
||||
}
|
||||
ks := hs256.NewKeySet(*p.ClientSecret)
|
||||
|
||||
var verifier = oidc.NewVerifier(p.OidcConfiguration.Issuer, ks, &oidc.Config{
|
||||
ClientID: *p.ClientId,
|
||||
@ -65,7 +57,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
||||
})
|
||||
|
||||
// Configure an OpenID Connect aware OAuth2 client.
|
||||
endpoint := GetOIDCEndpoint(p, akHost)
|
||||
endpoint := GetOIDCEndpoint(p, ak.Outpost.Config["authentik_host"].(string))
|
||||
oauth2Config := oauth2.Config{
|
||||
ClientID: *p.ClientId,
|
||||
ClientSecret: *p.ClientSecret,
|
||||
@ -77,6 +69,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
||||
a := &Application{
|
||||
Host: externalHost.Host,
|
||||
log: log.WithField("logger", "authentik.outpost.proxy.bundle").WithField("provider", p.Name),
|
||||
outpostName: ak.Outpost.Name,
|
||||
endpint: endpoint,
|
||||
oauthConfig: oauth2Config,
|
||||
tokenVerifier: verifier,
|
||||
@ -112,12 +105,13 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
||||
inner.ServeHTTP(rw, r)
|
||||
after := time.Since(before)
|
||||
metrics.Requests.With(prometheus.Labels{
|
||||
"type": "app",
|
||||
"scheme": r.URL.Scheme,
|
||||
"method": r.Method,
|
||||
"path": r.URL.Path,
|
||||
"host": web.GetHost(r),
|
||||
"user": user,
|
||||
"outpost_name": a.outpostName,
|
||||
"type": "app",
|
||||
"scheme": r.URL.Scheme,
|
||||
"method": r.Method,
|
||||
"path": r.URL.Path,
|
||||
"host": web.GetHost(r),
|
||||
"user": user,
|
||||
}).Observe(float64(after))
|
||||
})
|
||||
})
|
||||
|
@ -50,6 +50,7 @@ func (a *Application) configureProxy() error {
|
||||
user = claims.Email
|
||||
}
|
||||
metrics.UpstreamTiming.With(prometheus.Labels{
|
||||
"outpost_name": a.outpostName,
|
||||
"upstream_host": u.String(),
|
||||
"scheme": r.URL.Scheme,
|
||||
"method": r.Method,
|
||||
|
@ -44,15 +44,6 @@ func (a *Application) getClaims(r *http.Request) (*Claims, error) {
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// toString Generic to string function, currently supports actual strings and integers
|
||||
func toString(in interface{}) string {
|
||||
switch v := in.(type) {
|
||||
|
@ -15,12 +15,13 @@ func (ps *ProxyServer) HandlePing(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(204)
|
||||
after := time.Since(before)
|
||||
metrics.Requests.With(prometheus.Labels{
|
||||
"method": r.Method,
|
||||
"schema": r.URL.Scheme,
|
||||
"path": r.URL.Path,
|
||||
"host": web.GetHost(r),
|
||||
"type": "ping",
|
||||
"user": "",
|
||||
"outpost_name": ps.akAPI.Outpost.Name,
|
||||
"method": r.Method,
|
||||
"schema": r.URL.Scheme,
|
||||
"path": r.URL.Path,
|
||||
"host": web.GetHost(r),
|
||||
"type": "ping",
|
||||
"user": "",
|
||||
}).Observe(float64(after))
|
||||
}
|
||||
|
||||
@ -30,12 +31,13 @@ func (ps *ProxyServer) HandleStatic(rw http.ResponseWriter, r *http.Request) {
|
||||
http.StripPrefix("/akprox/static", staticFs).ServeHTTP(rw, r)
|
||||
after := time.Since(before)
|
||||
metrics.Requests.With(prometheus.Labels{
|
||||
"method": r.Method,
|
||||
"schema": r.URL.Scheme,
|
||||
"path": r.URL.Path,
|
||||
"host": web.GetHost(r),
|
||||
"type": "ping",
|
||||
"user": "",
|
||||
"outpost_name": ps.akAPI.Outpost.Name,
|
||||
"method": r.Method,
|
||||
"schema": r.URL.Scheme,
|
||||
"path": r.URL.Path,
|
||||
"host": web.GetHost(r),
|
||||
"type": "ping",
|
||||
"user": "",
|
||||
}).Observe(float64(after))
|
||||
}
|
||||
|
||||
|
@ -13,11 +13,11 @@ var (
|
||||
Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "authentik_outpost_proxy_requests",
|
||||
Help: "The total number of configured providers",
|
||||
}, []string{"method", "scheme", "path", "host", "type", "user"})
|
||||
}, []string{"outpost_name", "method", "scheme", "path", "host", "type", "user"})
|
||||
UpstreamTiming = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "authentik_outpost_proxy_upstream_time",
|
||||
Help: "A summary of the duration we wait for the upstream reply",
|
||||
}, []string{"method", "scheme", "path", "host", "upstream_host", "user"})
|
||||
}, []string{"outpost_name", "method", "scheme", "path", "host", "upstream_host", "user"})
|
||||
)
|
||||
|
||||
func RunServer() {
|
||||
|
@ -79,6 +79,10 @@ func (ps *ProxyServer) HandleHost(host string, rw http.ResponseWriter, r *http.R
|
||||
return false
|
||||
}
|
||||
|
||||
func (ps *ProxyServer) Type() string {
|
||||
return "proxy"
|
||||
}
|
||||
|
||||
func (ps *ProxyServer) TimerFlowCacheExpiry() {}
|
||||
|
||||
func (ps *ProxyServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
|
@ -24,7 +24,7 @@ func (ps *ProxyServer) Refresh() error {
|
||||
hc := &http.Client{
|
||||
Transport: ak.NewUserAgentTransport(constants.OutpostUserAgent()+ua, ak.NewTracingTransport(context.TODO(), ak.GetTLSTransport())),
|
||||
}
|
||||
a := application.NewApplication(provider, hc, ps.cryptoStore, ps.akAPI.Outpost.Config["authentik_host"].(string))
|
||||
a := application.NewApplication(provider, hc, ps.cryptoStore, ps.akAPI)
|
||||
apps[a.Host] = a
|
||||
}
|
||||
ps.apps = apps
|
||||
|
@ -22,7 +22,7 @@ var (
|
||||
func RunMetricsServer() {
|
||||
m := mux.NewRouter()
|
||||
m.Path("/metrics").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
defer promhttp.InstrumentMetricHandler(
|
||||
promhttp.InstrumentMetricHandler(
|
||||
prometheus.DefaultRegisterer, promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{
|
||||
DisableCompression: true,
|
||||
}),
|
||||
|
@ -58,7 +58,9 @@ disable =[
|
||||
"similarities",
|
||||
"cyclic-import",
|
||||
"protected-access",
|
||||
"raise-missing-from",]
|
||||
"raise-missing-from",
|
||||
# To preverse django's translation function we need to use %-formatting
|
||||
"consider-using-f-string",]
|
||||
|
||||
load-plugins=["pylint_django","pylint.extensions.bad_builtin"]
|
||||
django-settings-module="authentik.root.settings"
|
||||
|
10
schema.yml
10
schema.yml
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2021.9.1-rc1
|
||||
version: 2021.9.1-rc3
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@beryju.org
|
||||
@ -12018,7 +12018,7 @@ paths:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
/sources/ldap/{slug}/sync_status/:
|
||||
get:
|
||||
operationId: sources_ldap_sync_status_retrieve
|
||||
operationId: sources_ldap_sync_status_list
|
||||
description: Get source's sync status
|
||||
parameters:
|
||||
- in: path
|
||||
@ -12036,10 +12036,10 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Task'
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Task'
|
||||
description: ''
|
||||
'404':
|
||||
description: Task not found
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
|
@ -52,7 +52,7 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
||||
|
||||
code_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(totp.token())
|
||||
code_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(Keys.ENTER)
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.assert_user(USER())
|
||||
|
||||
@retry()
|
||||
@ -67,7 +67,7 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
||||
self.driver.get(self.url("authentik_core:if-flow", flow_slug=flow.slug))
|
||||
self.login()
|
||||
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.assert_user(USER())
|
||||
|
||||
self.driver.get(
|
||||
@ -112,7 +112,7 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
||||
self.driver.get(self.url("authentik_core:if-flow", flow_slug=flow.slug))
|
||||
self.login()
|
||||
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.assert_user(USER())
|
||||
|
||||
self.driver.get(
|
||||
|
@ -96,11 +96,11 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||
|
||||
self.initial_stages()
|
||||
|
||||
interface_admin = self.get_shadow_root("ak-interface-admin")
|
||||
wait = WebDriverWait(interface_admin, self.wait_timeout)
|
||||
interface_user = self.get_shadow_root("ak-interface-user")
|
||||
wait = WebDriverWait(interface_user, self.wait_timeout)
|
||||
|
||||
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
|
||||
self.driver.get(self.if_admin_url("/user"))
|
||||
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, ".pf-c-page__header")))
|
||||
self.driver.get(self.if_user_url("/settings"))
|
||||
|
||||
user = User.objects.get(username="foo")
|
||||
self.assertEqual(user.username, "foo")
|
||||
@ -195,10 +195,10 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||
|
||||
sleep(2)
|
||||
# We're now logged in
|
||||
wait = WebDriverWait(self.get_shadow_root("ak-interface-admin"), self.wait_timeout)
|
||||
wait = WebDriverWait(self.get_shadow_root("ak-interface-user"), self.wait_timeout)
|
||||
|
||||
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
|
||||
self.driver.get(self.if_admin_url("/user"))
|
||||
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, ".pf-c-page__header")))
|
||||
self.driver.get(self.if_user_url("/settings"))
|
||||
|
||||
self.assert_user(User.objects.get(username="foo"))
|
||||
|
||||
|
@ -22,5 +22,5 @@ class TestFlowsLogin(SeleniumTestCase):
|
||||
)
|
||||
)
|
||||
self.login()
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.assert_user(USER())
|
||||
|
@ -42,7 +42,7 @@ class TestFlowsStageSetup(SeleniumTestCase):
|
||||
)
|
||||
)
|
||||
self.login()
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
|
||||
self.driver.get(
|
||||
self.url(
|
||||
@ -62,7 +62,7 @@ class TestFlowsStageSetup(SeleniumTestCase):
|
||||
Keys.ENTER
|
||||
)
|
||||
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
# Because USER() is cached, we need to get the user manually here
|
||||
user = User.objects.get(username=USER().username)
|
||||
self.assertTrue(user.check_password(new_password))
|
||||
|
@ -174,8 +174,8 @@ class TestSourceOAuth2(SeleniumTestCase):
|
||||
prompt_stage.find_element(By.CSS_SELECTOR, "input[name=username]").send_keys(Keys.ENTER)
|
||||
|
||||
# Wait until we've logged in
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.driver.get(self.if_admin_url("/user"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.driver.get(self.if_user_url("/settings"))
|
||||
|
||||
self.assert_user(User(username="foo", name="admin", email="admin@example.com"))
|
||||
|
||||
@ -253,8 +253,8 @@ class TestSourceOAuth2(SeleniumTestCase):
|
||||
self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
|
||||
|
||||
# Wait until we've logged in
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.driver.get(self.if_admin_url("/user"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.driver.get(self.if_user_url("/settings"))
|
||||
|
||||
self.assert_user(User(username="foo", name="admin", email="admin@example.com"))
|
||||
|
||||
@ -348,7 +348,7 @@ class TestSourceOAuth1(SeleniumTestCase):
|
||||
# Wait until we've loaded the user info page
|
||||
sleep(2)
|
||||
# Wait until we've logged in
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.driver.get(self.if_admin_url("/user"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.driver.get(self.if_user_url("/settings"))
|
||||
|
||||
self.assert_user(User(username="example-user", name="test name", email="foo@example.com"))
|
||||
|
@ -153,8 +153,8 @@ class TestSourceSAML(SeleniumTestCase):
|
||||
self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER)
|
||||
|
||||
# Wait until we're logged in
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.driver.get(self.if_admin_url("/user"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.driver.get(self.if_user_url("/settings"))
|
||||
|
||||
self.assert_user(
|
||||
User.objects.exclude(username="akadmin")
|
||||
@ -233,8 +233,8 @@ class TestSourceSAML(SeleniumTestCase):
|
||||
self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER)
|
||||
|
||||
# Wait until we're logged in
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.driver.get(self.if_admin_url("/user"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.driver.get(self.if_user_url("/settings"))
|
||||
|
||||
self.assert_user(
|
||||
User.objects.exclude(username="akadmin")
|
||||
@ -300,8 +300,8 @@ class TestSourceSAML(SeleniumTestCase):
|
||||
self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER)
|
||||
|
||||
# Wait until we're logged in
|
||||
self.wait_for_url(self.if_admin_url("/library"))
|
||||
self.driver.get(self.if_admin_url("/user"))
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.driver.get(self.if_user_url("/settings"))
|
||||
|
||||
self.assert_user(
|
||||
User.objects.exclude(username="akadmin")
|
||||
|
@ -126,9 +126,9 @@ class SeleniumTestCase(StaticLiveServerTestCase):
|
||||
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
|
||||
return self.live_server_url + reverse(view, kwargs=kwargs)
|
||||
|
||||
def if_admin_url(self, view) -> str:
|
||||
def if_user_url(self, view) -> str:
|
||||
"""same as self.url() but show URL in shell"""
|
||||
return f"{self.live_server_url}/if/admin/#{view}"
|
||||
return f"{self.live_server_url}/if/user/#{view}"
|
||||
|
||||
def get_shadow_root(self, selector: str, container: Optional[WebElement] = None) -> WebElement:
|
||||
"""Get shadow root element's inner shadowRoot"""
|
||||
|
371
web/package-lock.json
generated
371
web/package-lock.json
generated
@ -15,7 +15,7 @@
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@goauthentik/api": "^2021.8.5-1631648842",
|
||||
"@goauthentik/api": "^2021.9.1-rc2-1631875394",
|
||||
"@lingui/cli": "^3.11.1",
|
||||
"@lingui/core": "^3.11.1",
|
||||
"@lingui/macro": "^3.11.1",
|
||||
@ -27,6 +27,7 @@
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@sentry/browser": "^6.12.0",
|
||||
"@sentry/tracing": "^6.12.0",
|
||||
"@squoosh/cli": "^0.7.2",
|
||||
"@types/chart.js": "^2.9.34",
|
||||
"@types/codemirror": "5.60.2",
|
||||
"@types/grecaptcha": "^3.0.3",
|
||||
@ -44,11 +45,12 @@
|
||||
"eslint-plugin-custom-elements": "0.0.2",
|
||||
"eslint-plugin-lit": "^1.5.1",
|
||||
"flowchart.js": "^1.15.0",
|
||||
"fuse.js": "^6.4.6",
|
||||
"lit-element": "^2.5.1",
|
||||
"lit-html": "^1.4.1",
|
||||
"moment": "^2.29.1",
|
||||
"prettier": "^2.4.0",
|
||||
"rapidoc": "^9.0.0",
|
||||
"prettier": "^2.4.1",
|
||||
"rapidoc": "^9.1.0",
|
||||
"rollup": "^2.56.2",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
@ -65,11 +67,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@apitools/openapi-parser": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@apitools/openapi-parser/-/openapi-parser-0.0.7.tgz",
|
||||
"integrity": "sha512-Bm+GmJ/HIJoNpcwEUSEF9Zh1SqTQ+LsPEK9u5EznVuvoYvVv+dyOWL5/UOAibkNF+wHv7uWS57+NICogPMwzMw==",
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@apitools/openapi-parser/-/openapi-parser-0.0.9.tgz",
|
||||
"integrity": "sha512-P236ejO7ZKHafR5Vbi8NOzFXmpzNA/cU/MZ+S8T6YEYLstoWn1LFVP/xudyrpHfgQBT1oIu1+EdWD5gL65oj4Q==",
|
||||
"dependencies": {
|
||||
"swagger-client": "^3.13.1"
|
||||
"swagger-client": "^3.16.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@ -1563,9 +1565,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime-corejs3": {
|
||||
"version": "7.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.3.tgz",
|
||||
"integrity": "sha512-30A3lP+sRL6ml8uhoJSs+8jwpKzbw8CqBvDc1laeptxPm5FahumJxirigcbD2qTs71Sonvj1cyZB0OKGAmxQ+A==",
|
||||
"version": "7.15.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.4.tgz",
|
||||
"integrity": "sha512-lWcAqKeB624/twtTc3w6w/2o9RqJPaNBhPGK6DKLSiwuVWC7WFkypWyNg+CpZoyJH0jVzv1uMtXZ/5/lQOLtCg==",
|
||||
"dependencies": {
|
||||
"core-js-pure": "^3.16.0",
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
@ -1689,9 +1691,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@goauthentik/api": {
|
||||
"version": "2021.8.5-1631648842",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.8.5-1631648842.tgz",
|
||||
"integrity": "sha512-hgwGJW5NOSbSJR3s0nYoW8YPoI5kdfz4pUL4/IulRSlet/J3rmyQXqtbYSAg5AfU/hASkmyiaHXtHtrihHg2hg=="
|
||||
"version": "2021.9.1-rc2-1631875394",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.9.1-rc2-1631875394.tgz",
|
||||
"integrity": "sha512-d7LyfFlN8JfHdIGjqupquMh8wj+ygN2rxPerJC7uCMiZTeZgguVjB/xLvIdeSPJcOyEEkXUrUPhSwSHuWGNE4Q=="
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.5.0",
|
||||
@ -2395,6 +2397,45 @@
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@squoosh/cli": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/cli/-/cli-0.7.2.tgz",
|
||||
"integrity": "sha512-uMnUWMx4S8UApO/EfPyRyvUmw+0jI9wwAfdHfGjvVg4DAIvEgsA+VWK2KOBnJiChvVd768K27g09ESzptyX93w==",
|
||||
"dependencies": {
|
||||
"@squoosh/lib": "^0.4.0",
|
||||
"commander": "^7.2.0",
|
||||
"json5": "^2.2.0",
|
||||
"kleur": "^4.1.4",
|
||||
"ora": "^5.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"cli": "src/index.js",
|
||||
"squoosh-cli": "src/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": " ^12.20.2 || ^14.13.1 || ^16.0.0 "
|
||||
}
|
||||
},
|
||||
"node_modules/@squoosh/cli/node_modules/commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@squoosh/lib": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.4.0.tgz",
|
||||
"integrity": "sha512-O1LyugWLZjMI4JZeZMA5vzfhfPjfMZXH5/HmVkRagP8B70wH3uoR7tjxfGNdSavey357MwL8YJDxbGwBBdHp7Q==",
|
||||
"dependencies": {
|
||||
"wasm-feature-detect": "^1.2.11",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": " ^12.5.0 || ^14.0.0 || ^16.0.0 "
|
||||
}
|
||||
},
|
||||
"node_modules/@types/chart.js": {
|
||||
"version": "2.9.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.34.tgz",
|
||||
@ -3010,9 +3051,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
|
||||
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz",
|
||||
"integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
@ -3565,9 +3606,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/core-js-pure": {
|
||||
"version": "3.16.2",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.16.2.tgz",
|
||||
"integrity": "sha512-oxKe64UH049mJqrKkynWp6Vu0Rlm/BTXO/bJZuN2mmR3RtOFNepLlSWDd1eo16PzHpQAoNG97rLU1V/YxesJjw==",
|
||||
"version": "3.17.3",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.17.3.tgz",
|
||||
"integrity": "sha512-YusrqwiOTTn8058JDa0cv9unbXdIiIgcgI9gXso0ey4WgkFLd3lYlV9rp9n7nDCsYxXsMDTjA4m1h3T348mdlQ==",
|
||||
"hasInstallScript": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -4424,19 +4465,6 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz",
|
||||
"integrity": "sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==",
|
||||
"engines": {
|
||||
"node": "^10.17.0 || >=12.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"domexception": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||
@ -4521,22 +4549,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data-encoder": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.4.4.tgz",
|
||||
"integrity": "sha512-7fHkKl/w+qxecNdv6Dy6gqAVuJ1Th4oyZd52nx0jGcgDBatMqCnIr5MtnuiFsLgEHs9HI2FufOmeHrj3obdhwA=="
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.5.3.tgz",
|
||||
"integrity": "sha512-TBXL4jWdTERP1oNLXCXEJYgBfA5dBbhGVvS6E9bvAl48gu4L1q+JQYnPfixEyemGewRUeCRRXLUOEdtRfE2FKQ=="
|
||||
},
|
||||
"node_modules/formdata-node": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.0.1.tgz",
|
||||
"integrity": "sha512-7qe/s/LQR4KE9zzPBg8HXRQQsgze4VtwTX9viuVOsodD5QSu7MKsNiSy5BWYDwV+kAcDTh3y7WnC5ZHK5t4Aqg==",
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.2.1.tgz",
|
||||
"integrity": "sha512-mYFfryf+E+r/zaYFWuouQEBbtjyJQql4hTDEVvUt9RexwCEzjj23pkVxAcwQDuFMftpf3MQhcbqp6FysWwN/tQ==",
|
||||
"dependencies": {
|
||||
"fetch-blob": "2.1.2",
|
||||
"node-domexception": "1.0.0"
|
||||
"node-domexception": "1.0.0",
|
||||
"web-streams-polyfill": "4.0.0-beta.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-node/node_modules/web-streams-polyfill": {
|
||||
"version": "4.0.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz",
|
||||
"integrity": "sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/fragment-cache": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
|
||||
@ -4590,6 +4626,14 @@
|
||||
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
|
||||
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
|
||||
},
|
||||
"node_modules/fuse.js": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.4.6.tgz",
|
||||
"integrity": "sha512-/gYxR/0VpXmWSfZOIPS3rWwU8SHgsRTwWuXhyb2O6s7aRuVtHtxCkR33bNYu3wyLyNx/Wpv0vU7FZy8Vj53VNw==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/fuzzaldrin": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz",
|
||||
@ -5407,6 +5451,14 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/kleur": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz",
|
||||
"integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/leven": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||
@ -5813,14 +5865,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
|
||||
"integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==",
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.4.tgz",
|
||||
"integrity": "sha512-jBo8AOayNaEcvBhNobg6/BLhdsK3NvnKWJg33MAAPbvTWiG4QBn9gpW1+7RssrKu4K1dKlN+0goVQwV41xEfOA==",
|
||||
"bin": {
|
||||
"marked": "bin/marked"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
@ -6470,9 +6522,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz",
|
||||
"integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==",
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
|
||||
"integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
@ -6525,9 +6577,9 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.24.1",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.24.1.tgz",
|
||||
"integrity": "sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow=="
|
||||
"version": "1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
|
||||
"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg=="
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
@ -6627,34 +6679,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rapidoc": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.0.0.tgz",
|
||||
"integrity": "sha512-ZZwUdzKLz6NtYdK92hwlwoG38aH3UMDbvwSk/o2crnDD21g909PRHvzSd3CxJqB8qbLAnU73LQmaxunQStzrXg==",
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.1.0.tgz",
|
||||
"integrity": "sha512-BY1fWj7RvUmUxmrbxqsXkcratS5ebVyt/ZeJNJ+AUKeMYCrIYdccjgQE3MVcwIatQqV+fPxAvaz6oKNBJcdaaQ==",
|
||||
"dependencies": {
|
||||
"@apitools/openapi-parser": "^0.0.7",
|
||||
"base64-arraybuffer": "^0.2.0",
|
||||
"lit-element": "2.4.0",
|
||||
"lit-html": "1.2.1",
|
||||
"marked": "^2.0.1",
|
||||
"prismjs": "^1.23.0"
|
||||
"@apitools/openapi-parser": "^0.0.9",
|
||||
"base64-arraybuffer": "^1.0.1",
|
||||
"lit-element": "2.5.1",
|
||||
"lit-html": "1.4.1",
|
||||
"marked": "^3.0.4",
|
||||
"prismjs": "^1.25.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rapidoc/node_modules/lit-element": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.4.0.tgz",
|
||||
"integrity": "sha512-pBGLglxyhq/Prk2H91nA0KByq/hx/wssJBQFiYqXhGDvEnY31PRGYf1RglVzyLeRysu0IHm2K0P196uLLWmwFg==",
|
||||
"dependencies": {
|
||||
"lit-html": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rapidoc/node_modules/lit-html": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.2.1.tgz",
|
||||
"integrity": "sha512-GSJHHXMGLZDzTRq59IUfL9FCdAlGfqNp/dEa7k7aBaaWD+JKaCjsAk9KYm2V12ItonVaYx2dprN66Zdm1AuBTQ=="
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
@ -7730,9 +7769,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-client": {
|
||||
"version": "3.16.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.16.0.tgz",
|
||||
"integrity": "sha512-fE+HPDla35+k9uXd9BmgZNvhUaM1oA8iCILOINYrziFK3+dkiSLG57h9Z4QOlcVMr/MjVHYy/1JbftlMt+sQ2A==",
|
||||
"version": "3.16.1",
|
||||
"resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.16.1.tgz",
|
||||
"integrity": "sha512-BcNRQzXHRGuXfhN0f80ptlr+bSaPvXwo8+gWbpmTnbKdAjcWOKAWwUx7rgGHjTKZh0qROr/GX9xOZIY8LrBuTg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime-corejs3": "^7.11.2",
|
||||
"btoa": "^1.2.1",
|
||||
@ -7744,7 +7783,7 @@
|
||||
"form-data-encoder": "^1.4.3",
|
||||
"formdata-node": "^4.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.19",
|
||||
"lodash": "^4.17.21",
|
||||
"qs": "^6.9.4",
|
||||
"querystring-browser": "^1.0.4",
|
||||
"traverse": "~0.6.6",
|
||||
@ -8246,6 +8285,11 @@
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz",
|
||||
"integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A=="
|
||||
},
|
||||
"node_modules/wasm-feature-detect": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
|
||||
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
|
||||
},
|
||||
"node_modules/wcwidth": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||
@ -8281,6 +8325,14 @@
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.1.1.tgz",
|
||||
"integrity": "sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q==",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/webcomponent-qr-code": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/webcomponent-qr-code/-/webcomponent-qr-code-1.0.5.tgz",
|
||||
@ -8476,11 +8528,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@apitools/openapi-parser": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@apitools/openapi-parser/-/openapi-parser-0.0.7.tgz",
|
||||
"integrity": "sha512-Bm+GmJ/HIJoNpcwEUSEF9Zh1SqTQ+LsPEK9u5EznVuvoYvVv+dyOWL5/UOAibkNF+wHv7uWS57+NICogPMwzMw==",
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@apitools/openapi-parser/-/openapi-parser-0.0.9.tgz",
|
||||
"integrity": "sha512-P236ejO7ZKHafR5Vbi8NOzFXmpzNA/cU/MZ+S8T6YEYLstoWn1LFVP/xudyrpHfgQBT1oIu1+EdWD5gL65oj4Q==",
|
||||
"requires": {
|
||||
"swagger-client": "^3.13.1"
|
||||
"swagger-client": "^3.16.1"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
@ -9475,9 +9527,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/runtime-corejs3": {
|
||||
"version": "7.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.3.tgz",
|
||||
"integrity": "sha512-30A3lP+sRL6ml8uhoJSs+8jwpKzbw8CqBvDc1laeptxPm5FahumJxirigcbD2qTs71Sonvj1cyZB0OKGAmxQ+A==",
|
||||
"version": "7.15.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.4.tgz",
|
||||
"integrity": "sha512-lWcAqKeB624/twtTc3w6w/2o9RqJPaNBhPGK6DKLSiwuVWC7WFkypWyNg+CpZoyJH0jVzv1uMtXZ/5/lQOLtCg==",
|
||||
"requires": {
|
||||
"core-js-pure": "^3.16.0",
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
@ -9566,9 +9618,9 @@
|
||||
"integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg=="
|
||||
},
|
||||
"@goauthentik/api": {
|
||||
"version": "2021.8.5-1631648842",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.8.5-1631648842.tgz",
|
||||
"integrity": "sha512-hgwGJW5NOSbSJR3s0nYoW8YPoI5kdfz4pUL4/IulRSlet/J3rmyQXqtbYSAg5AfU/hASkmyiaHXtHtrihHg2hg=="
|
||||
"version": "2021.9.1-rc2-1631875394",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.9.1-rc2-1631875394.tgz",
|
||||
"integrity": "sha512-d7LyfFlN8JfHdIGjqupquMh8wj+ygN2rxPerJC7uCMiZTeZgguVjB/xLvIdeSPJcOyEEkXUrUPhSwSHuWGNE4Q=="
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.5.0",
|
||||
@ -10132,6 +10184,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@squoosh/cli": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/cli/-/cli-0.7.2.tgz",
|
||||
"integrity": "sha512-uMnUWMx4S8UApO/EfPyRyvUmw+0jI9wwAfdHfGjvVg4DAIvEgsA+VWK2KOBnJiChvVd768K27g09ESzptyX93w==",
|
||||
"requires": {
|
||||
"@squoosh/lib": "^0.4.0",
|
||||
"commander": "^7.2.0",
|
||||
"json5": "^2.2.0",
|
||||
"kleur": "^4.1.4",
|
||||
"ora": "^5.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@squoosh/lib": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.4.0.tgz",
|
||||
"integrity": "sha512-O1LyugWLZjMI4JZeZMA5vzfhfPjfMZXH5/HmVkRagP8B70wH3uoR7tjxfGNdSavey357MwL8YJDxbGwBBdHp7Q==",
|
||||
"requires": {
|
||||
"wasm-feature-detect": "^1.2.11",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"@types/chart.js": {
|
||||
"version": "2.9.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.34.tgz",
|
||||
@ -10586,9 +10666,9 @@
|
||||
}
|
||||
},
|
||||
"base64-arraybuffer": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
|
||||
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ=="
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz",
|
||||
"integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA=="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
@ -10988,9 +11068,9 @@
|
||||
}
|
||||
},
|
||||
"core-js-pure": {
|
||||
"version": "3.16.2",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.16.2.tgz",
|
||||
"integrity": "sha512-oxKe64UH049mJqrKkynWp6Vu0Rlm/BTXO/bJZuN2mmR3RtOFNepLlSWDd1eo16PzHpQAoNG97rLU1V/YxesJjw=="
|
||||
"version": "3.17.3",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.17.3.tgz",
|
||||
"integrity": "sha512-YusrqwiOTTn8058JDa0cv9unbXdIiIgcgI9gXso0ey4WgkFLd3lYlV9rp9n7nDCsYxXsMDTjA4m1h3T348mdlQ=="
|
||||
},
|
||||
"cosmiconfig": {
|
||||
"version": "7.0.1",
|
||||
@ -11625,11 +11705,6 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"fetch-blob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz",
|
||||
"integrity": "sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow=="
|
||||
},
|
||||
"figures": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||
@ -11690,17 +11765,24 @@
|
||||
"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
|
||||
},
|
||||
"form-data-encoder": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.4.4.tgz",
|
||||
"integrity": "sha512-7fHkKl/w+qxecNdv6Dy6gqAVuJ1Th4oyZd52nx0jGcgDBatMqCnIr5MtnuiFsLgEHs9HI2FufOmeHrj3obdhwA=="
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.5.3.tgz",
|
||||
"integrity": "sha512-TBXL4jWdTERP1oNLXCXEJYgBfA5dBbhGVvS6E9bvAl48gu4L1q+JQYnPfixEyemGewRUeCRRXLUOEdtRfE2FKQ=="
|
||||
},
|
||||
"formdata-node": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.0.1.tgz",
|
||||
"integrity": "sha512-7qe/s/LQR4KE9zzPBg8HXRQQsgze4VtwTX9viuVOsodD5QSu7MKsNiSy5BWYDwV+kAcDTh3y7WnC5ZHK5t4Aqg==",
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.2.1.tgz",
|
||||
"integrity": "sha512-mYFfryf+E+r/zaYFWuouQEBbtjyJQql4hTDEVvUt9RexwCEzjj23pkVxAcwQDuFMftpf3MQhcbqp6FysWwN/tQ==",
|
||||
"requires": {
|
||||
"fetch-blob": "2.1.2",
|
||||
"node-domexception": "1.0.0"
|
||||
"node-domexception": "1.0.0",
|
||||
"web-streams-polyfill": "4.0.0-beta.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"web-streams-polyfill": {
|
||||
"version": "4.0.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz",
|
||||
"integrity": "sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"fragment-cache": {
|
||||
@ -11743,6 +11825,11 @@
|
||||
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
|
||||
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
|
||||
},
|
||||
"fuse.js": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.4.6.tgz",
|
||||
"integrity": "sha512-/gYxR/0VpXmWSfZOIPS3rWwU8SHgsRTwWuXhyb2O6s7aRuVtHtxCkR33bNYu3wyLyNx/Wpv0vU7FZy8Vj53VNw=="
|
||||
},
|
||||
"fuzzaldrin": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz",
|
||||
@ -12335,6 +12422,11 @@
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
|
||||
},
|
||||
"kleur": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz",
|
||||
"integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA=="
|
||||
},
|
||||
"leven": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||
@ -12669,9 +12761,9 @@
|
||||
}
|
||||
},
|
||||
"marked": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
|
||||
"integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA=="
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.4.tgz",
|
||||
"integrity": "sha512-jBo8AOayNaEcvBhNobg6/BLhdsK3NvnKWJg33MAAPbvTWiG4QBn9gpW1+7RssrKu4K1dKlN+0goVQwV41xEfOA=="
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
@ -13151,9 +13243,9 @@
|
||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz",
|
||||
"integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ=="
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
|
||||
"integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA=="
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "26.6.2",
|
||||
@ -13190,9 +13282,9 @@
|
||||
}
|
||||
},
|
||||
"prismjs": {
|
||||
"version": "1.24.1",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.24.1.tgz",
|
||||
"integrity": "sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow=="
|
||||
"version": "1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
|
||||
"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg=="
|
||||
},
|
||||
"progress": {
|
||||
"version": "2.0.3",
|
||||
@ -13262,31 +13354,16 @@
|
||||
}
|
||||
},
|
||||
"rapidoc": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.0.0.tgz",
|
||||
"integrity": "sha512-ZZwUdzKLz6NtYdK92hwlwoG38aH3UMDbvwSk/o2crnDD21g909PRHvzSd3CxJqB8qbLAnU73LQmaxunQStzrXg==",
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.1.0.tgz",
|
||||
"integrity": "sha512-BY1fWj7RvUmUxmrbxqsXkcratS5ebVyt/ZeJNJ+AUKeMYCrIYdccjgQE3MVcwIatQqV+fPxAvaz6oKNBJcdaaQ==",
|
||||
"requires": {
|
||||
"@apitools/openapi-parser": "^0.0.7",
|
||||
"base64-arraybuffer": "^0.2.0",
|
||||
"lit-element": "2.4.0",
|
||||
"lit-html": "1.2.1",
|
||||
"marked": "^2.0.1",
|
||||
"prismjs": "^1.23.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lit-element": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.4.0.tgz",
|
||||
"integrity": "sha512-pBGLglxyhq/Prk2H91nA0KByq/hx/wssJBQFiYqXhGDvEnY31PRGYf1RglVzyLeRysu0IHm2K0P196uLLWmwFg==",
|
||||
"requires": {
|
||||
"lit-html": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"lit-html": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.2.1.tgz",
|
||||
"integrity": "sha512-GSJHHXMGLZDzTRq59IUfL9FCdAlGfqNp/dEa7k7aBaaWD+JKaCjsAk9KYm2V12ItonVaYx2dprN66Zdm1AuBTQ=="
|
||||
}
|
||||
"@apitools/openapi-parser": "^0.0.9",
|
||||
"base64-arraybuffer": "^1.0.1",
|
||||
"lit-element": "2.5.1",
|
||||
"lit-html": "1.4.1",
|
||||
"marked": "^3.0.4",
|
||||
"prismjs": "^1.25.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
@ -14116,9 +14193,9 @@
|
||||
}
|
||||
},
|
||||
"swagger-client": {
|
||||
"version": "3.16.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.16.0.tgz",
|
||||
"integrity": "sha512-fE+HPDla35+k9uXd9BmgZNvhUaM1oA8iCILOINYrziFK3+dkiSLG57h9Z4QOlcVMr/MjVHYy/1JbftlMt+sQ2A==",
|
||||
"version": "3.16.1",
|
||||
"resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.16.1.tgz",
|
||||
"integrity": "sha512-BcNRQzXHRGuXfhN0f80ptlr+bSaPvXwo8+gWbpmTnbKdAjcWOKAWwUx7rgGHjTKZh0qROr/GX9xOZIY8LrBuTg==",
|
||||
"requires": {
|
||||
"@babel/runtime-corejs3": "^7.11.2",
|
||||
"btoa": "^1.2.1",
|
||||
@ -14130,7 +14207,7 @@
|
||||
"form-data-encoder": "^1.4.3",
|
||||
"formdata-node": "^4.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.19",
|
||||
"lodash": "^4.17.21",
|
||||
"qs": "^6.9.4",
|
||||
"querystring-browser": "^1.0.4",
|
||||
"traverse": "~0.6.6",
|
||||
@ -14522,6 +14599,11 @@
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz",
|
||||
"integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A=="
|
||||
},
|
||||
"wasm-feature-detect": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
|
||||
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
|
||||
},
|
||||
"wcwidth": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||
@ -14548,6 +14630,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"web-streams-polyfill": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.1.1.tgz",
|
||||
"integrity": "sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q=="
|
||||
},
|
||||
"webcomponent-qr-code": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/webcomponent-qr-code/-/webcomponent-qr-code-1.0.5.tgz",
|
||||
|
@ -10,7 +10,8 @@
|
||||
"lint": "eslint . --max-warnings 0 --fix",
|
||||
"lit-analyse": "lit-analyzer src",
|
||||
"prettier-check": "prettier --check .",
|
||||
"prettier": "prettier --write ."
|
||||
"prettier": "prettier --write .",
|
||||
"background-image": "npx @squoosh/cli --resize '{\"enabled\":true,\"width\":2560,\"method\":\"lanczos3\",\"fitMethod\":\"contain\",\"premultiply\":true,\"linearRGB\":true}' --mozjpeg '{\"quality\":75,\"baseline\":false,\"arithmetic\":false,\"progressive\":true,\"optimize_coding\":true,\"smoothing\":0,\"color_space\":3,\"quant_table\":3,\"trellis_multipass\":false,\"trellis_opt_zero\":false,\"trellis_opt_table\":false,\"trellis_loops\":1,\"auto_subsample\":true,\"chroma_subsample\":2,\"separate_chroma_quality\":false,\"chroma_quality\":75}' src/assets/images/flow_background.jpg"
|
||||
},
|
||||
"lingui": {
|
||||
"sourceLocale": "en",
|
||||
@ -46,7 +47,7 @@
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@goauthentik/api": "^2021.8.5-1631648842",
|
||||
"@goauthentik/api": "^2021.9.1-rc2-1631875394",
|
||||
"@lingui/cli": "^3.11.1",
|
||||
"@lingui/core": "^3.11.1",
|
||||
"@lingui/macro": "^3.11.1",
|
||||
@ -58,6 +59,7 @@
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@sentry/browser": "^6.12.0",
|
||||
"@sentry/tracing": "^6.12.0",
|
||||
"@squoosh/cli": "^0.7.2",
|
||||
"@types/chart.js": "^2.9.34",
|
||||
"@types/codemirror": "5.60.2",
|
||||
"@types/grecaptcha": "^3.0.3",
|
||||
@ -75,11 +77,12 @@
|
||||
"eslint-plugin-custom-elements": "0.0.2",
|
||||
"eslint-plugin-lit": "^1.5.1",
|
||||
"flowchart.js": "^1.15.0",
|
||||
"fuse.js": "^6.4.6",
|
||||
"lit-element": "^2.5.1",
|
||||
"lit-html": "^1.4.1",
|
||||
"moment": "^2.29.1",
|
||||
"prettier": "^2.4.0",
|
||||
"rapidoc": "^9.0.0",
|
||||
"prettier": "^2.4.1",
|
||||
"rapidoc": "^9.1.0",
|
||||
"rollup": "^2.56.2",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
|
@ -1,2 +1,4 @@
|
||||
// @ts-ignore
|
||||
window["polymerSkipLoadingFontRoboto"] = true;
|
||||
import "construct-style-sheets-polyfill";
|
||||
import "@webcomponents/webcomponentsjs";
|
||||
|
@ -87,40 +87,7 @@ export default [
|
||||
clearScreen: false,
|
||||
},
|
||||
},
|
||||
// Main Application
|
||||
{
|
||||
input: "./src/interfaces/AdminInterface.ts",
|
||||
context: "window",
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "dist",
|
||||
sourcemap: true,
|
||||
manualChunks: manualChunks,
|
||||
chunkFileNames: "admin-[name].js",
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
cssimport(),
|
||||
resolve({ extensions, browser: true }),
|
||||
commonjs(),
|
||||
babel({
|
||||
extensions,
|
||||
babelHelpers: "runtime",
|
||||
include: ["src/**/*"],
|
||||
}),
|
||||
replace({
|
||||
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
|
||||
"preventAssignment": true,
|
||||
}),
|
||||
sourcemaps(),
|
||||
isProdBuild && terser(),
|
||||
].filter((p) => p),
|
||||
watch: {
|
||||
clearScreen: false,
|
||||
},
|
||||
},
|
||||
// Flow executor
|
||||
// Flow interface
|
||||
{
|
||||
input: "./src/interfaces/FlowInterface.ts",
|
||||
context: "window",
|
||||
@ -153,4 +120,70 @@ export default [
|
||||
clearScreen: false,
|
||||
},
|
||||
},
|
||||
// Admin interface
|
||||
{
|
||||
input: "./src/interfaces/AdminInterface.ts",
|
||||
context: "window",
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "dist",
|
||||
sourcemap: true,
|
||||
manualChunks: manualChunks,
|
||||
chunkFileNames: "admin-[name].js",
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
cssimport(),
|
||||
resolve({ extensions, browser: true }),
|
||||
commonjs(),
|
||||
babel({
|
||||
extensions,
|
||||
babelHelpers: "runtime",
|
||||
include: ["src/**/*"],
|
||||
}),
|
||||
replace({
|
||||
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
|
||||
"preventAssignment": true,
|
||||
}),
|
||||
sourcemaps(),
|
||||
isProdBuild && terser(),
|
||||
].filter((p) => p),
|
||||
watch: {
|
||||
clearScreen: false,
|
||||
},
|
||||
},
|
||||
// User interface
|
||||
{
|
||||
input: "./src/interfaces/UserInterface.ts",
|
||||
context: "window",
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "dist",
|
||||
sourcemap: true,
|
||||
manualChunks: manualChunks,
|
||||
chunkFileNames: "user-[name].js",
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
cssimport(),
|
||||
resolve({ extensions, browser: true }),
|
||||
commonjs(),
|
||||
babel({
|
||||
extensions,
|
||||
babelHelpers: "runtime",
|
||||
include: ["src/**/*"],
|
||||
}),
|
||||
replace({
|
||||
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
|
||||
"preventAssignment": true,
|
||||
}),
|
||||
sourcemaps(),
|
||||
isProdBuild && terser(),
|
||||
].filter((p) => p),
|
||||
watch: {
|
||||
clearScreen: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 508 KiB After Width: | Height: | Size: 756 KiB |
@ -74,7 +74,7 @@ html > form > input {
|
||||
}
|
||||
|
||||
.pf-m-success {
|
||||
color: var(--pf-global--success-color--100);
|
||||
color: var(--pf-global--success-color--100) !important;
|
||||
}
|
||||
.pf-m-warning {
|
||||
color: var(--pf-global--warning-color--100);
|
||||
@ -83,9 +83,6 @@ html > form > input {
|
||||
color: var(--pf-global--danger-color--100);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--ak-dark-background) !important;
|
||||
}
|
||||
.ak-static-page h1 {
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
@ -99,6 +96,9 @@ body {
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: var(--ak-dark-background) !important;
|
||||
}
|
||||
:root {
|
||||
--pf-global--Color--100: var(--ak-dark-foreground);
|
||||
--pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker);
|
||||
|
@ -1,9 +1,9 @@
|
||||
export const PRIMARY_CLASS = "pf-m-primary";
|
||||
export const SECONDARY_CLASS = "pf-m-secondary";
|
||||
export const SUCCESS_CLASS = "pf-m-success";
|
||||
export const ERROR_CLASS = "pf-m-danger";
|
||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
export const CURRENT_CLASS = "pf-m-current";
|
||||
export const VERSION = "2021.9.1-rc1";
|
||||
export const VERSION = "2021.9.1-rc3";
|
||||
export const PAGE_SIZE = 20;
|
||||
export const TITLE_DEFAULT = "authentik";
|
||||
export const ROUTE_SEPARATOR = ";";
|
||||
|
@ -12,7 +12,7 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";
|
||||
import AKGlobal from "../../authentik.css";
|
||||
import { PFSize } from "../Spinner";
|
||||
import { ERROR_CLASS, PRIMARY_CLASS, PROGRESS_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||
import { ERROR_CLASS, PROGRESS_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||
|
||||
@customElement("ak-spinner-button")
|
||||
export class SpinnerButton extends LitElement {
|
||||
@ -39,7 +39,6 @@ export class SpinnerButton extends LitElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.classList.add(PRIMARY_CLASS);
|
||||
}
|
||||
|
||||
setLoading(): void {
|
||||
@ -51,10 +50,10 @@ export class SpinnerButton extends LitElement {
|
||||
setDone(statusClass: string): void {
|
||||
this.isRunning = false;
|
||||
this.classList.remove(PROGRESS_CLASS);
|
||||
this.classList.replace(PRIMARY_CLASS, statusClass);
|
||||
this.classList.add(statusClass);
|
||||
this.requestUpdate();
|
||||
setTimeout(() => {
|
||||
this.classList.replace(statusClass, PRIMARY_CLASS);
|
||||
this.classList.remove(statusClass);
|
||||
this.requestUpdate();
|
||||
}, 1000);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { customElement, property } from "lit-element";
|
||||
import { CoreApi } from "@goauthentik/api";
|
||||
import { PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||
import { ERROR_CLASS, SECONDARY_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
import { ActionButton } from "./ActionButton";
|
||||
|
||||
@ -10,7 +10,7 @@ export class TokenCopyButton extends ActionButton {
|
||||
identifier?: string;
|
||||
|
||||
@property()
|
||||
buttonClass: string = PRIMARY_CLASS;
|
||||
buttonClass: string = SECONDARY_CLASS;
|
||||
|
||||
apiRequest: () => Promise<unknown> = () => {
|
||||
this.setLoading();
|
||||
@ -28,13 +28,17 @@ export class TokenCopyButton extends ActionButton {
|
||||
return navigator.clipboard.writeText(token.key).then(() => {
|
||||
this.buttonClass = SUCCESS_CLASS;
|
||||
setTimeout(() => {
|
||||
this.buttonClass = PRIMARY_CLASS;
|
||||
this.buttonClass = SECONDARY_CLASS;
|
||||
}, 1500);
|
||||
});
|
||||
})
|
||||
.catch((err: Response | undefined) => {
|
||||
this.buttonClass = ERROR_CLASS;
|
||||
return err?.json().then((errResp) => {
|
||||
throw new Error(errResp["detail"]);
|
||||
setTimeout(() => {
|
||||
this.buttonClass = SECONDARY_CLASS;
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -58,7 +58,7 @@ export class ObjectChangelog extends Table<Event> {
|
||||
? html`<small> ${t`On behalf of ${item.user.on_behalf_of.username}`} </small>`
|
||||
: html``}`,
|
||||
html`<span>${item.created?.toLocaleString()}</span>`,
|
||||
html`<span>${item.clientIp || "-"}</span>`,
|
||||
html`<span>${item.clientIp || t`-`}</span>`,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ export class ObjectChangelog extends Table<Event> {
|
||||
? html`<small> ${t`On behalf of ${item.user.on_behalf_of.username}`} </small>`
|
||||
: html``}`,
|
||||
html`<span>${item.created?.toLocaleString()}</span>`,
|
||||
html`<span>${item.clientIp || "-"}</span>`,
|
||||
html`<span>${item.clientIp || t`-`}</span>`,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ export class DeleteObjectsTable<T> extends Table<T> {
|
||||
|
||||
renderUsedBy(usedBy: UsedBy[]): TemplateResult {
|
||||
if (usedBy.length < 1) {
|
||||
return html` <span>${t`Not used by any other object.`}</span>`;
|
||||
return html`<span>${t`Not used by any other object.`}</span>`;
|
||||
}
|
||||
return html`<ul class="pf-c-list">
|
||||
${usedBy.map((ub) => {
|
||||
@ -179,7 +179,7 @@ export class DeleteBulkForm extends ModalButton {
|
||||
</section>
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<form class="pf-c-form pf-m-horizontal">
|
||||
<p>
|
||||
<p class="pf-c-title">
|
||||
${t`Are you sure you want to delete ${this.objects.length} ${this.objectLabel}?`}
|
||||
</p>
|
||||
</form>
|
||||
|
@ -141,6 +141,14 @@ export class Form<T> extends LitElement {
|
||||
element.type === "datetime-local"
|
||||
) {
|
||||
json[element.name] = new Date(element.valueAsNumber);
|
||||
} else if (
|
||||
element.tagName.toLowerCase() === "input" &&
|
||||
"type" in element.dataset &&
|
||||
element.dataset["type"] === "datetime-local"
|
||||
) {
|
||||
// Workaround for Firefox <93, since 92 and older don't support
|
||||
// datetime-local fields
|
||||
json[element.name] = new Date(element.value);
|
||||
} else if (element.tagName.toLowerCase() === "input" && element.type === "checkbox") {
|
||||
json[element.name] = element.checked;
|
||||
} else {
|
||||
|
@ -28,6 +28,15 @@ export class Route {
|
||||
return this;
|
||||
}
|
||||
|
||||
redirectRaw(to: string): Route {
|
||||
this.callback = () => {
|
||||
console.debug(`authentik/router: redirecting ${to}`);
|
||||
window.location.hash = `${to}`;
|
||||
return html``;
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
then(render: (args: RouteArgs) => TemplateResult): Route {
|
||||
this.callback = render;
|
||||
return this;
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { Route } from "./Route";
|
||||
import { ROUTES } from "../../routes";
|
||||
import { RouteMatch } from "./RouteMatch";
|
||||
import AKGlobal from "../../authentik.css";
|
||||
|
||||
@ -45,6 +44,9 @@ export class RouterOutlet extends LitElement {
|
||||
@property()
|
||||
defaultUrl?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
routes: Route[] = [];
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
AKGlobal,
|
||||
@ -59,8 +61,6 @@ export class RouterOutlet extends LitElement {
|
||||
}
|
||||
}
|
||||
*:first-child {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
`,
|
||||
@ -90,7 +90,7 @@ export class RouterOutlet extends LitElement {
|
||||
return;
|
||||
}
|
||||
let matchedRoute: RouteMatch | null = null;
|
||||
ROUTES.some((route) => {
|
||||
this.routes.some((route) => {
|
||||
const match = route.url.exec(activeUrl);
|
||||
if (match != null) {
|
||||
matchedRoute = new RouteMatch(route);
|
||||
|
@ -32,7 +32,7 @@ export class SidebarUser extends LitElement {
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
<a href="#/user" class="pf-c-nav__link user-avatar" id="user-settings">
|
||||
<a href="/if/user/#/settings" class="pf-c-nav__link user-avatar" id="user-settings">
|
||||
${until(
|
||||
me().then((u) => {
|
||||
return html`<img
|
||||
|
@ -39,6 +39,12 @@ export class AuthenticatedSessionList extends Table<AuthenticatedSession> {
|
||||
return html`<ak-forms-delete-bulk
|
||||
objectLabel=${t`Session(s)`}
|
||||
.objects=${this.selectedElements}
|
||||
.metadata=${(item: AuthenticatedSession) => {
|
||||
return [
|
||||
{ key: t`Last IP`, value: item.lastIp },
|
||||
{ key: t`Expiry`, value: item.expires?.toLocaleString() || t`-` },
|
||||
];
|
||||
}}
|
||||
.usedBy=${(item: AuthenticatedSession) => {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsUsedByList({
|
||||
uuid: item.uuid || "",
|
||||
|
@ -374,7 +374,7 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||
${this.challenge?.flowInfo?.background?.startsWith("/static")
|
||||
? html`
|
||||
<li>
|
||||
<a href="https://unsplash.com/@wckd_official"
|
||||
<a href="https://unsplash.com/@introspectivedsgn"
|
||||
>${t`Background image`}</a
|
||||
>
|
||||
</li>
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
import { AdminApi, Version } from "@goauthentik/api";
|
||||
import { DEFAULT_CONFIG } from "../api/Config";
|
||||
import { WebsocketClient } from "../common/ws";
|
||||
import { ROUTES } from "../routesAdmin";
|
||||
|
||||
@customElement("ak-interface-admin")
|
||||
export class AdminInterface extends LitElement {
|
||||
@ -110,7 +111,8 @@ export class AdminInterface extends LitElement {
|
||||
class="pf-c-page__main"
|
||||
tabindex="-1"
|
||||
id="main-content"
|
||||
defaultUrl="/library"
|
||||
defaultUrl="/administration/overview"
|
||||
.routes=${ROUTES}
|
||||
>
|
||||
</ak-router-outlet>
|
||||
</main>
|
||||
@ -135,9 +137,11 @@ export class AdminInterface extends LitElement {
|
||||
}
|
||||
|
||||
renderSidebarItems(): TemplateResult {
|
||||
const superUserCondition = () => {
|
||||
return me().then((u) => u.user.isSuperuser || false);
|
||||
};
|
||||
me().then((u) => {
|
||||
if (!u.user.isSuperuser) {
|
||||
window.location.assign("/if/user");
|
||||
}
|
||||
});
|
||||
return html`
|
||||
${until(
|
||||
this.version.then((version) => {
|
||||
@ -167,19 +171,16 @@ export class AdminInterface extends LitElement {
|
||||
return html``;
|
||||
}),
|
||||
)}
|
||||
<ak-sidebar-item path="/library">
|
||||
<span slot="label">${t`Library`}</span>
|
||||
<ak-sidebar-item path="/if/user/" ?isAbsoluteLink=${true} ?highlight=${true}>
|
||||
<span slot="label">${t`User interface`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item .condition=${superUserCondition}>
|
||||
<span slot="label">${t`Monitor`}</span>
|
||||
<ak-sidebar-item path="/administration/overview">
|
||||
<span slot="label">${t`Overview`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/administration/system-tasks">
|
||||
<span slot="label">${t`System Tasks`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/administration/overview">
|
||||
<span slot="label">${t`Overview`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item .condition=${superUserCondition}>
|
||||
<ak-sidebar-item path="/administration/system-tasks">
|
||||
<span slot="label">${t`System Tasks`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Resources`}</span>
|
||||
<ak-sidebar-item
|
||||
path="/core/applications"
|
||||
@ -203,7 +204,7 @@ export class AdminInterface extends LitElement {
|
||||
<span slot="label">${t`Tenants`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item .condition=${superUserCondition}>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Outposts`}</span>
|
||||
<ak-sidebar-item path="/outpost/outposts">
|
||||
<span slot="label">${t`Outposts`}</span>
|
||||
@ -212,7 +213,7 @@ export class AdminInterface extends LitElement {
|
||||
<span slot="label">${t`Integrations`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item .condition=${superUserCondition}>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Events`}</span>
|
||||
<ak-sidebar-item
|
||||
path="/events/log"
|
||||
@ -227,7 +228,7 @@ export class AdminInterface extends LitElement {
|
||||
<span slot="label">${t`Notification Transports`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item .condition=${superUserCondition}>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Customisation`}</span>
|
||||
<ak-sidebar-item path="/policy/policies">
|
||||
<span slot="label">${t`Policies`}</span>
|
||||
@ -242,7 +243,7 @@ export class AdminInterface extends LitElement {
|
||||
<span slot="label">${t`Property Mappings`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item .condition=${superUserCondition}>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Flows`}</span>
|
||||
<ak-sidebar-item
|
||||
path="/flow/flows"
|
||||
@ -260,7 +261,7 @@ export class AdminInterface extends LitElement {
|
||||
<span slot="label">${t`Invitations`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item .condition=${superUserCondition}>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Identity & Cryptography`}</span>
|
||||
<ak-sidebar-item
|
||||
path="/identity/users"
|
||||
|
288
web/src/interfaces/UserInterface.ts
Normal file
288
web/src/interfaces/UserInterface.ts
Normal file
@ -0,0 +1,288 @@
|
||||
import "../elements/messages/MessageContainer";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { me } from "../api/Users";
|
||||
import "./locale";
|
||||
import "../elements/sidebar/SidebarItem";
|
||||
import { t } from "@lingui/macro";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFBrand from "@patternfly/patternfly/components/Brand/brand.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
|
||||
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
|
||||
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
|
||||
import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";
|
||||
import AKGlobal from "../authentik.css";
|
||||
|
||||
import "../elements/router/RouterOutlet";
|
||||
import "../elements/messages/MessageContainer";
|
||||
import "../elements/notifications/NotificationDrawer";
|
||||
import "../elements/sidebar/Sidebar";
|
||||
import { EVENT_API_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE } from "../constants";
|
||||
import { CurrentTenant, EventsApi } from "@goauthentik/api";
|
||||
import { DEFAULT_CONFIG, tenant } from "../api/Config";
|
||||
import { WebsocketClient } from "../common/ws";
|
||||
import { ROUTES } from "../routesUser";
|
||||
import { first } from "../utils";
|
||||
import { DefaultTenant } from "../elements/sidebar/SidebarBrand";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { uiConfig } from "../user/config";
|
||||
|
||||
@customElement("ak-interface-user")
|
||||
export class UserInterface extends LitElement {
|
||||
@property({ type: Boolean })
|
||||
notificationOpen = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
apiDrawerOpen = false;
|
||||
|
||||
ws: WebsocketClient;
|
||||
|
||||
@property({ attribute: false })
|
||||
tenant: CurrentTenant = DefaultTenant;
|
||||
|
||||
@property({ type: Number })
|
||||
notificationsCount = -1;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFBrand,
|
||||
PFPage,
|
||||
PFAvatar,
|
||||
PFButton,
|
||||
PFDrawer,
|
||||
PFDropdown,
|
||||
PFNotificationBadge,
|
||||
AKGlobal,
|
||||
css`
|
||||
.pf-c-page__main,
|
||||
.pf-c-drawer__content,
|
||||
.pf-c-page__drawer {
|
||||
z-index: auto !important;
|
||||
}
|
||||
.display-none {
|
||||
display: none;
|
||||
}
|
||||
.pf-c-brand {
|
||||
min-height: 48px;
|
||||
}
|
||||
.has-notifications {
|
||||
color: #2b9af3;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.ws = new WebsocketClient();
|
||||
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
|
||||
this.notificationOpen = !this.notificationOpen;
|
||||
});
|
||||
window.addEventListener(EVENT_API_DRAWER_TOGGLE, () => {
|
||||
this.apiDrawerOpen = !this.apiDrawerOpen;
|
||||
});
|
||||
tenant().then((tenant) => (this.tenant = tenant));
|
||||
new EventsApi(DEFAULT_CONFIG)
|
||||
.eventsNotificationsList({
|
||||
seen: false,
|
||||
ordering: "-created",
|
||||
pageSize: 1,
|
||||
})
|
||||
.then((r) => {
|
||||
this.notificationsCount = r.pagination.count;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html` <div class="pf-c-page">
|
||||
<header class="pf-c-page__header">
|
||||
<div class="pf-c-page__header-brand">
|
||||
<a href="#/" class="pf-c-page__header-brand-link">
|
||||
<img
|
||||
class="pf-c-brand"
|
||||
src="${first(this.tenant.brandingLogo, DefaultTenant.brandingLogo)}"
|
||||
alt="${(this.tenant.brandingTitle, DefaultTenant.brandingTitle)}"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pf-c-page__header-tools">
|
||||
<div class="pf-c-page__header-tools-group">
|
||||
${until(
|
||||
uiConfig().then((config) => {
|
||||
if (!config.enabledFeatures.apiDrawer) {
|
||||
return html``;
|
||||
}
|
||||
return html`<div
|
||||
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
|
||||
>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.apiDrawerOpen = !this.apiDrawerOpen;
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-code" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>`;
|
||||
}),
|
||||
)}
|
||||
${until(
|
||||
uiConfig().then((config) => {
|
||||
if (!config.enabledFeatures.notificationDrawer) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
aria-label="${t`Unread notifications`}"
|
||||
@click=${() => {
|
||||
this.notificationOpen = !this.notificationOpen;
|
||||
}}
|
||||
>
|
||||
<span
|
||||
class="pf-c-notification-badge ${this
|
||||
.notificationsCount > 0
|
||||
? "pf-m-unread"
|
||||
: ""}"
|
||||
>
|
||||
<i class="pf-icon-bell" aria-hidden="true"></i>
|
||||
<span class="pf-c-notification-badge__count"
|
||||
>${this.notificationsCount}</span
|
||||
>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
}),
|
||||
)}
|
||||
${until(
|
||||
uiConfig().then((config) => {
|
||||
if (!config.enabledFeatures.settings) {
|
||||
return html``;
|
||||
}
|
||||
return html` <div
|
||||
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
|
||||
>
|
||||
<a
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
href="#/settings"
|
||||
>
|
||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>`;
|
||||
}),
|
||||
)}
|
||||
<a href="/flows/-/default/invalidation/" class="pf-c-button pf-m-plain">
|
||||
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
|
||||
</a>
|
||||
${until(
|
||||
me().then((u) => {
|
||||
if (!u.user.isSuperuser) return html``;
|
||||
return html`
|
||||
<a class="pf-c-button pf-m-primary pf-m-small" href="/if/admin">
|
||||
${t`Admin interface`}
|
||||
</a>
|
||||
`;
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
${until(
|
||||
me().then((u) => {
|
||||
if (u.original) {
|
||||
return html`<div class="pf-c-page__header-tools">
|
||||
<div class="pf-c-page__header-tools-group">
|
||||
<a
|
||||
class="pf-c-button pf-m-warning pf-m-small"
|
||||
href=${`/-/impersonation/end/?back=${window.location.pathname}%23${window.location.hash}`}
|
||||
>
|
||||
${t`Stop impersonation`}
|
||||
</a>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
return html``;
|
||||
}),
|
||||
)}
|
||||
<div class="pf-c-page__header-tools-group">
|
||||
<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md">
|
||||
${until(
|
||||
uiConfig().then((config) => {
|
||||
return me().then((me) => {
|
||||
switch (config.navbar.userDisplay) {
|
||||
case "username":
|
||||
return me.user.username;
|
||||
case "name":
|
||||
return me.user.name;
|
||||
case "email":
|
||||
return me.user.email;
|
||||
default:
|
||||
return me.user.username;
|
||||
}
|
||||
});
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
${until(
|
||||
me().then((me) => {
|
||||
return html`<img
|
||||
class="pf-c-avatar"
|
||||
src=${me.user.avatar}
|
||||
alt="${t`Avatar image`}"
|
||||
/>`;
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
<div class="pf-c-page__drawer">
|
||||
<div
|
||||
class="pf-c-drawer ${this.notificationOpen || this.apiDrawerOpen
|
||||
? "pf-m-expanded"
|
||||
: "pf-m-collapsed"}"
|
||||
>
|
||||
<div class="pf-c-drawer__main">
|
||||
<div class="pf-c-drawer__content">
|
||||
<div class="pf-c-drawer__body">
|
||||
<main class="pf-c-page__main">
|
||||
<ak-router-outlet
|
||||
role="main"
|
||||
class="pf-l-bullseye__item pf-c-page__main"
|
||||
tabindex="-1"
|
||||
id="main-content"
|
||||
defaultUrl="/library"
|
||||
.routes=${ROUTES}
|
||||
>
|
||||
</ak-router-outlet>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<ak-notification-drawer
|
||||
class="pf-c-drawer__panel pf-m-width-33 ${this.notificationOpen
|
||||
? ""
|
||||
: "display-none"}"
|
||||
?hidden=${!this.notificationOpen}
|
||||
></ak-notification-drawer>
|
||||
<ak-api-drawer
|
||||
class="pf-c-drawer__panel pf-m-width-33 ${this.apiDrawerOpen
|
||||
? ""
|
||||
: "display-none"}"
|
||||
?hidden=${!this.apiDrawerOpen}
|
||||
></ak-api-drawer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/page.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/empty-state.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/spinner.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css" />
|
||||
<script src="/static/dist/poly.js" type="module"></script>
|
||||
<script>
|
||||
window["polymerSkipLoadingFontRoboto"] = true;
|
||||
</script>
|
||||
<script src="/static/dist/AdminInterface.js" type="module"></script>
|
||||
<title>authentik</title>
|
||||
</head>
|
||||
<body>
|
||||
<ak-message-container></ak-message-container>
|
||||
<ak-interface-admin>
|
||||
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
|
||||
<div class="pf-c-empty-state" style="height: 100vh">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<span
|
||||
class="pf-c-spinner pf-m-xl pf-c-empty-state__icon"
|
||||
role="progressbar"
|
||||
aria-valuetext="Loading..."
|
||||
>
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
<h1 class="pf-c-title pf-m-lg">Loading...</h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</ak-interface-admin>
|
||||
</body>
|
||||
</html>
|
@ -1,40 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/page.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/empty-state.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/spinner.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css" />
|
||||
<script>
|
||||
ShadyDOM = { force: !navigator.webdriver };
|
||||
window["polymerSkipLoadingFontRoboto"] = true;
|
||||
</script>
|
||||
<script src="/static/dist/poly.js" type="module"></script>
|
||||
<script src="/static/dist/FlowInterface.js" type="module"></script>
|
||||
<title>authentik</title>
|
||||
</head>
|
||||
<body>
|
||||
<ak-message-container></ak-message-container>
|
||||
<ak-flow-executor>
|
||||
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
|
||||
<div class="pf-c-empty-state" style="height: 100vh">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<span
|
||||
class="pf-c-spinner pf-m-xl pf-c-empty-state__icon"
|
||||
role="progressbar"
|
||||
aria-valuetext="Loading..."
|
||||
>
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
<h1 class="pf-c-title pf-m-lg">Loading...</h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</ak-flow-executor>
|
||||
</body>
|
||||
</html>
|
@ -21,7 +21,26 @@ msgstr ""
|
||||
msgid "(Format: hours=-1;minutes=-2;seconds=-3)."
|
||||
msgstr "(Format: hours=-1;minutes=-2;seconds=-3)."
|
||||
|
||||
#: src/elements/events/ObjectChangelog.ts
|
||||
#: src/elements/events/UserEvents.ts
|
||||
#: src/elements/user/SessionList.ts
|
||||
#: src/pages/applications/ApplicationListPage.ts
|
||||
#: src/pages/events/EventListPage.ts
|
||||
#: src/pages/events/EventListPage.ts
|
||||
#: src/pages/groups/GroupListPage.ts
|
||||
#: src/pages/groups/MemberSelectModal.ts
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "-"
|
||||
msgstr "-"
|
||||
|
||||
@ -167,6 +186,14 @@ msgstr "Additional group DN, prepended to the Base DN."
|
||||
msgid "Additional user DN, prepended to the Base DN."
|
||||
msgstr "Additional user DN, prepended to the Base DN."
|
||||
|
||||
#:
|
||||
#~ msgid "Admin"
|
||||
#~ msgstr "Admin"
|
||||
|
||||
#: src/interfaces/UserInterface.ts
|
||||
msgid "Admin interface"
|
||||
msgstr "Admin interface"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
@ -259,6 +286,7 @@ msgid "Application"
|
||||
msgstr "Application"
|
||||
|
||||
#: src/pages/applications/ApplicationListPage.ts
|
||||
#: src/user/LibraryApplication.ts
|
||||
msgid "Application Icon"
|
||||
msgstr "Application Icon"
|
||||
|
||||
@ -279,7 +307,6 @@ msgid "Application(s)"
|
||||
msgstr "Application(s)"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/LibraryPage.ts
|
||||
#: src/pages/applications/ApplicationListPage.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
msgid "Applications"
|
||||
@ -380,7 +407,7 @@ msgid "Authentication flow"
|
||||
msgstr "Authentication flow"
|
||||
|
||||
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
msgid "Authenticator"
|
||||
msgstr "Authenticator"
|
||||
|
||||
@ -416,6 +443,10 @@ msgstr "Authorize URL"
|
||||
msgid "Authorized application:"
|
||||
msgstr "Authorized application:"
|
||||
|
||||
#: src/interfaces/UserInterface.ts
|
||||
msgid "Avatar image"
|
||||
msgstr "Avatar image"
|
||||
|
||||
#: src/pages/stages/password/PasswordStageForm.ts
|
||||
msgid "Backends"
|
||||
msgstr "Backends"
|
||||
@ -601,7 +632,7 @@ msgstr "Certificate/Key used for authentication. Can be left empty for no authen
|
||||
msgid "Certificates"
|
||||
msgstr "Certificates"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsPassword.ts
|
||||
#: src/user/user-settings/stages/UserSettingsPassword.ts
|
||||
msgid "Change password"
|
||||
msgstr "Change password"
|
||||
|
||||
@ -609,7 +640,7 @@ msgstr "Change password"
|
||||
msgid "Change status"
|
||||
msgstr "Change status"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsPassword.ts
|
||||
#: src/user/user-settings/stages/UserSettingsPassword.ts
|
||||
msgid "Change your password"
|
||||
msgstr "Change your password"
|
||||
|
||||
@ -787,7 +818,7 @@ msgstr "Configuration flow"
|
||||
msgid "Configuration stage"
|
||||
msgstr "Configuration stage"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
msgid "Configure WebAuthn"
|
||||
msgstr "Configure WebAuthn"
|
||||
|
||||
@ -819,9 +850,10 @@ msgstr "Configure how the flow executor should handle an invalid response to a c
|
||||
msgid "Configure how the issuer field of the ID Token should be filled."
|
||||
msgstr "Configure how the issuer field of the ID Token should be filled."
|
||||
|
||||
#: src/pages/user-settings/UserSettingsPage.ts
|
||||
msgid "Configure settings relevant to your user profile."
|
||||
msgstr "Configure settings relevant to your user profile."
|
||||
#:
|
||||
#:
|
||||
#~ msgid "Configure settings relevant to your user profile."
|
||||
#~ msgstr "Configure settings relevant to your user profile."
|
||||
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
msgid "Configure the maximum allowed time drift for an asseration."
|
||||
@ -835,12 +867,16 @@ msgstr "Configure visual settings and defaults for different domains."
|
||||
msgid "Configure what data should be used as unique User Identifier. For most cases, the default should be fine."
|
||||
msgstr "Configure what data should be used as unique User Identifier. For most cases, the default should be fine."
|
||||
|
||||
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
|
||||
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
|
||||
msgid "Connect"
|
||||
msgstr "Connect"
|
||||
|
||||
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
|
||||
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
|
||||
#: src/user/user-settings/UserSettingsPage.ts
|
||||
msgid "Connected services"
|
||||
msgstr "Connected services"
|
||||
|
||||
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
|
||||
#: src/user/user-settings/sources/SourceSettingsPlex.ts
|
||||
msgid "Connected."
|
||||
msgstr "Connected."
|
||||
|
||||
@ -853,9 +889,9 @@ msgstr "Connection error, reconnecting..."
|
||||
msgid "Connection settings"
|
||||
msgstr "Connection settings"
|
||||
|
||||
#:
|
||||
#~ msgid "Consent"
|
||||
#~ msgstr "Consent"
|
||||
#: src/user/user-settings/UserSettingsPage.ts
|
||||
msgid "Consent"
|
||||
msgstr "Consent"
|
||||
|
||||
#: src/pages/stages/consent/ConsentStageForm.ts
|
||||
msgid "Consent expires in"
|
||||
@ -926,7 +962,7 @@ msgstr "Cookie domain"
|
||||
msgid "Copy"
|
||||
msgstr "Copy"
|
||||
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Copy Key"
|
||||
msgstr "Copy Key"
|
||||
|
||||
@ -983,16 +1019,16 @@ msgstr "Copy recovery link"
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Create"
|
||||
msgstr "Create"
|
||||
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Create App password"
|
||||
msgstr "Create App password"
|
||||
|
||||
@ -1068,8 +1104,8 @@ msgid "Create Tenant"
|
||||
msgstr "Create Tenant"
|
||||
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Create Token"
|
||||
msgstr "Create Token"
|
||||
|
||||
@ -1105,7 +1141,7 @@ msgstr "Create {0}"
|
||||
msgid "Created by"
|
||||
msgstr "Created by"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
msgid "Created {0}"
|
||||
msgstr "Created {0}"
|
||||
|
||||
@ -1188,9 +1224,9 @@ msgstr "Define how notifications are sent to users, like Email or Webhook."
|
||||
#: src/pages/stages/prompt/PromptListPage.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
@ -1215,7 +1251,7 @@ msgstr "Delete"
|
||||
#~ msgid "Delete Session"
|
||||
#~ msgstr "Delete Session"
|
||||
|
||||
#: src/pages/user-settings/UserSelfForm.ts
|
||||
#: src/user/user-settings/UserSelfForm.ts
|
||||
msgid "Delete account"
|
||||
msgstr "Delete account"
|
||||
|
||||
@ -1252,7 +1288,7 @@ msgstr "Deny the user access"
|
||||
#: src/pages/property-mappings/PropertyMappingScopeForm.ts
|
||||
#: src/pages/system-tasks/SystemTaskListPage.ts
|
||||
#: src/pages/tokens/TokenForm.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenForm.ts
|
||||
#: src/user/user-settings/tokens/UserTokenForm.ts
|
||||
msgid "Description"
|
||||
msgstr "Description"
|
||||
|
||||
@ -1293,7 +1329,7 @@ msgstr "Device classes"
|
||||
msgid "Device classes which can be used to authenticate."
|
||||
msgstr "Device classes which can be used to authenticate."
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
msgid "Device name"
|
||||
msgstr "Device name"
|
||||
|
||||
@ -1311,15 +1347,15 @@ msgstr "Digits"
|
||||
#~ msgid "Disable"
|
||||
#~ msgstr "Disable"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
|
||||
msgid "Disable Duo authenticator"
|
||||
msgstr "Disable Duo authenticator"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
|
||||
msgid "Disable Static Tokens"
|
||||
msgstr "Disable Static Tokens"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
|
||||
msgid "Disable Time-based OTP"
|
||||
msgstr "Disable Time-based OTP"
|
||||
|
||||
@ -1327,8 +1363,8 @@ msgstr "Disable Time-based OTP"
|
||||
msgid "Disabled"
|
||||
msgstr "Disabled"
|
||||
|
||||
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
|
||||
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
|
||||
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
|
||||
#: src/user/user-settings/sources/SourceSettingsPlex.ts
|
||||
msgid "Disconnect"
|
||||
msgstr "Disconnect"
|
||||
|
||||
@ -1363,7 +1399,7 @@ msgstr "Due to protocol limitations, this certificate is only used when the outp
|
||||
msgid "Dummy stage used for testing. Shows a simple continue button and always passes."
|
||||
msgstr "Dummy stage used for testing. Shows a simple continue button and always passes."
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
|
||||
msgid "Duo"
|
||||
msgstr "Duo"
|
||||
|
||||
@ -1422,16 +1458,16 @@ msgstr "Edit Stage"
|
||||
msgid "Edit User"
|
||||
msgstr "Edit User"
|
||||
|
||||
#: src/pages/LibraryPage.ts
|
||||
#: src/user/LibraryPage.ts
|
||||
msgid "Either no applications are defined, or you don't have access to any."
|
||||
msgstr "Either no applications are defined, or you don't have access to any."
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
#: src/pages/events/TransportForm.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/user-settings/UserSelfForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
#: src/user/user-settings/UserSelfForm.ts
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
@ -1468,7 +1504,7 @@ msgstr "Embedded outpost is not configured correctly."
|
||||
#~ msgid "Enable"
|
||||
#~ msgstr "Enable"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
|
||||
msgid "Enable Duo authenticator"
|
||||
msgstr "Enable Duo authenticator"
|
||||
|
||||
@ -1476,11 +1512,11 @@ msgstr "Enable Duo authenticator"
|
||||
msgid "Enable StartTLS"
|
||||
msgstr "Enable StartTLS"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
|
||||
msgid "Enable Static Tokens"
|
||||
msgstr "Enable Static Tokens"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
|
||||
msgid "Enable TOTP"
|
||||
msgstr "Enable TOTP"
|
||||
|
||||
@ -1532,11 +1568,11 @@ msgstr "Error when creating credential: {err}"
|
||||
msgid "Error when validating assertion on server: {err}"
|
||||
msgstr "Error when validating assertion on server: {err}"
|
||||
|
||||
#: src/pages/user-settings/UserSettingsPage.ts
|
||||
#: src/user/user-settings/sources/SourceSettings.ts
|
||||
msgid "Error: unsupported source settings: {0}"
|
||||
msgstr "Error: unsupported source settings: {0}"
|
||||
|
||||
#: src/pages/user-settings/UserSettingsPage.ts
|
||||
#: src/user/user-settings/stages/StageSettings.ts
|
||||
msgid "Error: unsupported stage settings: {0}"
|
||||
msgstr "Error: unsupported stage settings: {0}"
|
||||
|
||||
@ -1620,8 +1656,8 @@ msgstr "Expires on"
|
||||
msgid "Expires?"
|
||||
msgstr "Expires?"
|
||||
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Expiring"
|
||||
msgstr "Expiring"
|
||||
|
||||
@ -1629,6 +1665,7 @@ msgstr "Expiring"
|
||||
msgid "Expiring?"
|
||||
msgstr "Expiring?"
|
||||
|
||||
#: src/elements/user/SessionList.ts
|
||||
#: src/pages/crypto/CertificateKeyPairListPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
msgid "Expiry"
|
||||
@ -1884,6 +1921,10 @@ msgstr "Generate"
|
||||
msgid "Generate Certificate-Key Pair"
|
||||
msgstr "Generate Certificate-Key Pair"
|
||||
|
||||
#:
|
||||
#~ msgid "Go to admin interface"
|
||||
#~ msgstr "Go to admin interface"
|
||||
|
||||
#: src/elements/table/TablePagination.ts
|
||||
msgid "Go to next page"
|
||||
msgstr "Go to next page"
|
||||
@ -1892,6 +1933,10 @@ msgstr "Go to next page"
|
||||
msgid "Go to previous page"
|
||||
msgstr "Go to previous page"
|
||||
|
||||
#:
|
||||
#~ msgid "Go to user interface"
|
||||
#~ msgstr "Go to user interface"
|
||||
|
||||
#: src/pages/events/RuleForm.ts
|
||||
#: src/pages/policies/PolicyBindingForm.ts
|
||||
#: src/pages/policies/PolicyBindingForm.ts
|
||||
@ -2027,8 +2072,8 @@ msgstr "Icon shown in the browser tab."
|
||||
#: src/pages/system-tasks/SystemTaskListPage.ts
|
||||
#: src/pages/tokens/TokenForm.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenForm.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenForm.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Identifier"
|
||||
msgstr "Identifier"
|
||||
|
||||
@ -2133,7 +2178,7 @@ msgstr "Integrations"
|
||||
|
||||
#: src/pages/tokens/TokenForm.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Intent"
|
||||
msgstr "Intent"
|
||||
|
||||
@ -2238,6 +2283,7 @@ msgstr "Label"
|
||||
msgid "Label shown next to/above the prompt."
|
||||
msgstr "Label shown next to/above the prompt."
|
||||
|
||||
#: src/elements/user/SessionList.ts
|
||||
#: src/elements/user/SessionList.ts
|
||||
msgid "Last IP"
|
||||
msgstr "Last IP"
|
||||
@ -2274,9 +2320,9 @@ msgstr "Launch URL"
|
||||
msgid "Let the user identify themselves with their username or Email address."
|
||||
msgstr "Let the user identify themselves with their username or Email address."
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Library"
|
||||
msgstr "Library"
|
||||
#:
|
||||
#~ msgid "Library"
|
||||
#~ msgstr "Library"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
@ -2321,7 +2367,9 @@ msgstr "Load servers"
|
||||
#: src/flows/stages/prompt/PromptStage.ts
|
||||
#: src/pages/applications/ApplicationViewPage.ts
|
||||
#: src/pages/applications/ApplicationViewPage.ts
|
||||
#: src/pages/user-settings/UserSelfForm.ts
|
||||
#: src/user/user-settings/UserSelfForm.ts
|
||||
#: src/user/user-settings/sources/SourceSettings.ts
|
||||
#: src/user/user-settings/stages/StageSettings.ts
|
||||
#: src/utils.ts
|
||||
msgid "Loading"
|
||||
msgstr "Loading"
|
||||
@ -2541,14 +2589,18 @@ msgstr "Model deleted"
|
||||
msgid "Model updated"
|
||||
msgstr "Model updated"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Monitor"
|
||||
msgstr "Monitor"
|
||||
#:
|
||||
#~ msgid "Monitor"
|
||||
#~ msgstr "Monitor"
|
||||
|
||||
#: src/pages/LibraryPage.ts
|
||||
#: src/user/LibraryPage.ts
|
||||
msgid "My Applications"
|
||||
msgstr "My Applications"
|
||||
|
||||
#: src/user/LibraryPage.ts
|
||||
msgid "My applications"
|
||||
msgstr "My applications"
|
||||
|
||||
#: src/elements/forms/DeleteBulkForm.ts
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
#: src/pages/applications/ApplicationListPage.ts
|
||||
@ -2622,11 +2674,11 @@ msgstr "My Applications"
|
||||
#: src/pages/stages/user_login/UserLoginStageForm.ts
|
||||
#: src/pages/stages/user_logout/UserLogoutStageForm.ts
|
||||
#: src/pages/stages/user_write/UserWriteStageForm.ts
|
||||
#: src/pages/user-settings/UserSelfForm.ts
|
||||
#: src/pages/users/GroupSelectModal.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
#: src/user/user-settings/UserSelfForm.ts
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
@ -2673,13 +2725,13 @@ msgstr "Newly created users are added to this group, if a group is selected."
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/users/GroupSelectModal.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
#: src/pages/LibraryPage.ts
|
||||
#: src/user/LibraryPage.ts
|
||||
msgid "No Applications available."
|
||||
msgstr "No Applications available."
|
||||
|
||||
@ -2745,8 +2797,8 @@ msgstr "Not available"
|
||||
msgid "Not configured action"
|
||||
msgstr "Not configured action"
|
||||
|
||||
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
|
||||
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
|
||||
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
|
||||
#: src/user/user-settings/sources/SourceSettingsPlex.ts
|
||||
msgid "Not connected."
|
||||
msgstr "Not connected."
|
||||
|
||||
@ -3011,6 +3063,10 @@ msgstr "Password set"
|
||||
msgid "Password stage"
|
||||
msgstr "Password stage"
|
||||
|
||||
#: src/user/user-settings/UserSettingsPage.ts
|
||||
msgid "Password, 2FA, etc"
|
||||
msgstr "Password, 2FA, etc"
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "Password: Masked input, password is validated against sources. Policies still have to be applied to this Stage. If two of these are used in the same stage, they are ensured to be identical."
|
||||
msgstr "Password: Masked input, password is validated against sources. Policies still have to be applied to this Stage. If two of these are used in the same stage, they are ensured to be identical."
|
||||
@ -3411,9 +3467,9 @@ msgstr "Required"
|
||||
msgid "Required."
|
||||
msgstr "Required."
|
||||
|
||||
#: src/pages/user-settings/UserSelfForm.ts
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
#: src/user/user-settings/UserSelfForm.ts
|
||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
|
||||
@ -3430,10 +3486,9 @@ msgstr "Resources"
|
||||
msgid "Result"
|
||||
msgstr "Result"
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceViewPage.ts
|
||||
#: src/pages/system-tasks/SystemTaskListPage.ts
|
||||
msgid "Retry Task"
|
||||
msgstr "Retry Task"
|
||||
#:
|
||||
#~ msgid "Retry Task"
|
||||
#~ msgstr "Retry Task"
|
||||
|
||||
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts
|
||||
msgid "Retry authentication"
|
||||
@ -3457,6 +3512,10 @@ msgstr "Return to device picker"
|
||||
msgid "Revoked?"
|
||||
msgstr "Revoked?"
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceViewPage.ts
|
||||
msgid "Run sync again"
|
||||
msgstr "Run sync again"
|
||||
|
||||
#: src/pages/property-mappings/PropertyMappingSAMLForm.ts
|
||||
msgid "SAML Attribute Name"
|
||||
msgstr "SAML Attribute Name"
|
||||
@ -3511,6 +3570,10 @@ msgstr "SSO URL"
|
||||
msgid "Same identifier is used for all providers"
|
||||
msgstr "Same identifier is used for all providers"
|
||||
|
||||
#: src/user/user-settings/UserSelfForm.ts
|
||||
msgid "Save"
|
||||
msgstr "Save"
|
||||
|
||||
#: src/pages/property-mappings/PropertyMappingScopeForm.ts
|
||||
msgid "Scope name"
|
||||
msgstr "Scope name"
|
||||
@ -3531,6 +3594,7 @@ msgid "Score"
|
||||
msgstr "Score"
|
||||
|
||||
#: src/elements/table/TableSearch.ts
|
||||
#: src/user/LibraryPage.ts
|
||||
msgid "Search..."
|
||||
msgstr "Search..."
|
||||
|
||||
@ -3701,6 +3765,7 @@ msgid "Session(s)"
|
||||
msgstr "Session(s)"
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
#: src/user/user-settings/UserSettingsPage.ts
|
||||
msgid "Sessions"
|
||||
msgstr "Sessions"
|
||||
|
||||
@ -3788,8 +3853,8 @@ msgstr "Something went wrong! Please try again later."
|
||||
msgid "Source linked"
|
||||
msgstr "Source linked"
|
||||
|
||||
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
|
||||
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
|
||||
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
|
||||
#: src/user/user-settings/sources/SourceSettingsPlex.ts
|
||||
msgid "Source {0}"
|
||||
msgstr "Source {0}"
|
||||
|
||||
@ -3899,7 +3964,7 @@ msgstr "State"
|
||||
msgid "Static Tokens"
|
||||
msgstr "Static Tokens"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
|
||||
msgid "Static tokens"
|
||||
msgstr "Static tokens"
|
||||
|
||||
@ -3915,18 +3980,22 @@ msgstr "Statically deny the flow. To use this stage effectively, disable *Evalua
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
|
||||
msgid "Status: Disabled"
|
||||
msgstr "Status: Disabled"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
|
||||
msgid "Status: Enabled"
|
||||
msgstr "Status: Enabled"
|
||||
|
||||
#: src/interfaces/UserInterface.ts
|
||||
msgid "Stop impersonation"
|
||||
msgstr "Stop impersonation"
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "Subject"
|
||||
@ -4064,7 +4133,7 @@ msgid "Successfully created tenant."
|
||||
msgstr "Successfully created tenant."
|
||||
|
||||
#: src/pages/tokens/TokenForm.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenForm.ts
|
||||
#: src/user/user-settings/tokens/UserTokenForm.ts
|
||||
msgid "Successfully created token."
|
||||
msgstr "Successfully created token."
|
||||
|
||||
@ -4122,11 +4191,11 @@ msgstr "Successfully updated binding."
|
||||
msgid "Successfully updated certificate-key pair."
|
||||
msgstr "Successfully updated certificate-key pair."
|
||||
|
||||
#: src/pages/user-settings/UserSelfForm.ts
|
||||
#: src/user/user-settings/UserSelfForm.ts
|
||||
msgid "Successfully updated details."
|
||||
msgstr "Successfully updated details."
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
msgid "Successfully updated device."
|
||||
msgstr "Successfully updated device."
|
||||
|
||||
@ -4221,7 +4290,7 @@ msgid "Successfully updated tenant."
|
||||
msgstr "Successfully updated tenant."
|
||||
|
||||
#: src/pages/tokens/TokenForm.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenForm.ts
|
||||
#: src/user/user-settings/tokens/UserTokenForm.ts
|
||||
msgid "Successfully updated token."
|
||||
msgstr "Successfully updated token."
|
||||
|
||||
@ -4464,7 +4533,7 @@ msgstr "Time in minutes the token sent is valid."
|
||||
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
|
||||
msgstr "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
|
||||
msgid "Time-based One-Time Passwords"
|
||||
msgstr "Time-based One-Time Passwords"
|
||||
|
||||
@ -4511,7 +4580,7 @@ msgid "Token validity"
|
||||
msgstr "Token validity"
|
||||
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Token(s)"
|
||||
msgstr "Token(s)"
|
||||
|
||||
@ -4524,7 +4593,7 @@ msgstr "Tokens"
|
||||
msgid "Tokens & App passwords"
|
||||
msgstr "Tokens & App passwords"
|
||||
|
||||
#: src/pages/user-settings/UserSettingsPage.ts
|
||||
#: src/user/user-settings/UserSettingsPage.ts
|
||||
msgid "Tokens and App passwords"
|
||||
msgstr "Tokens and App passwords"
|
||||
|
||||
@ -4645,6 +4714,10 @@ msgstr "Unknown"
|
||||
#~ msgid "Unmanaged"
|
||||
#~ msgstr "Unmanaged"
|
||||
|
||||
#: src/interfaces/UserInterface.ts
|
||||
msgid "Unread notifications"
|
||||
msgstr "Unread notifications"
|
||||
|
||||
#: src/pages/admin-overview/charts/LDAPSyncStatusChart.ts
|
||||
msgid "Unsynced sources"
|
||||
msgstr "Unsynced sources"
|
||||
@ -4684,14 +4757,13 @@ msgstr "Up-to-date!"
|
||||
#: src/pages/stages/prompt/PromptListPage.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/UserSelfForm.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/users/UserActiveForm.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Update"
|
||||
msgstr "Update"
|
||||
|
||||
@ -4774,7 +4846,7 @@ msgid "Update Tenant"
|
||||
msgstr "Update Tenant"
|
||||
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Update Token"
|
||||
msgstr "Update Token"
|
||||
|
||||
@ -4788,7 +4860,7 @@ msgstr "Update User"
|
||||
msgid "Update available"
|
||||
msgstr "Update available"
|
||||
|
||||
#: src/pages/user-settings/UserSettingsPage.ts
|
||||
#: src/user/user-settings/UserSettingsPage.ts
|
||||
msgid "Update details"
|
||||
msgstr "Update details"
|
||||
|
||||
@ -4865,8 +4937,8 @@ msgstr "Use this tenant for each domain that doesn't have a dedicated tenant."
|
||||
#: src/pages/property-mappings/PropertyMappingTestForm.ts
|
||||
#: src/pages/tokens/TokenForm.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "User"
|
||||
msgstr "User"
|
||||
|
||||
@ -4883,9 +4955,10 @@ msgstr "User Property Mappings"
|
||||
msgid "User Reputation"
|
||||
msgstr "User Reputation"
|
||||
|
||||
#: src/pages/user-settings/UserSettingsPage.ts
|
||||
msgid "User Settings"
|
||||
msgstr "User Settings"
|
||||
#:
|
||||
#:
|
||||
#~ msgid "User Settings"
|
||||
#~ msgstr "User Settings"
|
||||
|
||||
#: src/pages/stages/password/PasswordStageForm.ts
|
||||
msgid "User database + LDAP password"
|
||||
@ -4899,7 +4972,7 @@ msgstr "User database + app passwords"
|
||||
msgid "User database + standard password"
|
||||
msgstr "User database + standard password"
|
||||
|
||||
#: src/pages/user-settings/UserSettingsPage.ts
|
||||
#: src/user/user-settings/UserSettingsPage.ts
|
||||
msgid "User details"
|
||||
msgstr "User details"
|
||||
|
||||
@ -4911,6 +4984,10 @@ msgstr "User events"
|
||||
msgid "User fields"
|
||||
msgstr "User fields"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "User interface"
|
||||
msgstr "User interface"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
msgid "User matching mode"
|
||||
@ -4941,8 +5018,8 @@ msgstr "User {0}"
|
||||
msgid "User's avatar"
|
||||
msgstr "User's avatar"
|
||||
|
||||
#: src/pages/user-settings/UserSelfForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
#: src/user/user-settings/UserSelfForm.ts
|
||||
msgid "User's display name."
|
||||
msgstr "User's display name."
|
||||
|
||||
@ -4965,12 +5042,12 @@ msgstr "Userinfo URL"
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
#: src/pages/policies/reputation/UserReputationListPage.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/user-settings/UserSelfForm.ts
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
#: src/pages/users/UserForm.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
#: src/user/user-settings/UserSelfForm.ts
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
|
||||
@ -5000,6 +5077,10 @@ msgstr "Using flow"
|
||||
msgid "Using source"
|
||||
msgstr "Using source"
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
msgid "Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List."
|
||||
msgstr "Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List."
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Valid redirect URLs after a successful authorization flow. Also specify any origins here for Implicit flows."
|
||||
msgstr "Valid redirect URLs after a successful authorization flow. Also specify any origins here for Implicit flows."
|
||||
@ -5084,7 +5165,7 @@ msgstr "Warning: authentik Domain is not configured, authentication will not wor
|
||||
msgid "WebAuthn Authenticators"
|
||||
msgstr "WebAuthn Authenticators"
|
||||
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
|
||||
msgid "WebAuthn Devices"
|
||||
msgstr "WebAuthn Devices"
|
||||
|
||||
@ -5177,9 +5258,9 @@ msgstr "X509 Subject"
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/users/GroupSelectModal.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/user/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Yes"
|
||||
msgstr "Yes"
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user