Compare commits

..

52 Commits

Author SHA1 Message Date
ac52667327 release: 2021.9.1-rc3 2021-09-19 21:52:49 +02:00
0d7c5c2108 web/user: fix edit button for applications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 21:10:57 +02:00
73e3d19384 web/user: add auto-focus search for applications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 21:07:18 +02:00
f6e0f0282d core: fix tokens not being viewable but superusers
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 16:11:20 +02:00
3f42067a8f web: improve display of action buttons with non-primary classes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:57:12 +02:00
ed6f5b98df sources/ldap: improve messages of sync tasks in UI
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:54:22 +02:00
dd290e264c web/admin: trigger refresh after syncing ldap
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:34:44 +02:00
c85484fc00 core: allow admins to create tokens with all parameters, re-add user to token form
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:25:48 +02:00
663dffd8be web/admin: fix error in firefox when creating token
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 00:28:40 +02:00
c15d0c3d17 web: fix text colour in delete form in dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 00:14:50 +02:00
bf09a54f35 web/user: optimise load, fix unread status for notifications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-18 18:16:31 +02:00
930dd51663 web: fix datetime-local fields throwing errors on firefox
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-18 15:31:48 +02:00
12a523c7aa website: remove sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-18 14:33:53 +02:00
ea9a6d57dd build(deps): bump rapidoc from 9.0.0 to 9.1.0 in /website (#1414)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.0.0 to 9.1.0.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/9.0.0...v9.1.0)

---
updated-dependencies:
- dependency-name: rapidoc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-18 11:23:54 +02:00
91958e1232 build(deps): bump rapidoc from 9.0.0 to 9.1.0 in /web (#1415)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.0.0 to 9.1.0.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/9.0.0...v9.1.0)

---
updated-dependencies:
- dependency-name: rapidoc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-18 11:23:44 +02:00
8925afb089 web/admin: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:54:48 +02:00
ccafe7be4f website/docs: update 2021.9 changelog
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:49:36 +02:00
8279690a8f sources/ldap: prevent error when retrying old system task with no arguments
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:49:26 +02:00
763d3ae76a web/admin: fix ldap sync status for new API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:46:18 +02:00
b775e7f4d3 web: Update Web API Client version (#1412)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-17 12:45:16 +02:00
3d8d93ece5 root: log failed celery tasks to event log
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 12:42:42 +02:00
06af306e8a sources/ldap: bump timeout, run each sync component in its own task
closes #1411

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 12:42:20 +02:00
9257f3c919 web/user: add missing stop impersonation button
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 10:04:47 +02:00
2fe7f4cf04 web/user: fix final redirect after stage setup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 09:53:14 +02:00
04399bc8bb web/admin: fix settings link on user avatar
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 09:53:04 +02:00
fcbcfbc3c0 website/docs: Minor LDAP and NGINX Documentation Updates (#1406)
* update LDAP documentation

* include domain level nginx forward auth example

* wrap in banner

* update placeholder
2021-09-17 09:47:27 +02:00
3e4ce62dfe build(deps-dev): bump pylint from 2.10.2 to 2.11.1 (#1409)
* build(deps-dev): bump pylint from 2.10.2 to 2.11.1

Bumps [pylint](https://github.com/PyCQA/pylint) from 2.10.2 to 2.11.1.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.10.2...v2.11.1)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* root: update pylint config

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 09:46:39 +02:00
d8292151e6 build(deps-dev): bump prettier from 2.4.0 to 2.4.1 in /website (#1407) 2021-09-17 09:17:20 +02:00
3d01a59b34 build(deps): bump prettier from 2.4.0 to 2.4.1 in /web (#1408) 2021-09-17 09:17:12 +02:00
5df15c4105 build(deps): bump boto3 from 1.18.42 to 1.18.43 (#1410) 2021-09-17 09:16:43 +02:00
75d695105d web: Update Web API Client version (#1405)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-16 23:44:53 +02:00
28189bdddf release: 2021.9.1-rc2 2021-09-16 23:23:36 +02:00
f6885c7cf8 website: update screenshots
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 23:20:05 +02:00
2c43f0824e tests/e2e: fix e2e tests for new URLs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 22:48:34 +02:00
13e2eea72f web/user: new end-user interface (#1404)
* web/user: migrate to top navbar

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

* web/user: prepare config from server

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

* re-sort

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

* remove old interface

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

* update issue template

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

* use notification badge

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

* web/user: re-add go-to-admin button

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

* *: fix remaining redirects directly to admin

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

* make settings better

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

* api: ensure sources and stages are sorted

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

* web/user: add sessions and consent

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

* providers/oauth2: add post wrapper to stage

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

* website/docs: add new interface to release notes

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 22:17:05 +02:00
9441be1ee2 interface split (#943) 2021-09-16 17:30:16 +02:00
d7ab2a362a ci: bump cache
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 13:46:11 +02:00
e920be3a72 ci: add versioning to cache keys
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 12:31:29 +02:00
f771383c4b cmd: fix outpost metrics not being set in embedded mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 12:09:12 +02:00
65c75f085a website/docs: add 2021.9.1-rc2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 11:06:03 +02:00
17503365f7 policies: improve error handling when using bindings without policy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 11:04:31 +02:00
ebf9f0ca63 stages/email: don't crash when testing stage does not exist
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 11:04:08 +02:00
ae26d2756f providers/saml: improved error handling
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 10:58:51 +02:00
124071f9be root: remove python requirement from pipfile
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 10:37:43 +02:00
471f7d9c62 outposts: add consistent name and type to metrics
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 10:14:51 +02:00
a6a6b3bd06 outposts: add outpost_name label to metrics
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 10:04:17 +02:00
48ad3dccda outposts/proxy: remove deprecated rs256
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 09:57:47 +02:00
341c58a722 core: fix token expiry for service accounts being only 30 minutes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 09:57:34 +02:00
9b04f2da48 website/docs: add notice for rancher cert
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-16 09:40:04 +02:00
f7a296544f web: Update Web API Client version (#1401)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-15 22:45:32 +02:00
78641a57ad web: update background image
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-15 22:43:03 +02:00
a77ff5ffec website/docs: add 2021.9 to sidebar
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-15 22:37:39 +02:00
154 changed files with 2444 additions and 1783 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -49,9 +49,6 @@ ua-parser = "*"
deepmerge = "*"
colorama = "*"
[requires]
python_version = "3.9"
[dev-packages]
bandit = "*"
black = "==21.5b1"

40
Pipfile.lock generated
View File

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

View File

@ -1,3 +1,3 @@
"""authentik"""
__version__ = "2021.9.1-rc1"
__version__ = "2021.9.1-rc3"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@
{% load i18n %}
{% block head_before %}
{{ block.super }}
{% if flow.compatibility_mode %}
<script>ShadyDOM = { force: !navigator.webdriver };</script>
{% endif %}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,4 +4,5 @@ type Outpost interface {
Start() error
Refresh() error
TimerFlowCacheExpiry()
Type() string
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -1,2 +1,4 @@
// @ts-ignore
window["polymerSkipLoadingFontRoboto"] = true;
import "construct-style-sheets-polyfill";
import "@webcomponents/webcomponentsjs";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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