Compare commits

...

40 Commits

Author SHA1 Message Date
3fa987f443 start config file watch
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 21:32:33 +01:00
5ea9595c9c internal: fix cache-control header
Signed-off-by: Jens Langhammer <jens@goauthentik.io>

#4525
2023-01-25 21:18:20 +01:00
1b6f920265 web/flows: improve handling of flow info
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 15:01:08 +01:00
3bf8c915d5 web/flows: update flow background image
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 15:00:56 +01:00
1cc578be66 website: update comparison
closes #4088

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 14:49:09 +01:00
36f8f8bae5 stages/prompt: fix mismatched name field in migration
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 14:46:40 +01:00
68058fb2ae stages/authenticator_validate: fix error with passwordless webauthn login, improve tests
closes #4527

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 14:45:00 +01:00
0abbe8288e website: update comparison
Signed-off-by: Jens Langhammer <jens@goauthentik.io>

#4088
2023-01-25 11:39:58 +01:00
a9de9101ca web: bump @sentry/browser from 7.32.1 to 7.33.0 in /web (#4516)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 7.32.1 to 7.33.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.32.1...7.33.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:34:41 +01:00
19ed9dc5e9 web: bump @lingui/macro from 3.16.0 to 3.16.1 in /web (#4518)
Bumps [@lingui/macro](https://github.com/lingui/js-lingui) from 3.16.0 to 3.16.1.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.16.0...v3.16.1)

---
updated-dependencies:
- dependency-name: "@lingui/macro"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:34:28 +01:00
b5ae712f35 website: bump ua-parser-js from 0.7.32 to 0.7.33 in /website (#4526)
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.32 to 0.7.33.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.32...0.7.33)

---
updated-dependencies:
- dependency-name: ua-parser-js
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:25:38 +01:00
b1c01b53e7 web: bump @lingui/cli from 3.16.0 to 3.16.1 in /web (#4522)
Bumps [@lingui/cli](https://github.com/lingui/js-lingui) from 3.16.0 to 3.16.1.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.16.0...v3.16.1)

---
updated-dependencies:
- dependency-name: "@lingui/cli"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:24:11 +01:00
5b31e47573 core: bump goauthentik.io/api/v3 from 3.2023012.1 to 3.2023012.2 (#4523)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2023012.1 to 3.2023012.2.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2023012.1...v3.2023012.2)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:23:50 +01:00
d2a9a294f2 web: bump pyright from 1.1.290 to 1.1.291 in /web (#4519)
Bumps [pyright](https://github.com/Microsoft/pyright/tree/HEAD/packages/pyright) from 1.1.290 to 1.1.291.
- [Release notes](https://github.com/Microsoft/pyright/releases)
- [Commits](https://github.com/Microsoft/pyright/commits/1.1.291/packages/pyright)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:23:38 +01:00
32c7c58518 web: bump @sentry/tracing from 7.32.1 to 7.33.0 in /web (#4517)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 7.32.1 to 7.33.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.32.1...7.33.0)

---
updated-dependencies:
- dependency-name: "@sentry/tracing"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:23:28 +01:00
349c6e50c2 web: bump @lingui/detect-locale from 3.16.0 to 3.16.1 in /web (#4521)
Bumps [@lingui/detect-locale](https://github.com/lingui/js-lingui) from 3.16.0 to 3.16.1.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.16.0...v3.16.1)

---
updated-dependencies:
- dependency-name: "@lingui/detect-locale"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:22:50 +01:00
2e8027fa04 core: bump coverage from 7.0.5 to 7.1.0 (#4524)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.0.5 to 7.1.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.0.5...7.1.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:22:37 +01:00
0bc1b33663 website/blog: fix mixed up images
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 19:46:15 +01:00
8564f9ef87 website: hardcode url for disqus comments
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 19:26:04 +01:00
7cfd84d8f0 website/blog: update blog title
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 19:03:05 +01:00
f2e40ec7e3 website: publish new blog post
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 18:59:54 +01:00
1f1255a829 web: bump API Client version (#4508)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
2023-01-24 13:45:05 +01:00
53b65a9d1a stages/prompt: field name (#4497)
* add prompt field name

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove numerical prefix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing name

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use text field

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add description label

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add migrate blueprint to remove old stages

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add task to remove unretrievable blueprints

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* lint

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix blueprint test paths

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* actually fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests even more

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix fixtures

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 12:23:22 +01:00
9437e2d3ab website: add disqus to blog posts
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 12:14:59 +01:00
6a7b78abc2 web: bump @sentry/browser from 7.31.1 to 7.32.1 in /web (#4498)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 7.31.1 to 7.32.1.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.31.1...7.32.1)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 11:05:12 +01:00
9c24e5195b core: bump goauthentik.io/api/v3 from 3.2023010.1 to 3.2023012.1 (#4500)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2023010.1 to 3.2023012.1.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2023010.1...v3.2023012.1)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:56:29 +01:00
306b046b9a web: bump @sentry/tracing from 7.31.1 to 7.32.1 in /web (#4499)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 7.31.1 to 7.32.1.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.31.1...7.32.1)

---
updated-dependencies:
- dependency-name: "@sentry/tracing"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:56:18 +01:00
10b50c5845 web: bump @typescript-eslint/parser from 5.48.2 to 5.49.0 in /web (#4501)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.48.2 to 5.49.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.49.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:52:08 +01:00
3912a57df2 core: bump sentry-sdk from 1.13.0 to 1.14.0 (#4502)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/1.13.0...1.14.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:51:38 +01:00
08142d393f web: bump @typescript-eslint/eslint-plugin from 5.48.2 to 5.49.0 in /web (#4503)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.48.2 to 5.49.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.49.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:48:19 +01:00
0f7af256d6 web: bump @types/codemirror from 5.60.6 to 5.60.7 in /web (#4504)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 5.60.6 to 5.60.7.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror)

---
updated-dependencies:
- dependency-name: "@types/codemirror"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:48:00 +01:00
16076cc46f outposts: fallback to ghcr
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 10:47:30 +01:00
8aa16e66e4 core: bump selenium from 4.7.2 to 4.8.0 (#4505)
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.7.2 to 4.8.0.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/commits/selenium-4.8.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:47:10 +01:00
a9b32e2f97 providers/ldap: add unbind flow execution (#4484)
add unbind flow execution

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 20:36:30 +01:00
b2d272bf6f api: fix lint
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 20:19:03 +01:00
31ef6fb6a6 core: delete session when user is set to inactive
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 16:24:30 +01:00
c9c059a008 api: ensure user is active when authenticating
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 16:24:30 +01:00
a5e84b5482 web: bump API Client version (#4492)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
2023-01-23 15:18:33 +00:00
7c697e09f3 Merge branch 'version-2023.1' 2023-01-23 14:39:59 +01:00
b99afd82b2 stages/user_write: fix migration setting wrong value, fix form
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 14:38:26 +01:00
88 changed files with 1578 additions and 728 deletions

View File

@ -31,6 +31,16 @@ def validate_auth(header: bytes) -> Optional[str]:
def bearer_auth(raw_header: bytes) -> Optional[User]: def bearer_auth(raw_header: bytes) -> Optional[User]:
"""raw_header in the Format of `Bearer ....`"""
user = auth_user_lookup(raw_header)
if not user:
return None
if not user.is_active:
raise AuthenticationFailed("Token invalid/expired")
return user
def auth_user_lookup(raw_header: bytes) -> Optional[User]:
"""raw_header in the Format of `Bearer ....`""" """raw_header in the Format of `Bearer ....`"""
from authentik.providers.oauth2.models import RefreshToken from authentik.providers.oauth2.models import RefreshToken

View File

@ -3,13 +3,12 @@ from base64 import b64encode
from django.conf import settings from django.conf import settings
from django.test import TestCase from django.test import TestCase
from guardian.shortcuts import get_anonymous_user
from rest_framework.exceptions import AuthenticationFailed from rest_framework.exceptions import AuthenticationFailed
from authentik.api.authentication import bearer_auth from authentik.api.authentication import bearer_auth
from authentik.blueprints.tests import reconcile_app from authentik.blueprints.tests import reconcile_app
from authentik.core.models import USER_ATTRIBUTE_SA, Token, TokenIntents from authentik.core.models import USER_ATTRIBUTE_SA, Token, TokenIntents
from authentik.core.tests.utils import create_test_flow from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
@ -36,9 +35,18 @@ class TestAPIAuth(TestCase):
def test_bearer_valid(self): def test_bearer_valid(self):
"""Test valid token""" """Test valid token"""
token = Token.objects.create(intent=TokenIntents.INTENT_API, user=get_anonymous_user()) token = Token.objects.create(intent=TokenIntents.INTENT_API, user=create_test_admin_user())
self.assertEqual(bearer_auth(f"Bearer {token.key}".encode()), token.user) self.assertEqual(bearer_auth(f"Bearer {token.key}".encode()), token.user)
def test_bearer_valid_deactivated(self):
"""Test valid token"""
user = create_test_admin_user()
user.is_active = False
user.save()
token = Token.objects.create(intent=TokenIntents.INTENT_API, user=user)
with self.assertRaises(AuthenticationFailed):
bearer_auth(f"Bearer {token.key}".encode())
def test_managed_outpost(self): def test_managed_outpost(self):
"""Test managed outpost""" """Test managed outpost"""
with self.assertRaises(AuthenticationFailed): with self.assertRaises(AuthenticationFailed):
@ -56,7 +64,7 @@ class TestAPIAuth(TestCase):
name=generate_id(), client_id=generate_id(), authorization_flow=create_test_flow() name=generate_id(), client_id=generate_id(), authorization_flow=create_test_flow()
) )
refresh = RefreshToken.objects.create( refresh = RefreshToken.objects.create(
user=get_anonymous_user(), user=create_test_admin_user(),
provider=provider, provider=provider,
refresh_token=generate_id(), refresh_token=generate_id(),
_scope=SCOPE_AUTHENTIK_API, _scope=SCOPE_AUTHENTIK_API,
@ -69,7 +77,7 @@ class TestAPIAuth(TestCase):
name=generate_id(), client_id=generate_id(), authorization_flow=create_test_flow() name=generate_id(), client_id=generate_id(), authorization_flow=create_test_flow()
) )
refresh = RefreshToken.objects.create( refresh = RefreshToken.objects.create(
user=get_anonymous_user(), user=create_test_admin_user(),
provider=provider, provider=provider,
refresh_token=generate_id(), refresh_token=generate_id(),
_scope="", _scope="",

View File

@ -57,9 +57,10 @@ class AuthentikBlueprintsConfig(ManagedAppConfig):
def reconcile_blueprints_discover(self): def reconcile_blueprints_discover(self):
"""Run blueprint discovery""" """Run blueprint discovery"""
from authentik.blueprints.v1.tasks import blueprints_discover from authentik.blueprints.v1.tasks import blueprints_discover, clear_failed_blueprints
blueprints_discover.delay() blueprints_discover.delay()
clear_failed_blueprints.delay()
def import_models(self): def import_models(self):
super().import_models() super().import_models()

View File

@ -9,4 +9,9 @@ CELERY_BEAT_SCHEDULE = {
"schedule": crontab(minute=fqdn_rand("blueprints_v1_discover"), hour="*"), "schedule": crontab(minute=fqdn_rand("blueprints_v1_discover"), hour="*"),
"options": {"queue": "authentik_scheduled"}, "options": {"queue": "authentik_scheduled"},
}, },
"blueprints_v1_cleanup": {
"task": "authentik.blueprints.v1.tasks.clear_failed_blueprints",
"schedule": crontab(minute=fqdn_rand("blueprints_v1_cleanup"), hour="*"),
"options": {"queue": "authentik_scheduled"},
},
} }

View File

@ -4,6 +4,7 @@ entries:
pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4 pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
name: qwerweqrq
field_key: username field_key: username
label: Username label: Username
type: username type: username

View File

@ -13,7 +13,7 @@ from authentik.tenants.models import Tenant
class TestPackaged(TransactionTestCase): class TestPackaged(TransactionTestCase):
"""Empty class, test methods are added dynamically""" """Empty class, test methods are added dynamically"""
@apply_blueprint("default/90-default-tenant.yaml") @apply_blueprint("default/default-tenant.yaml")
def test_decorator_static(self): def test_decorator_static(self):
"""Test @apply_blueprint decorator""" """Test @apply_blueprint decorator"""
self.assertTrue(Tenant.objects.filter(domain="authentik-default").exists()) self.assertTrue(Tenant.objects.filter(domain="authentik-default").exists())

View File

@ -262,15 +262,21 @@ class TestBlueprintsV1(TransactionTestCase):
with transaction_rollback(): with transaction_rollback():
# First stage fields # First stage fields
username_prompt = Prompt.objects.create( username_prompt = Prompt.objects.create(
field_key="username", label="Username", order=0, type=FieldTypes.TEXT name=generate_id(),
field_key="username",
label="Username",
order=0,
type=FieldTypes.TEXT,
) )
password = Prompt.objects.create( password = Prompt.objects.create(
name=generate_id(),
field_key="password", field_key="password",
label="Password", label="Password",
order=1, order=1,
type=FieldTypes.PASSWORD, type=FieldTypes.PASSWORD,
) )
password_repeat = Prompt.objects.create( password_repeat = Prompt.objects.create(
name=generate_id(),
field_key="password_repeat", field_key="password_repeat",
label="Password (repeat)", label="Password (repeat)",
order=2, order=2,

View File

@ -3,3 +3,4 @@
LABEL_AUTHENTIK_SYSTEM = "blueprints.goauthentik.io/system" LABEL_AUTHENTIK_SYSTEM = "blueprints.goauthentik.io/system"
LABEL_AUTHENTIK_INSTANTIATE = "blueprints.goauthentik.io/instantiate" LABEL_AUTHENTIK_INSTANTIATE = "blueprints.goauthentik.io/instantiate"
LABEL_AUTHENTIK_GENERATED = "blueprints.goauthentik.io/generated" LABEL_AUTHENTIK_GENERATED = "blueprints.goauthentik.io/generated"
LABEL_AUTHENTIK_DESCRIPTION = "blueprints.goauthentik.io/description"

View File

@ -219,3 +219,14 @@ def apply_blueprint(self: MonitoredTask, instance_pk: str):
finally: finally:
if instance: if instance:
instance.save() instance.save()
@CELERY_APP.task()
def clear_failed_blueprints():
"""Remove blueprints which couldn't be fetched"""
# Exclude OCI blueprints as those might be temporarily unavailable
for blueprint in BlueprintInstance.objects.exclude(path__startswith="oci://"):
try:
blueprint.retrieve()
except BlueprintRetrievalFailed:
blueprint.delete()

View File

@ -4,6 +4,8 @@ from json import loads
from typing import Any, Optional from typing import Any, Optional
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core.cache import cache
from django.db.models.functions import ExtractHour from django.db.models.functions import ExtractHour
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.db.transaction import atomic from django.db.transaction import atomic
@ -57,6 +59,7 @@ from authentik.core.models import (
USER_ATTRIBUTE_SA, USER_ATTRIBUTE_SA,
USER_ATTRIBUTE_TOKEN_EXPIRING, USER_ATTRIBUTE_TOKEN_EXPIRING,
USER_PATH_SERVICE_ACCOUNT, USER_PATH_SERVICE_ACCOUNT,
AuthenticatedSession,
Group, Group,
Token, Token,
TokenIntents, TokenIntents,
@ -561,3 +564,14 @@ class UserViewSet(UsedByMixin, ModelViewSet):
) )
} }
) )
def partial_update(self, request: Request, *args, **kwargs) -> Response:
response = super().partial_update(request, *args, **kwargs)
instance: User = self.get_object()
if not instance.is_active:
sessions = AuthenticatedSession.objects.filter(user=instance)
session_ids = sessions.values_list("session_key", flat=True)
cache.delete_many(f"{KEY_PREFIX}{session}" for session in session_ids)
sessions.delete()
LOGGER.debug("Deleted user's sessions", user=instance.username)
return response

View File

@ -1,10 +1,12 @@
"""Test Users API""" """Test Users API"""
from json import loads from json import loads
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core.cache import cache
from django.urls.base import reverse from django.urls.base import reverse
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from authentik.core.models import User from authentik.core.models import AuthenticatedSession, User
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_tenant from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_tenant
from authentik.flows.models import FlowDesignation from authentik.flows.models import FlowDesignation
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
@ -257,3 +259,26 @@ class TestUsersAPI(APITestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
body = loads(response.content.decode()) body = loads(response.content.decode())
self.assertEqual(body["user"]["avatar"], "bar") self.assertEqual(body["user"]["avatar"], "bar")
def test_session_delete(self):
"""Ensure sessions are deleted when a user is deactivated"""
user = create_test_admin_user()
session_id = generate_id()
AuthenticatedSession.objects.create(
user=user,
session_key=session_id,
last_ip="",
)
cache.set(KEY_PREFIX + session_id, "foo")
self.client.force_login(self.admin)
response = self.client.patch(
reverse("authentik_api:user-detail", kwargs={"pk": user.pk}),
data={
"is_active": False,
},
)
self.assertEqual(response.status_code, 200)
self.assertIsNone(cache.get(KEY_PREFIX + session_id))
self.assertFalse(AuthenticatedSession.objects.filter(session_key=session_id).exists())

View File

@ -5,13 +5,20 @@ from contextlib import contextmanager
from glob import glob from glob import glob
from json import dumps, loads from json import dumps, loads
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
from pathlib import Path
from sys import argv, stderr from sys import argv, stderr
from time import time from time import time
from typing import Any from typing import Any, Optional
from urllib.parse import urlparse from urllib.parse import urlparse
import yaml import yaml
from django.conf import ImproperlyConfigured from django.conf import ImproperlyConfigured
from watchdog.events import (
FileModifiedEvent,
FileSystemEvent,
FileSystemEventHandler,
)
from watchdog.observers import Observer
SEARCH_PATHS = ["authentik/lib/default.yml", "/etc/authentik/config.yml", ""] + glob( SEARCH_PATHS = ["authentik/lib/default.yml", "/etc/authentik/config.yml", ""] + glob(
"/etc/authentik/config.d/*.yml", recursive=True "/etc/authentik/config.d/*.yml", recursive=True
@ -38,9 +45,47 @@ class ConfigLoader:
A variable like AUTHENTIK_POSTGRESQL__HOST would translate to postgresql.host""" A variable like AUTHENTIK_POSTGRESQL__HOST would translate to postgresql.host"""
loaded_file = [] loaded_file = []
observer: Observer
class FSObserver(FileSystemEventHandler):
"""File system observer"""
loader: "ConfigLoader"
path: str
container: Optional[dict] = None
key: Optional[str] = None
def __init__(
self,
loader: "ConfigLoader",
path: str,
container: Optional[dict] = None,
key: Optional[str] = None,
) -> None:
super().__init__()
self.loader = loader
self.path = path
self.container = container
self.key = key
def on_any_event(self, event: FileSystemEvent):
if not isinstance(event, FileModifiedEvent):
return
if event.is_directory:
return
if event.src_path != self.path:
return
if self.container and self.key:
with open(self.path, "r", encoding="utf8") as _file:
self.container[self.key] = _file.read()
else:
self.loader.log("info", "Updating from changed file", file=self.path)
self.loader.update_from_file(self.path, watch=False)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.observer = Observer()
self.observer.start()
self.__config = {} self.__config = {}
base_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), "../..")) base_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), "../.."))
for path in SEARCH_PATHS: for path in SEARCH_PATHS:
@ -81,11 +126,11 @@ class ConfigLoader:
root[key] = self.update(root.get(key, {}), value) root[key] = self.update(root.get(key, {}), value)
else: else:
if isinstance(value, str): if isinstance(value, str):
value = self.parse_uri(value) value = self.parse_uri(value, root, key)
root[key] = value root[key] = value
return root return root
def parse_uri(self, value: str) -> str: def parse_uri(self, value: str, container: dict[str, Any], key: Optional[str] = None, ) -> str:
"""Parse string values which start with a URI""" """Parse string values which start with a URI"""
url = urlparse(value) url = urlparse(value)
if url.scheme == "env": if url.scheme == "env":
@ -93,13 +138,23 @@ class ConfigLoader:
if url.scheme == "file": if url.scheme == "file":
try: try:
with open(url.path, "r", encoding="utf8") as _file: with open(url.path, "r", encoding="utf8") as _file:
value = _file.read().strip() value = _file.read()
if key:
self.observer.schedule(
ConfigLoader.FSObserver(
self,
url.path,
container,
key,
),
Path(url.path).parent,
)
except OSError as exc: except OSError as exc:
self.log("error", f"Failed to read config value from {url.path}: {exc}") self.log("error", f"Failed to read config value from {url.path}: {exc}")
value = url.query value = url.query
return value return value
def update_from_file(self, path: str): def update_from_file(self, path: str, watch=True):
"""Update config from file contents""" """Update config from file contents"""
try: try:
with open(path, encoding="utf8") as file: with open(path, encoding="utf8") as file:
@ -107,6 +162,8 @@ class ConfigLoader:
self.update(self.__config, yaml.safe_load(file)) self.update(self.__config, yaml.safe_load(file))
self.log("debug", "Loaded config", file=path) self.log("debug", "Loaded config", file=path)
self.loaded_file.append(path) self.loaded_file.append(path)
if watch:
self.observer.schedule(ConfigLoader.FSObserver(self, path), Path(path).parent)
except yaml.YAMLError as exc: except yaml.YAMLError as exc:
raise ImproperlyConfigured from exc raise ImproperlyConfigured from exc
except PermissionError as exc: except PermissionError as exc:
@ -181,13 +238,12 @@ class ConfigLoader:
if comp not in root: if comp not in root:
root[comp] = {} root[comp] = {}
root = root.get(comp, {}) root = root.get(comp, {})
root[path_parts[-1]] = value self.parse_uri(value, root, path_parts[-1])
def y_bool(self, path: str, default=False) -> bool: def y_bool(self, path: str, default=False) -> bool:
"""Wrapper for y that converts value into boolean""" """Wrapper for y that converts value into boolean"""
return str(self.y(path, default)).lower() == "true" return str(self.y(path, default)).lower() == "true"
CONFIG = ConfigLoader() CONFIG = ConfigLoader()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -5,7 +5,7 @@ from tempfile import mkstemp
from django.conf import ImproperlyConfigured from django.conf import ImproperlyConfigured
from django.test import TestCase from django.test import TestCase
from authentik.lib.config import ENV_PREFIX, ConfigLoader from authentik.lib.config import CONFIG, ENV_PREFIX, ConfigLoader
class TestConfig(TestCase): class TestConfig(TestCase):
@ -31,8 +31,8 @@ class TestConfig(TestCase):
"""Test URI parsing (environment)""" """Test URI parsing (environment)"""
config = ConfigLoader() config = ConfigLoader()
environ["foo"] = "bar" environ["foo"] = "bar"
self.assertEqual(config.parse_uri("env://foo"), "bar") self.assertEqual(config.parse_uri("env://foo", {}), "bar")
self.assertEqual(config.parse_uri("env://foo?bar"), "bar") self.assertEqual(config.parse_uri("env://foo?bar", {}), "bar")
def test_uri_file(self): def test_uri_file(self):
"""Test URI parsing (file load)""" """Test URI parsing (file load)"""
@ -41,8 +41,8 @@ class TestConfig(TestCase):
write(file, "foo".encode()) write(file, "foo".encode())
_, file2_name = mkstemp() _, file2_name = mkstemp()
chmod(file2_name, 0o000) # Remove all permissions so we can't read the file chmod(file2_name, 0o000) # Remove all permissions so we can't read the file
self.assertEqual(config.parse_uri(f"file://{file_name}"), "foo") self.assertEqual(config.parse_uri(f"file://{file_name}", {}), "foo")
self.assertEqual(config.parse_uri(f"file://{file2_name}?def"), "def") self.assertEqual(config.parse_uri(f"file://{file2_name}?def", {}), "def")
unlink(file_name) unlink(file_name)
unlink(file2_name) unlink(file2_name)
@ -59,3 +59,13 @@ class TestConfig(TestCase):
config.update_from_file(file2_name) config.update_from_file(file2_name)
unlink(file_name) unlink(file_name)
unlink(file2_name) unlink(file2_name)
def test_update(self):
"""Test change to file"""
file, file_name = mkstemp()
write(file, b"test")
CONFIG.y_set("test.file", f"file://{file_name}")
self.assertEqual(CONFIG.y("test.file"), "test")
write(file, "test2")
self.assertEqual(CONFIG.y("test.file"), "test2")
unlink(file_name)

View File

@ -183,7 +183,7 @@ class DockerController(BaseController):
try: try:
self.client.images.pull(image) self.client.images.pull(image)
except DockerException: # pragma: no cover except DockerException: # pragma: no cover
image = f"goauthentik.io/{self.outpost.type}:latest" image = f"ghcr.io/goauthentik/{self.outpost.type}:latest"
self.client.images.pull(image) self.client.images.pull(image)
return image return image

View File

@ -4,6 +4,7 @@ from django.urls.base import reverse
from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.models import FlowDesignation, FlowStageBinding from authentik.flows.models import FlowDesignation, FlowStageBinding
from authentik.flows.tests import FlowTestCase from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_id
from authentik.policies.password.models import PasswordPolicy from authentik.policies.password.models import PasswordPolicy
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
@ -16,6 +17,7 @@ class TestPasswordPolicyFlow(FlowTestCase):
self.flow = create_test_flow(FlowDesignation.AUTHENTICATION) self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
password_prompt = Prompt.objects.create( password_prompt = Prompt.objects.create(
name=generate_id(),
field_key="password", field_key="password",
label="PASSWORD_LABEL", label="PASSWORD_LABEL",
type=FieldTypes.PASSWORD, type=FieldTypes.PASSWORD,

View File

@ -376,7 +376,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
def challenge_valid(self, response: AuthenticatorValidationChallengeResponse) -> HttpResponse: def challenge_valid(self, response: AuthenticatorValidationChallengeResponse) -> HttpResponse:
# All validation is done by the serializer # All validation is done by the serializer
user = self.get_pending_user() user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
if not user: if not user:
if "webauthn" not in response.data: if "webauthn" not in response.data:
return self.executor.stage_invalid() return self.executor.stage_invalid()

View File

@ -9,9 +9,10 @@ from webauthn.helpers.bytes_to_base64url import bytes_to_base64url
from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.models import FlowStageBinding, NotConfiguredAction from authentik.flows.models import FlowStageBinding, NotConfiguredAction
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.stage import StageView from authentik.flows.stage import StageView
from authentik.flows.tests import FlowTestCase from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import FlowExecutorView from authentik.flows.views.executor import SESSION_KEY_PLAN, FlowExecutorView
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import get_request from authentik.lib.tests.utils import get_request
from authentik.stages.authenticator_validate.challenge import ( from authentik.stages.authenticator_validate.challenge import (
@ -20,10 +21,14 @@ from authentik.stages.authenticator_validate.challenge import (
validate_challenge_webauthn, validate_challenge_webauthn,
) )
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
from authentik.stages.authenticator_validate.stage import AuthenticatorValidateStageView from authentik.stages.authenticator_validate.stage import (
SESSION_KEY_DEVICE_CHALLENGES,
AuthenticatorValidateStageView,
)
from authentik.stages.authenticator_webauthn.models import UserVerification, WebAuthnDevice from authentik.stages.authenticator_webauthn.models import UserVerification, WebAuthnDevice
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
from authentik.stages.identification.models import IdentificationStage, UserFields from authentik.stages.identification.models import IdentificationStage, UserFields
from authentik.stages.user_login.models import UserLoginStage
class AuthenticatorValidateStageWebAuthnTests(FlowTestCase): class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
@ -185,10 +190,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
def test_validate_challenge(self): def test_validate_challenge(self):
"""Test webauthn""" """Test webauthn"""
request = get_request("/") device = WebAuthnDevice.objects.create(
request.user = self.user
WebAuthnDevice.objects.create(
user=self.user, user=self.user,
public_key=( public_key=(
"pQECAyYgASFYIGsBLkklToCQkT7qJT_bJYN1sEc1oJdbnmoOc43i0J" "pQECAyYgASFYIGsBLkklToCQkT7qJT_bJYN1sEc1oJdbnmoOc43i0J"
@ -204,49 +206,134 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
not_configured_action=NotConfiguredAction.CONFIGURE, not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.WEBAUTHN], device_classes=[DeviceClasses.WEBAUTHN],
) )
stage_view = AuthenticatorValidateStageView( session = self.client.session
FlowExecutorView(flow=flow, current_stage=stage), request=request plan = FlowPlan(flow_pk=flow.pk.hex)
) plan.append_stage(stage)
request = get_request("/") plan.append_stage(UserLoginStage(name=generate_id()))
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes( plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session[SESSION_KEY_PLAN] = plan
session[SESSION_KEY_DEVICE_CHALLENGES] = [
{
"device_class": device.__class__.__name__.lower().replace("device", ""),
"device_uid": device.pk,
"challenge": {},
}
]
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
( (
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1" "g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1"
"jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA" "jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
) )
) )
request.session.save() session.save()
stage_view = AuthenticatorValidateStageView( response = self.client.post(
FlowExecutorView(flow=flow, current_stage=stage), request=request reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
) data={
request.META["SERVER_NAME"] = "localhost" "webauthn": {
request.META["SERVER_PORT"] = "9000" "id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
validate_challenge_webauthn( "rawId": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
{ "type": "public-key",
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU", "assertionClientExtensions": "{}",
"rawId": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU", "response": {
"type": "public-key", "clientDataJSON": (
"assertionClientExtensions": "{}", "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2WlhvNWx4T"
"response": { "GZockQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGczQWdPNFdoLU"
"clientDataJSON": ( "JxYTU1NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjkwMDA"
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2WlhvNWx4TGZo" "iLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hlcmUi"
"ckQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGczQWdPNFdoLUJxYTU1" "OiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuI"
"NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJjcm9z" "FNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ==",
"c09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hlcmUiOiJkbyBub3Qg" ),
"Y29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczov" "signature": (
"L2dvby5nbC95YWJQZXgifQ==", "MEQCIFNlrHf9ablJAalXLWkrqvHB8oIu8kwvRpH3X3rbJVpI"
), "AiAqtOK6mIZPk62kZN0OzFsHfuvu_RlOl7zlqSNzDdz_Ag=="
"signature": ( ),
"MEQCIFNlrHf9ablJAalXLWkrqvHB8oIu8kwvRpH3X3rbJVpI" "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAABQ==",
"AiAqtOK6mIZPk62kZN0OzFsHfuvu_RlOl7zlqSNzDdz_Ag==" "userHandle": None,
), },
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAABQ==",
"userHandle": None,
}, },
}, },
stage_view, SERVER_NAME="localhost",
self.user, SERVER_PORT="9000",
) )
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_validate_challenge_userless(self):
"""Test webauthn"""
device = WebAuthnDevice.objects.create(
user=self.user,
public_key=(
"pQECAyYgASFYIGsBLkklToCQkT7qJT_bJYN1sEc1oJdbnmoOc43i0J"
"H6IlggLTXytuhzFVYYAK4PQNj8_coGrbbzSfUxdiPAcZTQCyU"
),
credential_id="QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
sign_count=4,
rp_id=generate_id(),
)
flow = create_test_flow()
stage = AuthenticatorValidateStage.objects.create(
name=generate_id(),
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.WEBAUTHN],
)
session = self.client.session
plan = FlowPlan(flow_pk=flow.pk.hex)
plan.append_stage(stage)
plan.append_stage(UserLoginStage(name=generate_id()))
session[SESSION_KEY_PLAN] = plan
session[SESSION_KEY_DEVICE_CHALLENGES] = [
{
"device_class": device.__class__.__name__.lower().replace("device", ""),
"device_uid": device.pk,
"challenge": {},
}
]
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
(
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1"
"jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
)
)
session.save()
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
data={
"webauthn": {
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
"rawId": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
"type": "public-key",
"assertionClientExtensions": "{}",
"response": {
"clientDataJSON": (
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2WlhvNWx4T"
"GZockQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGczQWdPNFdoLU"
"JxYTU1NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjkwMDA"
"iLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hlcmUi"
"OiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuI"
"FNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ==",
),
"signature": (
"MEQCIFNlrHf9ablJAalXLWkrqvHB8oIu8kwvRpH3X3rbJVpI"
"AiAqtOK6mIZPk62kZN0OzFsHfuvu_RlOl7zlqSNzDdz_Ag=="
),
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAABQ==",
"userHandle": None,
},
},
},
SERVER_NAME="localhost",
SERVER_PORT="9000",
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_validate_challenge_invalid(self): def test_validate_challenge_invalid(self):
"""Test webauthn""" """Test webauthn"""

View File

@ -42,6 +42,7 @@ class PromptSerializer(ModelSerializer):
model = Prompt model = Prompt
fields = [ fields = [
"pk", "pk",
"name",
"field_key", "field_key",
"label", "label",
"type", "type",
@ -59,5 +60,5 @@ class PromptViewSet(UsedByMixin, ModelViewSet):
queryset = Prompt.objects.all().prefetch_related("promptstage_set") queryset = Prompt.objects.all().prefetch_related("promptstage_set")
serializer_class = PromptSerializer serializer_class = PromptSerializer
filterset_fields = ["field_key", "label", "type", "placeholder"] filterset_fields = ["field_key", "name", "label", "type", "placeholder"]
search_fields = ["field_key", "label", "type", "placeholder"] search_fields = ["field_key", "name", "label", "type", "placeholder"]

View File

@ -0,0 +1,40 @@
# Generated by Django 4.1.5 on 2023-01-23 19:42
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def set_generated_name(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Prompt = apps.get_model("authentik_stages_prompt", "prompt")
for prompt in Prompt.objects.using(db_alias).all():
name = prompt.field_key
stage = prompt.promptstage_set.order_by("name").first()
if stage:
name += "_" + stage.name
prompt.name = name
prompt.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_prompt", "0008_alter_prompt_type"),
]
operations = [
migrations.AddField(
model_name="prompt",
name="name",
field=models.TextField(default="", unique=False, db_index=False, blank=False),
preserve_default=False,
),
migrations.RunPython(code=set_generated_name),
migrations.AlterField(
model_name="prompt",
name="name",
field=models.TextField(unique=True),
),
]

View File

@ -96,6 +96,7 @@ class Prompt(SerializerModel):
"""Single Prompt, part of a prompt stage.""" """Single Prompt, part of a prompt stage."""
prompt_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) prompt_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField(unique=True, blank=False)
field_key = models.TextField( field_key = models.TextField(
help_text=_("Name of the form field, also used to store the value") help_text=_("Name of the form field, also used to store the value")

View File

@ -30,6 +30,7 @@ class TestPromptStage(FlowTestCase):
self.factory = RequestFactory() self.factory = RequestFactory()
self.flow = create_test_flow() self.flow = create_test_flow()
username_prompt = Prompt.objects.create( username_prompt = Prompt.objects.create(
name=generate_id(),
field_key="username_prompt", field_key="username_prompt",
label="USERNAME_LABEL", label="USERNAME_LABEL",
type=FieldTypes.USERNAME, type=FieldTypes.USERNAME,
@ -37,6 +38,7 @@ class TestPromptStage(FlowTestCase):
placeholder="USERNAME_PLACEHOLDER", placeholder="USERNAME_PLACEHOLDER",
) )
text_prompt = Prompt.objects.create( text_prompt = Prompt.objects.create(
name=generate_id(),
field_key="text_prompt", field_key="text_prompt",
label="TEXT_LABEL", label="TEXT_LABEL",
type=FieldTypes.TEXT, type=FieldTypes.TEXT,
@ -44,6 +46,7 @@ class TestPromptStage(FlowTestCase):
placeholder="TEXT_PLACEHOLDER", placeholder="TEXT_PLACEHOLDER",
) )
email_prompt = Prompt.objects.create( email_prompt = Prompt.objects.create(
name=generate_id(),
field_key="email_prompt", field_key="email_prompt",
label="EMAIL_LABEL", label="EMAIL_LABEL",
type=FieldTypes.EMAIL, type=FieldTypes.EMAIL,
@ -51,6 +54,7 @@ class TestPromptStage(FlowTestCase):
placeholder="EMAIL_PLACEHOLDER", placeholder="EMAIL_PLACEHOLDER",
) )
password_prompt = Prompt.objects.create( password_prompt = Prompt.objects.create(
name=generate_id(),
field_key="password_prompt", field_key="password_prompt",
label="PASSWORD_LABEL", label="PASSWORD_LABEL",
type=FieldTypes.PASSWORD, type=FieldTypes.PASSWORD,
@ -58,6 +62,7 @@ class TestPromptStage(FlowTestCase):
placeholder="PASSWORD_PLACEHOLDER", placeholder="PASSWORD_PLACEHOLDER",
) )
password2_prompt = Prompt.objects.create( password2_prompt = Prompt.objects.create(
name=generate_id(),
field_key="password2_prompt", field_key="password2_prompt",
label="PASSWORD_LABEL", label="PASSWORD_LABEL",
type=FieldTypes.PASSWORD, type=FieldTypes.PASSWORD,
@ -65,6 +70,7 @@ class TestPromptStage(FlowTestCase):
placeholder="PASSWORD_PLACEHOLDER", placeholder="PASSWORD_PLACEHOLDER",
) )
number_prompt = Prompt.objects.create( number_prompt = Prompt.objects.create(
name=generate_id(),
field_key="number_prompt", field_key="number_prompt",
label="NUMBER_LABEL", label="NUMBER_LABEL",
type=FieldTypes.NUMBER, type=FieldTypes.NUMBER,
@ -72,12 +78,14 @@ class TestPromptStage(FlowTestCase):
placeholder="NUMBER_PLACEHOLDER", placeholder="NUMBER_PLACEHOLDER",
) )
hidden_prompt = Prompt.objects.create( hidden_prompt = Prompt.objects.create(
name=generate_id(),
field_key="hidden_prompt", field_key="hidden_prompt",
type=FieldTypes.HIDDEN, type=FieldTypes.HIDDEN,
required=True, required=True,
placeholder="HIDDEN_PLACEHOLDER", placeholder="HIDDEN_PLACEHOLDER",
) )
static_prompt = Prompt.objects.create( static_prompt = Prompt.objects.create(
name=generate_id(),
field_key="static_prompt", field_key="static_prompt",
type=FieldTypes.STATIC, type=FieldTypes.STATIC,
required=True, required=True,

View File

@ -17,9 +17,10 @@ entries:
placeholder_expression: false placeholder_expression: false
required: true required: true
type: text type: text
identifiers:
field_key: username field_key: username
label: Username label: Username
identifiers:
name: default-source-enrollment-field-username
id: prompt-field-username id: prompt-field-username
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:

View File

@ -21,9 +21,10 @@ entries:
placeholder_expression: true placeholder_expression: true
required: true required: true
type: text type: text
identifiers:
field_key: username field_key: username
label: Username label: Username
identifiers:
name: default-user-settings-field-username
id: prompt-field-username id: prompt-field-username
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:
@ -36,9 +37,10 @@ entries:
placeholder_expression: true placeholder_expression: true
required: true required: true
type: text type: text
identifiers:
field_key: name field_key: name
label: Name label: Name
identifiers:
name: default-user-settings-field-name
id: prompt-field-name id: prompt-field-name
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:
@ -51,9 +53,10 @@ entries:
placeholder_expression: true placeholder_expression: true
required: true required: true
type: email type: email
identifiers:
field_key: email field_key: email
label: Email label: Email
identifiers:
name: default-user-settings-field-email
id: prompt-field-email id: prompt-field-email
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:
@ -66,9 +69,10 @@ entries:
placeholder_expression: true placeholder_expression: true
required: true required: true
type: ak-locale type: ak-locale
identifiers:
field_key: attributes.settings.locale field_key: attributes.settings.locale
label: Locale label: Locale
identifiers:
name: default-user-settings-field-locale
id: prompt-field-locale id: prompt-field-locale
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:

View File

@ -19,10 +19,11 @@ entries:
required: true required: true
sub_text: '' sub_text: ''
type: static type: static
id: prompt-field-header
identifiers:
field_key: oobe-header-text field_key: oobe-header-text
label: oobe-header-text label: oobe-header-text
id: prompt-field-header
identifiers:
name: initial-setup-field-header
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:
order: 101 order: 101
@ -31,10 +32,11 @@ entries:
required: true required: true
sub_text: '' sub_text: ''
type: email type: email
field_key: email
label: Email
id: prompt-field-email id: prompt-field-email
identifiers: identifiers:
field_key: admin_email name: initial-setup-field-email
label: Email
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:
order: 300 order: 300
@ -43,10 +45,11 @@ entries:
required: true required: true
sub_text: '' sub_text: ''
type: password type: password
id: prompt-field-password
identifiers:
field_key: password field_key: password
label: Password label: Password
id: prompt-field-password
identifiers:
name: initial-setup-field-password
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:
order: 301 order: 301
@ -55,10 +58,11 @@ entries:
required: true required: true
sub_text: '' sub_text: ''
type: password type: password
id: prompt-field-password-repeat
identifiers:
field_key: password_repeat field_key: password_repeat
label: Password (repeat) label: Password (repeat)
id: prompt-field-password-repeat
identifiers:
name: initial-setup-field-password-repeat
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:
expression: | expression: |
@ -66,8 +70,6 @@ entries:
# by injecting "pending_user" # by injecting "pending_user"
akadmin = ak_user_by(username="akadmin") akadmin = ak_user_by(username="akadmin")
context["flow_plan"].context["pending_user"] = akadmin context["flow_plan"].context["pending_user"] = akadmin
# Remap the email value
context["prompt_data"]["email"] = context["prompt_data"]["admin_email"]
return True return True
id: policy-default-oobe-prefill-user id: policy-default-oobe-prefill-user
identifiers: identifiers:

View File

@ -17,9 +17,10 @@ entries:
placeholder_expression: false placeholder_expression: false
required: true required: true
type: password type: password
identifiers:
field_key: password field_key: password
label: Password label: Password
identifiers:
name: default-password-change-field-password
id: prompt-field-password id: prompt-field-password
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:
@ -28,9 +29,10 @@ entries:
placeholder_expression: false placeholder_expression: false
required: true required: true
type: password type: password
identifiers:
field_key: password_repeat field_key: password_repeat
label: Password (repeat) label: Password (repeat)
identifiers:
name: default-password-change-field-password-repeat
id: prompt-field-password-repeat id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
- attrs: - attrs:

View File

@ -13,56 +13,61 @@ entries:
title: Welcome to authentik! title: Welcome to authentik!
designation: enrollment designation: enrollment
authentication: require_unauthenticated authentication: require_unauthenticated
- identifiers: - id: prompt-field-username
model: authentik_stages_prompt.prompt
identifiers:
name: default-enrollment-field-username
attrs:
field_key: username field_key: username
label: Username label: Username
id: prompt-field-username
model: authentik_stages_prompt.prompt
attrs:
type: username type: username
required: true required: true
placeholder: Username placeholder: Username
placeholder_expression: false placeholder_expression: false
order: 0 order: 0
- identifiers: - identifiers:
field_key: password name: default-enrollment-field-password
label: Password
id: prompt-field-password id: prompt-field-password
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: password
label: Password
type: password type: password
required: true required: true
placeholder: Password placeholder: Password
placeholder_expression: false placeholder_expression: false
order: 0 order: 0
- identifiers: - identifiers:
field_key: password_repeat name: default-enrollment-field-password-repeat
label: Password (repeat)
id: prompt-field-password-repeat id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: password_repeat
label: Password (repeat)
type: password type: password
required: true required: true
placeholder: Password (repeat) placeholder: Password (repeat)
placeholder_expression: false placeholder_expression: false
order: 1 order: 1
- identifiers: - identifiers:
field_key: name name: default-enrollment-field-name
label: Name
id: prompt-field-name id: prompt-field-name
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: name
label: Name
type: text type: text
required: true required: true
placeholder: Name placeholder: Name
placeholder_expression: false placeholder_expression: false
order: 0 order: 0
- identifiers: - identifiers:
field_key: email name: default-enrollment-field-email
label: Email
id: prompt-field-email id: prompt-field-email
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: email
label: Email
type: email type: email
required: true required: true
placeholder: Email placeholder: Email

View File

@ -14,55 +14,60 @@ entries:
designation: enrollment designation: enrollment
authentication: require_unauthenticated authentication: require_unauthenticated
- identifiers: - identifiers:
field_key: username name: default-enrollment-field-username
label: Username
id: prompt-field-username id: prompt-field-username
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: username
label: Username
type: username type: username
required: true required: true
placeholder: Username placeholder: Username
placeholder_expression: false placeholder_expression: false
order: 0 order: 0
- identifiers: - identifiers:
field_key: password name: default-enrollment-field-password
label: Password
id: prompt-field-password id: prompt-field-password
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: password
label: Password
type: password type: password
required: true required: true
placeholder: Password placeholder: Password
placeholder_expression: false placeholder_expression: false
order: 0 order: 0
- identifiers: - identifiers:
field_key: password_repeat name: default-enrollment-field-password-repeat
label: Password (repeat)
id: prompt-field-password-repeat id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: password_repeat
label: Password (repeat)
type: password type: password
required: true required: true
placeholder: Password (repeat) placeholder: Password (repeat)
placeholder_expression: false placeholder_expression: false
order: 1 order: 1
- identifiers: - identifiers:
field_key: name name: default-enrollment-field-name
label: Name
id: prompt-field-name id: prompt-field-name
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: name
label: Name
type: text type: text
required: true required: true
placeholder: Name placeholder: Name
placeholder_expression: false placeholder_expression: false
order: 0 order: 0
- identifiers: - identifiers:
field_key: email name: default-enrollment-field-email
label: Email
id: prompt-field-email id: prompt-field-email
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: email
label: Email
type: email type: email
required: true required: true
placeholder: Email placeholder: Email

View File

@ -14,22 +14,24 @@ entries:
designation: recovery designation: recovery
authentication: require_unauthenticated authentication: require_unauthenticated
- identifiers: - identifiers:
field_key: password name: default-recovery-field-password
label: Password
id: prompt-field-password id: prompt-field-password
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: password
label: Password
type: password type: password
required: true required: true
placeholder: Password placeholder: Password
order: 0 order: 0
placeholder_expression: false placeholder_expression: false
- identifiers: - identifiers:
field_key: password_repeat name: default-recovery-field-password-repeat
label: Password (repeat)
id: prompt-field-password-repeat id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: password_repeat
label: Password (repeat)
type: password type: password
required: true required: true
placeholder: Password (repeat) placeholder: Password (repeat)

View File

@ -0,0 +1,70 @@
version: 1
metadata:
labels:
blueprints.goauthentik.io/description: Migrate to 2023.2, remove unused prompt fields
name: Migration - Remove old prompt fields
entries:
- model: authentik_stages_prompt.prompt
identifiers:
name: admin_email_stage-default-oobe-password
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: attributes.settings.locale_default-user-settings
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: email_default-user-settings
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: name_default-user-settings
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: oobe-header-text_stage-default-oobe-password
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: password_default-password-change-prompt
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: password_repeat_default-password-change-prompt
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: username_default-source-enrollment-prompt
attrs:
field_key: foo
label: foo
type: text
state: absent

2
go.mod
View File

@ -25,7 +25,7 @@ require (
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
goauthentik.io/api/v3 v3.2023010.1 goauthentik.io/api/v3 v3.2023012.2
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b

4
go.sum
View File

@ -382,8 +382,8 @@ go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZp
go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
goauthentik.io/api/v3 v3.2023010.1 h1:DQuf201pD+OKVnzpLKCaKw8IS397ewzMzFIGXyhmQ3w= goauthentik.io/api/v3 v3.2023012.2 h1:aP/JlZCxGaPuV+vPabL3Niz7lWAYnNolrINYSFBDFG0=
goauthentik.io/api/v3 v3.2023010.1/go.mod h1:QM9J32HgYE4gL71lWAfAoXSPdSmLVLW08itfLI3Mo10= goauthentik.io/api/v3 v3.2023012.2/go.mod h1:QM9J32HgYE4gL71lWAfAoXSPdSmLVLW08itfLI3Mo10=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=

View File

@ -1,8 +1,11 @@
package ak package ak
import "context"
type Outpost interface { type Outpost interface {
Start() error Start() error
Stop() error
Refresh() error Refresh() error
TimerFlowCacheExpiry() TimerFlowCacheExpiry(context.Context)
Type() string Type() string
} }

View File

@ -1,15 +1,16 @@
package ak package ak
import ( import (
"context"
"time" "time"
) )
func (a *APIController) startPeriodicalTasks() { func (a *APIController) startPeriodicalTasks() {
go a.Server.TimerFlowCacheExpiry() ctx, canc := context.WithCancel(context.Background())
go func() { defer canc()
for range time.Tick(time.Duration(a.GlobalConfig.CacheTimeoutFlows) * time.Second) { go a.Server.TimerFlowCacheExpiry(ctx)
a.logger.WithField("timer", "cache-timeout").Debug("Running periodical tasks") for range time.Tick(time.Duration(a.GlobalConfig.CacheTimeoutFlows) * time.Second) {
a.Server.TimerFlowCacheExpiry() a.logger.WithField("timer", "cache-timeout").Debug("Running periodical tasks")
} a.Server.TimerFlowCacheExpiry(ctx)
}() }
} }

View File

@ -143,6 +143,10 @@ func (fe *FlowExecutor) GetSession() *http.Cookie {
return fe.session return fe.session
} }
func (fe *FlowExecutor) SetSession(s *http.Cookie) {
fe.session = s
}
// WarmUp Ensure authentik's flow cache is warmed up // WarmUp Ensure authentik's flow cache is warmed up
func (fe *FlowExecutor) WarmUp() error { func (fe *FlowExecutor) WarmUp() error {
gcsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.get_challenge") gcsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.get_challenge")

View File

@ -1,9 +1,14 @@
package bind package bind
import "github.com/nmcclain/ldap" import (
"context"
"github.com/nmcclain/ldap"
)
type Binder interface { type Binder interface {
GetUsername(string) (string, error) GetUsername(string) (string, error)
Bind(username string, req *Request) (ldap.LDAPResultCode, error) Bind(username string, req *Request) (ldap.LDAPResultCode, error)
TimerFlowCacheExpiry() Unbind(username string, req *Request) (ldap.LDAPResultCode, error)
TimerFlowCacheExpiry(context.Context)
} }

View File

@ -0,0 +1,98 @@
package direct
import (
"context"
"github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/bind"
"goauthentik.io/internal/outpost/ldap/flags"
"goauthentik.io/internal/outpost/ldap/metrics"
)
func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResultCode, error) {
fe := flow.NewFlowExecutor(req.Context(), db.si.GetAuthenticationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{
"bindDN": req.BindDN,
"client": req.RemoteAddr(),
"requestId": req.ID(),
})
fe.DelegateClientIP(req.RemoteAddr())
fe.Params.Add("goauthentik.io/outpost/ldap", "true")
fe.Answers[flow.StageIdentification] = username
fe.Answers[flow.StagePassword] = req.BindPW
passed, err := fe.Execute()
flags := flags.UserFlags{
Session: fe.GetSession(),
}
db.si.SetFlags(req.BindDN, &flags)
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "flow_error",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to execute flow")
return ldap.LDAPResultInvalidCredentials, nil
}
if !passed {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "invalid_credentials",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().Info("Invalid credentials")
return ldap.LDAPResultInvalidCredentials, nil
}
access, err := fe.CheckApplicationAccess(db.si.GetAppSlug())
if !access {
req.Log().Info("Access denied for user")
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "access_denied",
"app": db.si.GetAppSlug(),
}).Inc()
return ldap.LDAPResultInsufficientAccessRights, nil
}
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "access_check_fail",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to check access")
return ldap.LDAPResultOperationsError, nil
}
req.Log().Info("User has access")
uisp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.bind.user_info")
// Get user info to store in context
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(context.Background()).Execute()
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "user_info_fail",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to get user info")
return ldap.LDAPResultOperationsError, nil
}
cs := db.SearchAccessCheck(userInfo.User)
flags.UserPk = userInfo.User.Pk
flags.CanSearch = cs != nil
db.si.SetFlags(req.BindDN, &flags)
if flags.CanSearch {
req.Log().WithField("group", cs).Info("Allowed access to search")
}
uisp.Finish()
return ldap.LDAPResultSuccess, nil
}

View File

@ -5,16 +5,10 @@ import (
"errors" "errors"
"strings" "strings"
"github.com/getsentry/sentry-go"
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/flow" "goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/bind"
"goauthentik.io/internal/outpost/ldap/flags"
"goauthentik.io/internal/outpost/ldap/metrics"
"goauthentik.io/internal/outpost/ldap/server" "goauthentik.io/internal/outpost/ldap/server"
"goauthentik.io/internal/outpost/ldap/utils" "goauthentik.io/internal/outpost/ldap/utils"
) )
@ -53,90 +47,6 @@ func (db *DirectBinder) GetUsername(dn string) (string, error) {
return "", errors.New("failed to find cn") return "", errors.New("failed to find cn")
} }
func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResultCode, error) {
fe := flow.NewFlowExecutor(req.Context(), db.si.GetFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{
"bindDN": req.BindDN,
"client": req.RemoteAddr(),
"requestId": req.ID(),
})
fe.DelegateClientIP(req.RemoteAddr())
fe.Params.Add("goauthentik.io/outpost/ldap", "true")
fe.Answers[flow.StageIdentification] = username
fe.Answers[flow.StagePassword] = req.BindPW
passed, err := fe.Execute()
flags := flags.UserFlags{
Session: fe.GetSession(),
}
db.si.SetFlags(req.BindDN, flags)
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "flow_error",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to execute flow")
return ldap.LDAPResultInvalidCredentials, nil
}
if !passed {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "invalid_credentials",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().Info("Invalid credentials")
return ldap.LDAPResultInvalidCredentials, nil
}
access, err := fe.CheckApplicationAccess(db.si.GetAppSlug())
if !access {
req.Log().Info("Access denied for user")
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "access_denied",
"app": db.si.GetAppSlug(),
}).Inc()
return ldap.LDAPResultInsufficientAccessRights, nil
}
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "access_check_fail",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to check access")
return ldap.LDAPResultOperationsError, nil
}
req.Log().Info("User has access")
uisp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.bind.user_info")
// Get user info to store in context
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(context.Background()).Execute()
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "user_info_fail",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to get user info")
return ldap.LDAPResultOperationsError, nil
}
cs := db.SearchAccessCheck(userInfo.User)
flags.UserPk = userInfo.User.Pk
flags.CanSearch = cs != nil
db.si.SetFlags(req.BindDN, flags)
if flags.CanSearch {
req.Log().WithField("group", cs).Info("Allowed access to search")
}
uisp.Finish()
return ldap.LDAPResultSuccess, nil
}
// SearchAccessCheck Check if the current user is allowed to search // SearchAccessCheck Check if the current user is allowed to search
func (db *DirectBinder) SearchAccessCheck(user api.UserSelf) *string { func (db *DirectBinder) SearchAccessCheck(user api.UserSelf) *string {
for _, group := range user.Groups { for _, group := range user.Groups {
@ -153,8 +63,8 @@ func (db *DirectBinder) SearchAccessCheck(user api.UserSelf) *string {
return nil return nil
} }
func (db *DirectBinder) TimerFlowCacheExpiry() { func (db *DirectBinder) TimerFlowCacheExpiry(ctx context.Context) {
fe := flow.NewFlowExecutor(context.Background(), db.si.GetFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{}) fe := flow.NewFlowExecutor(ctx, db.si.GetAuthenticationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{})
fe.Params.Add("goauthentik.io/outpost/ldap", "true") fe.Params.Add("goauthentik.io/outpost/ldap", "true")
fe.Params.Add("goauthentik.io/outpost/ldap-warmup", "true") fe.Params.Add("goauthentik.io/outpost/ldap-warmup", "true")

View File

@ -0,0 +1,29 @@
package direct
import (
"github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/bind"
)
func (db *DirectBinder) Unbind(username string, req *bind.Request) (ldap.LDAPResultCode, error) {
flags := db.si.GetFlags(req.BindDN)
if flags == nil || flags.Session == nil {
return ldap.LDAPResultSuccess, nil
}
fe := flow.NewFlowExecutor(req.Context(), db.si.GetInvalidationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{
"boundDN": req.BindDN,
"client": req.RemoteAddr(),
"requestId": req.ID(),
})
fe.SetSession(flags.Session)
fe.DelegateClientIP(req.RemoteAddr())
fe.Params.Add("goauthentik.io/outpost/ldap", "true")
_, err := fe.Execute()
if err != nil {
db.log.WithError(err).Warning("failed to logout user")
}
db.si.SetFlags(req.BindDN, nil)
return ldap.LDAPResultSuccess, nil
}

View File

@ -27,10 +27,11 @@ type ProviderInstance struct {
searcher search.Searcher searcher search.Searcher
binder bind.Binder binder bind.Binder
appSlug string appSlug string
flowSlug string authenticationFlowSlug string
s *LDAPServer invalidationFlowSlug string
log *log.Entry s *LDAPServer
log *log.Entry
tlsServerName *string tlsServerName *string
cert *tls.Certificate cert *tls.Certificate
@ -79,9 +80,13 @@ func (pi *ProviderInstance) GetFlags(dn string) *flags.UserFlags {
return flags return flags
} }
func (pi *ProviderInstance) SetFlags(dn string, flag flags.UserFlags) { func (pi *ProviderInstance) SetFlags(dn string, flag *flags.UserFlags) {
pi.boundUsersMutex.Lock() pi.boundUsersMutex.Lock()
pi.boundUsers[dn] = &flag if flag == nil {
delete(pi.boundUsers, dn)
} else {
pi.boundUsers[dn] = flag
}
pi.boundUsersMutex.Unlock() pi.boundUsersMutex.Unlock()
} }
@ -89,8 +94,12 @@ func (pi *ProviderInstance) GetAppSlug() string {
return pi.appSlug return pi.appSlug
} }
func (pi *ProviderInstance) GetFlowSlug() string { func (pi *ProviderInstance) GetAuthenticationFlowSlug() string {
return pi.flowSlug return pi.authenticationFlowSlug
}
func (pi *ProviderInstance) GetInvalidationFlowSlug() string {
return pi.invalidationFlowSlug
} }
func (pi *ProviderInstance) GetSearchAllowedGroups() []*strfmt.UUID { func (pi *ProviderInstance) GetSearchAllowedGroups() []*strfmt.UUID {

View File

@ -1,6 +1,7 @@
package ldap package ldap
import ( import (
"context"
"crypto/tls" "crypto/tls"
"net" "net"
"sync" "sync"
@ -40,6 +41,7 @@ func NewServer(ac *ak.APIController) *LDAPServer {
} }
ls.defaultCert = &defaultCert ls.defaultCert = &defaultCert
s.BindFunc("", ls) s.BindFunc("", ls)
s.UnbindFunc("", ls)
s.SearchFunc("", ls) s.SearchFunc("", ls)
return ls return ls
} }
@ -92,9 +94,13 @@ func (ls *LDAPServer) Start() error {
return nil return nil
} }
func (ls *LDAPServer) TimerFlowCacheExpiry() { func (ls *LDAPServer) Stop() error {
return nil
}
func (ls *LDAPServer) TimerFlowCacheExpiry(ctx context.Context) {
for _, p := range ls.providers { for _, p := range ls.providers {
ls.log.WithField("flow", p.flowSlug).Debug("Pre-heating flow cache") ls.log.WithField("flow", p.authenticationFlowSlug).Debug("Pre-heating flow cache")
p.binder.TimerFlowCacheExpiry() p.binder.TimerFlowCacheExpiry(ctx)
} }
} }

View File

@ -28,6 +28,16 @@ func (ls *LDAPServer) getCurrentProvider(pk int32) *ProviderInstance {
return nil return nil
} }
func (ls *LDAPServer) getInvalidationFlow() string {
req, _, err := ls.ac.Client.CoreApi.CoreTenantsCurrentRetrieve(context.Background()).Execute()
if err != nil {
ls.log.WithError(err).Warning("failed to fetch tenant config")
return ""
}
flow := req.GetFlowInvalidation()
return flow
}
func (ls *LDAPServer) Refresh() error { func (ls *LDAPServer) Refresh() error {
outposts, _, err := ls.ac.Client.OutpostsApi.OutpostsLdapList(context.Background()).Execute() outposts, _, err := ls.ac.Client.OutpostsApi.OutpostsLdapList(context.Background()).Execute()
if err != nil { if err != nil {
@ -37,6 +47,7 @@ func (ls *LDAPServer) Refresh() error {
return errors.New("no ldap provider defined") return errors.New("no ldap provider defined")
} }
providers := make([]*ProviderInstance, len(outposts.Results)) providers := make([]*ProviderInstance, len(outposts.Results))
invalidationFlow := ls.getInvalidationFlow()
for idx, provider := range outposts.Results { for idx, provider := range outposts.Results {
userDN := strings.ToLower(fmt.Sprintf("ou=%s,%s", constants.OUUsers, *provider.BaseDn)) userDN := strings.ToLower(fmt.Sprintf("ou=%s,%s", constants.OUUsers, *provider.BaseDn))
groupDN := strings.ToLower(fmt.Sprintf("ou=%s,%s", constants.OUGroups, *provider.BaseDn)) groupDN := strings.ToLower(fmt.Sprintf("ou=%s,%s", constants.OUGroups, *provider.BaseDn))
@ -53,22 +64,23 @@ func (ls *LDAPServer) Refresh() error {
} }
providers[idx] = &ProviderInstance{ providers[idx] = &ProviderInstance{
BaseDN: *provider.BaseDn, BaseDN: *provider.BaseDn,
VirtualGroupDN: virtualGroupDN, VirtualGroupDN: virtualGroupDN,
GroupDN: groupDN, GroupDN: groupDN,
UserDN: userDN, UserDN: userDN,
appSlug: provider.ApplicationSlug, appSlug: provider.ApplicationSlug,
flowSlug: provider.BindFlowSlug, authenticationFlowSlug: provider.BindFlowSlug,
searchAllowedGroups: []*strfmt.UUID{(*strfmt.UUID)(provider.SearchGroup.Get())}, invalidationFlowSlug: invalidationFlow,
boundUsersMutex: sync.RWMutex{}, searchAllowedGroups: []*strfmt.UUID{(*strfmt.UUID)(provider.SearchGroup.Get())},
boundUsers: users, boundUsersMutex: sync.RWMutex{},
s: ls, boundUsers: users,
log: logger, s: ls,
tlsServerName: provider.TlsServerName, log: logger,
uidStartNumber: *provider.UidStartNumber, tlsServerName: provider.TlsServerName,
gidStartNumber: *provider.GidStartNumber, uidStartNumber: *provider.UidStartNumber,
outpostName: ls.ac.Outpost.Name, gidStartNumber: *provider.GidStartNumber,
outpostPk: provider.Pk, outpostName: ls.ac.Outpost.Name,
outpostPk: provider.Pk,
} }
if kp := provider.Certificate.Get(); kp != nil { if kp := provider.Certificate.Get(); kp != nil {
err := ls.cs.AddKeypair(*kp) err := ls.cs.AddKeypair(*kp)

View File

@ -11,7 +11,8 @@ type LDAPServerInstance interface {
GetAPIClient() *api.APIClient GetAPIClient() *api.APIClient
GetOutpostName() string GetOutpostName() string
GetFlowSlug() string GetAuthenticationFlowSlug() string
GetInvalidationFlowSlug() string
GetAppSlug() string GetAppSlug() string
GetSearchAllowedGroups() []*strfmt.UUID GetSearchAllowedGroups() []*strfmt.UUID
@ -32,7 +33,7 @@ type LDAPServerInstance interface {
UsersForGroup(api.Group) []string UsersForGroup(api.Group) []string
GetFlags(dn string) *flags.UserFlags GetFlags(dn string) *flags.UserFlags
SetFlags(dn string, flags flags.UserFlags) SetFlags(dn string, flags *flags.UserFlags)
GetBaseEntry() *ldap.Entry GetBaseEntry() *ldap.Entry
GetNeededObjects(int, string, string) (bool, bool) GetNeededObjects(int, string, string) (bool, bool)

View File

@ -0,0 +1,53 @@
package ldap
import (
"net"
"github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ldap/bind"
"goauthentik.io/internal/outpost/ldap/metrics"
)
func (ls *LDAPServer) Unbind(boundDN string, conn net.Conn) (ldap.LDAPResultCode, error) {
req, span := bind.NewRequest(boundDN, "", conn)
selectedApp := ""
defer func() {
span.Finish()
metrics.Requests.With(prometheus.Labels{
"outpost_name": ls.ac.Outpost.Name,
"type": "unbind",
"app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime)))
req.Log().WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Unbind request")
}()
defer func() {
err := recover()
if err == nil {
return
}
log.WithError(err.(error)).Error("recover in bind request")
sentry.CaptureException(err.(error))
}()
for _, instance := range ls.providers {
username, err := instance.binder.GetUsername(boundDN)
if err == nil {
selectedApp = instance.GetAppSlug()
return instance.binder.Unbind(username, req)
} else {
req.Log().WithError(err).Debug("Username not for instance")
}
}
req.Log().WithField("request", "unbind").Warning("No provider found for request")
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": ls.ac.Outpost.Name,
"type": "unbind",
"reason": "no_provider",
"app": "",
}).Inc()
return ldap.LDAPResultOperationsError, nil
}

View File

@ -81,7 +81,7 @@ func (ps *ProxyServer) Type() string {
return "proxy" return "proxy"
} }
func (ps *ProxyServer) TimerFlowCacheExpiry() {} func (ps *ProxyServer) TimerFlowCacheExpiry(context.Context) {}
func (ps *ProxyServer) GetCertificate(serverName string) *tls.Certificate { func (ps *ProxyServer) GetCertificate(serverName string) *tls.Certificate {
app, ok := ps.apps[serverName] app, ok := ps.apps[serverName]
@ -163,6 +163,10 @@ func (ps *ProxyServer) Start() error {
return nil return nil
} }
func (ps *ProxyServer) Stop() error {
return nil
}
func (ps *ProxyServer) serve(listener net.Listener) { func (ps *ProxyServer) serve(listener net.Listener) {
srv := &http.Server{Handler: ps.mux} srv := &http.Server{Handler: ps.mux}

View File

@ -61,7 +61,7 @@ func (ws *WebServer) configureStatic() {
func (ws *WebServer) staticHeaderMiddleware(h http.Handler) http.Handler { func (ws *WebServer) staticHeaderMiddleware(h http.Handler) http.Handler {
etagHandler := etag.Handler(h, false) etagHandler := etag.Handler(h, false)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "\"public, no-transform\"") w.Header().Set("Cache-Control", "public, no-transform")
w.Header().Set("X-authentik-version", constants.VERSION) w.Header().Set("X-authentik-version", constants.VERSION)
w.Header().Set("Vary", "X-authentik-version, Etag") w.Header().Set("Vary", "X-authentik-version, Etag")
etagHandler.ServeHTTP(w, r) etagHandler.ServeHTTP(w, r)

120
poetry.lock generated
View File

@ -789,63 +789,63 @@ files = [
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "7.0.5" version = "7.1.0"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "coverage-7.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b"}, {file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"},
{file = "coverage-7.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89"}, {file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"},
{file = "coverage-7.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40"}, {file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"},
{file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66e50680e888840c0995f2ad766e726ce71ca682e3c5f4eee82272c7671d38a2"}, {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c"},
{file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9fed35ca8c6e946e877893bbac022e8563b94404a605af1d1e6accc7eb73289"}, {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada"},
{file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d8d04e755934195bdc1db45ba9e040b8d20d046d04d6d77e71b3b34a8cc002d0"}, {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f"},
{file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e109f1c9a3ece676597831874126555997c48f62bddbcace6ed17be3e372de8"}, {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a"},
{file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0a1890fca2962c4f1ad16551d660b46ea77291fba2cc21c024cd527b9d9c8809"}, {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c"},
{file = "coverage-7.0.5-cp310-cp310-win32.whl", hash = "sha256:be9fcf32c010da0ba40bf4ee01889d6c737658f4ddff160bd7eb9cac8f094b21"}, {file = "coverage-7.1.0-cp310-cp310-win32.whl", hash = "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352"},
{file = "coverage-7.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:cbfcba14a3225b055a28b3199c3d81cd0ab37d2353ffd7f6fd64844cebab31ad"}, {file = "coverage-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038"},
{file = "coverage-7.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30b5fec1d34cc932c1bc04017b538ce16bf84e239378b8f75220478645d11fca"}, {file = "coverage-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040"},
{file = "coverage-7.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1caed2367b32cc80a2b7f58a9f46658218a19c6cfe5bc234021966dc3daa01f0"}, {file = "coverage-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a"},
{file = "coverage-7.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d254666d29540a72d17cc0175746cfb03d5123db33e67d1020e42dae611dc196"}, {file = "coverage-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f"},
{file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19245c249aa711d954623d94f23cc94c0fd65865661f20b7781210cb97c471c0"}, {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222"},
{file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b05ed4b35bf6ee790832f68932baf1f00caa32283d66cc4d455c9e9d115aafc"}, {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146"},
{file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:29de916ba1099ba2aab76aca101580006adfac5646de9b7c010a0f13867cba45"}, {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b"},
{file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e057e74e53db78122a3979f908973e171909a58ac20df05c33998d52e6d35757"}, {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"},
{file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:411d4ff9d041be08fdfc02adf62e89c735b9468f6d8f6427f8a14b6bb0a85095"}, {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e"},
{file = "coverage-7.0.5-cp311-cp311-win32.whl", hash = "sha256:52ab14b9e09ce052237dfe12d6892dd39b0401690856bcfe75d5baba4bfe2831"}, {file = "coverage-7.1.0-cp311-cp311-win32.whl", hash = "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7"},
{file = "coverage-7.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:1f66862d3a41674ebd8d1a7b6f5387fe5ce353f8719040a986551a545d7d83ea"}, {file = "coverage-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c"},
{file = "coverage-7.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b69522b168a6b64edf0c33ba53eac491c0a8f5cc94fa4337f9c6f4c8f2f5296c"}, {file = "coverage-7.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d"},
{file = "coverage-7.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436e103950d05b7d7f55e39beeb4d5be298ca3e119e0589c0227e6d0b01ee8c7"}, {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a"},
{file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c56bec53d6e3154eaff6ea941226e7bd7cc0d99f9b3756c2520fc7a94e6d96"}, {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8"},
{file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a38362528a9115a4e276e65eeabf67dcfaf57698e17ae388599568a78dcb029"}, {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050"},
{file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f67472c09a0c7486e27f3275f617c964d25e35727af952869dd496b9b5b7f6a3"}, {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c"},
{file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:220e3fa77d14c8a507b2d951e463b57a1f7810a6443a26f9b7591ef39047b1b2"}, {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d"},
{file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ecb0f73954892f98611e183f50acdc9e21a4653f294dfbe079da73c6378a6f47"}, {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3"},
{file = "coverage-7.0.5-cp37-cp37m-win32.whl", hash = "sha256:d8f3e2e0a1d6777e58e834fd5a04657f66affa615dae61dd67c35d1568c38882"}, {file = "coverage-7.1.0-cp37-cp37m-win32.whl", hash = "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73"},
{file = "coverage-7.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9e662e6fc4f513b79da5d10a23edd2b87685815b337b1a30cd11307a6679148d"}, {file = "coverage-7.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5"},
{file = "coverage-7.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:790e4433962c9f454e213b21b0fd4b42310ade9c077e8edcb5113db0818450cb"}, {file = "coverage-7.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06"},
{file = "coverage-7.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49640bda9bda35b057b0e65b7c43ba706fa2335c9a9896652aebe0fa399e80e6"}, {file = "coverage-7.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52"},
{file = "coverage-7.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d66187792bfe56f8c18ba986a0e4ae44856b1c645336bd2c776e3386da91e1dd"}, {file = "coverage-7.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851"},
{file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:276f4cd0001cd83b00817c8db76730938b1ee40f4993b6a905f40a7278103b3a"}, {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d"},
{file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95304068686545aa368b35dfda1cdfbbdbe2f6fe43de4a2e9baa8ebd71be46e2"}, {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0"},
{file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:17e01dd8666c445025c29684d4aabf5a90dc6ef1ab25328aa52bedaa95b65ad7"}, {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912"},
{file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea76dbcad0b7b0deb265d8c36e0801abcddf6cc1395940a24e3595288b405ca0"}, {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8"},
{file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:50a6adc2be8edd7ee67d1abc3cd20678987c7b9d79cd265de55941e3d0d56499"}, {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0"},
{file = "coverage-7.0.5-cp38-cp38-win32.whl", hash = "sha256:e4ce984133b888cc3a46867c8b4372c7dee9cee300335e2925e197bcd45b9e16"}, {file = "coverage-7.1.0-cp38-cp38-win32.whl", hash = "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab"},
{file = "coverage-7.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:4a950f83fd3f9bca23b77442f3a2b2ea4ac900944d8af9993743774c4fdc57af"}, {file = "coverage-7.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c"},
{file = "coverage-7.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c2155943896ac78b9b0fd910fb381186d0c345911f5333ee46ac44c8f0e43ab"}, {file = "coverage-7.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6"},
{file = "coverage-7.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54f7e9705e14b2c9f6abdeb127c390f679f6dbe64ba732788d3015f7f76ef637"}, {file = "coverage-7.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa"},
{file = "coverage-7.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee30375b409d9a7ea0f30c50645d436b6f5dfee254edffd27e45a980ad2c7f4"}, {file = "coverage-7.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc"},
{file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b78729038abea6a5df0d2708dce21e82073463b2d79d10884d7d591e0f385ded"}, {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311"},
{file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13250b1f0bd023e0c9f11838bdeb60214dd5b6aaf8e8d2f110c7e232a1bff83b"}, {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063"},
{file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c407b1950b2d2ffa091f4e225ca19a66a9bd81222f27c56bd12658fc5ca1209"}, {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09"},
{file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c76a3075e96b9c9ff00df8b5f7f560f5634dffd1658bafb79eb2682867e94f78"}, {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8"},
{file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f26648e1b3b03b6022b48a9b910d0ae209e2d51f50441db5dce5b530fad6d9b1"}, {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c"},
{file = "coverage-7.0.5-cp39-cp39-win32.whl", hash = "sha256:ba3027deb7abf02859aca49c865ece538aee56dcb4871b4cced23ba4d5088904"}, {file = "coverage-7.1.0-cp39-cp39-win32.whl", hash = "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4"},
{file = "coverage-7.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:949844af60ee96a376aac1ded2a27e134b8c8d35cc006a52903fc06c24a3296f"}, {file = "coverage-7.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3"},
{file = "coverage-7.0.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0"}, {file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"},
{file = "coverage-7.0.5.tar.gz", hash = "sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45"}, {file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"},
] ]
[package.extras] [package.extras]
@ -2851,14 +2851,14 @@ pyasn1 = ">=0.1.3"
[[package]] [[package]]
name = "selenium" name = "selenium"
version = "4.7.2" version = "4.8.0"
description = "" description = ""
category = "dev" category = "dev"
optional = false optional = false
python-versions = "~=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "selenium-4.7.2-py3-none-any.whl", hash = "sha256:06a1c7d9f313130b21c3218ddd8852070d0e7419afdd31f96160cd576555a5ce"}, {file = "selenium-4.8.0-py3-none-any.whl", hash = "sha256:20f28ee4ea9b273b4112a7df5276ebb3052f79ff6eff42a564db6143e5926683"},
{file = "selenium-4.7.2.tar.gz", hash = "sha256:3aefa14a28a42e520550c1cd0f29cf1d566328186ea63aa9a3e01fb265b5894d"}, {file = "selenium-4.8.0.tar.gz", hash = "sha256:fee36724d6cf0b18c73781bb8ec7be4a35ab1e2564e64e64e64da75e50e052af"},
] ]
[package.dependencies] [package.dependencies]
@ -2869,14 +2869,14 @@ urllib3 = {version = ">=1.26,<2.0", extras = ["socks"]}
[[package]] [[package]]
name = "sentry-sdk" name = "sentry-sdk"
version = "1.13.0" version = "1.14.0"
description = "Python client for Sentry (https://sentry.io)" description = "Python client for Sentry (https://sentry.io)"
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "sentry-sdk-1.13.0.tar.gz", hash = "sha256:72da0766c3069a3941eadbdfa0996f83f5a33e55902a19ba399557cfee1dddcc"}, {file = "sentry-sdk-1.14.0.tar.gz", hash = "sha256:273fe05adf052b40fd19f6d4b9a5556316807246bd817e5e3482930730726bb0"},
{file = "sentry_sdk-1.13.0-py2.py3-none-any.whl", hash = "sha256:b7ff6318183e551145b5c4766eb65b59ad5b63ff234dffddc5fb50340cad6729"}, {file = "sentry_sdk-1.14.0-py2.py3-none-any.whl", hash = "sha256:72c00322217d813cf493fe76590b23a757e063ff62fec59299f4af7201dd4448"},
] ]
[package.dependencies] [package.dependencies]
@ -2894,7 +2894,7 @@ falcon = ["falcon (>=1.4)"]
fastapi = ["fastapi (>=0.79.0)"] fastapi = ["fastapi (>=0.79.0)"]
flask = ["blinker (>=1.1)", "flask (>=0.11)"] flask = ["blinker (>=1.1)", "flask (>=0.11)"]
httpx = ["httpx (>=0.16.0)"] httpx = ["httpx (>=0.16.0)"]
opentelemetry = ["opentelemetry-distro (>=0.350b0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
pure-eval = ["asttokens", "executing", "pure-eval"] pure-eval = ["asttokens", "executing", "pure-eval"]
pymongo = ["pymongo (>=3.1)"] pymongo = ["pymongo (>=3.1)"]
pyspark = ["pyspark (>=2.4.4)"] pyspark = ["pyspark (>=2.4.4)"]

View File

@ -23008,6 +23008,10 @@ paths:
name: label name: label
schema: schema:
type: string type: string
- in: query
name: name
schema:
type: string
- name: ordering - name: ordering
required: false required: false
in: query in: query
@ -34263,6 +34267,9 @@ components:
type: object type: object
description: Prompt Serializer description: Prompt Serializer
properties: properties:
name:
type: string
minLength: 1
field_key: field_key:
type: string type: string
minLength: 1 minLength: 1
@ -35278,6 +35285,8 @@ components:
format: uuid format: uuid
readOnly: true readOnly: true
title: Prompt uuid title: Prompt uuid
name:
type: string
field_key: field_key:
type: string type: string
description: Name of the form field, also used to store the value description: Name of the form field, also used to store the value
@ -35304,6 +35313,7 @@ components:
required: required:
- field_key - field_key
- label - label
- name
- pk - pk
- type - type
PromptChallenge: PromptChallenge:
@ -35345,6 +35355,9 @@ components:
type: object type: object
description: Prompt Serializer description: Prompt Serializer
properties: properties:
name:
type: string
minLength: 1
field_key: field_key:
type: string type: string
minLength: 1 minLength: 1
@ -35373,6 +35386,7 @@ components:
required: required:
- field_key - field_key
- label - label
- name
- type - type
PromptStage: PromptStage:
type: object type: object

View File

@ -26,8 +26,8 @@ class TestFlowsAuthenticator(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
def test_totp_validate(self): def test_totp_validate(self):
"""test flow with otp stages""" """test flow with otp stages"""
@ -52,10 +52,10 @@ class TestFlowsAuthenticator(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint("default/20-flow-default-authenticator-totp-setup.yaml") @apply_blueprint("default/flow-default-authenticator-totp-setup.yaml")
def test_totp_setup(self): def test_totp_setup(self):
"""test TOTP Setup stage""" """test TOTP Setup stage"""
flow: Flow = Flow.objects.get(slug="default-authentication-flow") flow: Flow = Flow.objects.get(slug="default-authentication-flow")
@ -98,10 +98,10 @@ class TestFlowsAuthenticator(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint("default/20-flow-default-authenticator-static-setup.yaml") @apply_blueprint("default/flow-default-authenticator-static-setup.yaml")
def test_static_setup(self): def test_static_setup(self):
"""test Static OTP Setup stage""" """test Static OTP Setup stage"""
flow: Flow = Flow.objects.get(slug="default-authentication-flow") flow: Flow = Flow.objects.get(slug="default-authentication-flow")

View File

@ -41,19 +41,28 @@ class TestFlowsEnroll(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
def test_enroll_2_step(self): def test_enroll_2_step(self):
"""Test 2-step enroll flow""" """Test 2-step enroll flow"""
# First stage fields # First stage fields
username_prompt = Prompt.objects.create( username_prompt = Prompt.objects.create(
field_key="username", label="Username", order=0, type=FieldTypes.TEXT name=generate_id(),
field_key="username",
label="Username",
order=0,
type=FieldTypes.TEXT,
) )
password = Prompt.objects.create( password = Prompt.objects.create(
field_key="password", label="Password", order=1, type=FieldTypes.PASSWORD name=generate_id(),
field_key="password",
label="Password",
order=1,
type=FieldTypes.PASSWORD,
) )
password_repeat = Prompt.objects.create( password_repeat = Prompt.objects.create(
name=generate_id(),
field_key="password_repeat", field_key="password_repeat",
label="Password (repeat)", label="Password (repeat)",
order=2, order=2,
@ -62,10 +71,10 @@ class TestFlowsEnroll(SeleniumTestCase):
# Second stage fields # Second stage fields
name_field = Prompt.objects.create( name_field = Prompt.objects.create(
field_key="name", label="Name", order=0, type=FieldTypes.TEXT name=generate_id(), field_key="name", label="Name", order=0, type=FieldTypes.TEXT
) )
email = Prompt.objects.create( email = Prompt.objects.create(
field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL name=generate_id(), field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL
) )
# Stages # Stages
@ -107,19 +116,28 @@ class TestFlowsEnroll(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
def test_enroll_email(self): def test_enroll_email(self):
"""Test enroll with Email verification""" """Test enroll with Email verification"""
# First stage fields # First stage fields
username_prompt = Prompt.objects.create( username_prompt = Prompt.objects.create(
field_key="username", label="Username", order=0, type=FieldTypes.TEXT name=generate_id(),
field_key="username",
label="Username",
order=0,
type=FieldTypes.TEXT,
) )
password = Prompt.objects.create( password = Prompt.objects.create(
field_key="password", label="Password", order=1, type=FieldTypes.PASSWORD name=generate_id(),
field_key="password",
label="Password",
order=1,
type=FieldTypes.PASSWORD,
) )
password_repeat = Prompt.objects.create( password_repeat = Prompt.objects.create(
name=generate_id(),
field_key="password_repeat", field_key="password_repeat",
label="Password (repeat)", label="Password (repeat)",
order=2, order=2,
@ -128,10 +146,10 @@ class TestFlowsEnroll(SeleniumTestCase):
# Second stage fields # Second stage fields
name_field = Prompt.objects.create( name_field = Prompt.objects.create(
field_key="name", label="Name", order=0, type=FieldTypes.TEXT name=generate_id(), field_key="name", label="Name", order=0, type=FieldTypes.TEXT
) )
email = Prompt.objects.create( email = Prompt.objects.create(
field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL name=generate_id(), field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL
) )
# Stages # Stages

View File

@ -12,8 +12,8 @@ class TestFlowsLogin(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
def test_login(self): def test_login(self):
"""test default login flow""" """test default login flow"""

View File

@ -18,10 +18,10 @@ class TestFlowsStageSetup(SeleniumTestCase):
"""test stage setup flows""" """test stage setup flows"""
@retry() @retry()
@apply_blueprint("default/0-flow-password-change.yaml") @apply_blueprint("default/flow-password-change.yaml")
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
def test_password_change(self): def test_password_change(self):
"""test password change flow""" """test password change flow"""

View File

@ -81,8 +81,8 @@ class TestProviderLDAP(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
def test_ldap_bind_success(self): def test_ldap_bind_success(self):
"""Test simple bind""" """Test simple bind"""
@ -108,8 +108,8 @@ class TestProviderLDAP(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
def test_ldap_bind_success_ssl(self): def test_ldap_bind_success_ssl(self):
"""Test simple bind with ssl""" """Test simple bind with ssl"""
@ -135,8 +135,8 @@ class TestProviderLDAP(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
def test_ldap_bind_fail(self): def test_ldap_bind_fail(self):
"""Test simple bind (failed)""" """Test simple bind (failed)"""
@ -160,8 +160,8 @@ class TestProviderLDAP(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@reconcile_app("authentik_outposts") @reconcile_app("authentik_outposts")
def test_ldap_bind_search(self): def test_ldap_bind_search(self):

View File

@ -58,12 +58,12 @@ class TestProviderOAuth2Github(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-oauth2.yaml", "system/providers-oauth2.yaml",
@ -114,12 +114,12 @@ class TestProviderOAuth2Github(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-oauth2.yaml", "system/providers-oauth2.yaml",
@ -189,12 +189,12 @@ class TestProviderOAuth2Github(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_denied(self): def test_denied(self):

View File

@ -67,12 +67,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-oauth2.yaml", "system/providers-oauth2.yaml",
@ -116,12 +116,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-oauth2.yaml", "system/providers-oauth2.yaml",
@ -178,12 +178,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-oauth2.yaml", "system/providers-oauth2.yaml",
@ -249,12 +249,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-oauth2.yaml", "system/providers-oauth2.yaml",
@ -329,12 +329,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-oauth2.yaml", "system/providers-oauth2.yaml",

View File

@ -65,12 +65,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_redirect_uri_error(self): def test_redirect_uri_error(self):
@ -111,12 +111,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
@apply_blueprint("system/providers-oauth2.yaml") @apply_blueprint("system/providers-oauth2.yaml")
@ -166,12 +166,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
@apply_blueprint("system/providers-oauth2.yaml") @apply_blueprint("system/providers-oauth2.yaml")
@ -236,12 +236,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_authorization_denied(self): def test_authorization_denied(self):

View File

@ -65,12 +65,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_redirect_uri_error(self): def test_redirect_uri_error(self):
@ -111,12 +111,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
@apply_blueprint("system/providers-oauth2.yaml") @apply_blueprint("system/providers-oauth2.yaml")
@ -161,12 +161,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
@apply_blueprint("system/providers-oauth2.yaml") @apply_blueprint("system/providers-oauth2.yaml")
@ -227,12 +227,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_authorization_denied(self): def test_authorization_denied(self):

View File

@ -57,12 +57,12 @@ class TestProviderProxy(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-oauth2.yaml", "system/providers-oauth2.yaml",
@ -123,12 +123,12 @@ class TestProviderProxy(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-oauth2.yaml", "system/providers-oauth2.yaml",
@ -200,12 +200,12 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@reconcile_app("authentik_crypto") @reconcile_app("authentik_crypto")
def test_proxy_connectivity(self): def test_proxy_connectivity(self):

View File

@ -64,12 +64,12 @@ class TestProviderSAML(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-saml.yaml", "system/providers-saml.yaml",
@ -133,12 +133,12 @@ class TestProviderSAML(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-saml.yaml", "system/providers-saml.yaml",
@ -217,12 +217,12 @@ class TestProviderSAML(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-saml.yaml", "system/providers-saml.yaml",
@ -301,12 +301,12 @@ class TestProviderSAML(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-saml.yaml", "system/providers-saml.yaml",
@ -376,12 +376,12 @@ class TestProviderSAML(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-saml.yaml", "system/providers-saml.yaml",
@ -425,12 +425,12 @@ class TestProviderSAML(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"system/providers-saml.yaml", "system/providers-saml.yaml",

View File

@ -143,17 +143,17 @@ class TestSourceOAuth2(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-source-authentication.yaml", "default/flow-default-source-authentication.yaml",
"default/20-flow-default-source-enrollment.yaml", "default/flow-default-source-enrollment.yaml",
"default/20-flow-default-source-pre-authentication.yaml", "default/flow-default-source-pre-authentication.yaml",
) )
def test_oauth_enroll(self): def test_oauth_enroll(self):
"""test OAuth Source With With OIDC""" """test OAuth Source With With OIDC"""
@ -200,12 +200,12 @@ class TestSourceOAuth2(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml", "default/flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml", "default/flow-default-provider-authorization-implicit-consent.yaml",
) )
def test_oauth_enroll_auth(self): def test_oauth_enroll_auth(self):
"""test OAuth Source With With OIDC (enroll and authenticate again)""" """test OAuth Source With With OIDC (enroll and authenticate again)"""
@ -292,13 +292,13 @@ class TestSourceOAuth1(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-source-authentication.yaml", "default/flow-default-source-authentication.yaml",
"default/20-flow-default-source-enrollment.yaml", "default/flow-default-source-enrollment.yaml",
"default/20-flow-default-source-pre-authentication.yaml", "default/flow-default-source-pre-authentication.yaml",
) )
def test_oauth_enroll(self): def test_oauth_enroll(self):
"""test OAuth Source With With OIDC""" """test OAuth Source With With OIDC"""

View File

@ -96,13 +96,13 @@ class TestSourceSAML(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-source-authentication.yaml", "default/flow-default-source-authentication.yaml",
"default/20-flow-default-source-enrollment.yaml", "default/flow-default-source-enrollment.yaml",
"default/20-flow-default-source-pre-authentication.yaml", "default/flow-default-source-pre-authentication.yaml",
) )
def test_idp_redirect(self): def test_idp_redirect(self):
"""test SAML Source With redirect binding""" """test SAML Source With redirect binding"""
@ -166,13 +166,13 @@ class TestSourceSAML(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-source-authentication.yaml", "default/flow-default-source-authentication.yaml",
"default/20-flow-default-source-enrollment.yaml", "default/flow-default-source-enrollment.yaml",
"default/20-flow-default-source-pre-authentication.yaml", "default/flow-default-source-pre-authentication.yaml",
) )
def test_idp_post(self): def test_idp_post(self):
"""test SAML Source With post binding""" """test SAML Source With post binding"""
@ -249,13 +249,13 @@ class TestSourceSAML(SeleniumTestCase):
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/10-flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml", "default/flow-default-invalidation-flow.yaml",
) )
@apply_blueprint( @apply_blueprint(
"default/20-flow-default-source-authentication.yaml", "default/flow-default-source-authentication.yaml",
"default/20-flow-default-source-enrollment.yaml", "default/flow-default-source-enrollment.yaml",
"default/20-flow-default-source-pre-authentication.yaml", "default/flow-default-source-pre-authentication.yaml",
) )
def test_idp_post_auto(self): def test_idp_post_auto(self):
"""test SAML Source With post binding (auto redirect)""" """test SAML Source With post binding (auto redirect)"""

432
web/package-lock.json generated
View File

@ -21,13 +21,13 @@
"@codemirror/legacy-modes": "^6.3.1", "@codemirror/legacy-modes": "^6.3.1",
"@formatjs/intl-listformat": "^7.1.7", "@formatjs/intl-listformat": "^7.1.7",
"@fortawesome/fontawesome-free": "^6.2.1", "@fortawesome/fontawesome-free": "^6.2.1",
"@goauthentik/api": "^2023.1.0-1674058489", "@goauthentik/api": "^2023.1.2-1674559422",
"@hcaptcha/types": "^1.0.3", "@hcaptcha/types": "^1.0.3",
"@jackfranklin/rollup-plugin-markdown": "^0.4.0", "@jackfranklin/rollup-plugin-markdown": "^0.4.0",
"@lingui/cli": "^3.16.0", "@lingui/cli": "^3.16.1",
"@lingui/core": "^3.16.0", "@lingui/core": "^3.16.0",
"@lingui/detect-locale": "^3.16.0", "@lingui/detect-locale": "^3.16.1",
"@lingui/macro": "^3.16.0", "@lingui/macro": "^3.16.1",
"@patternfly/patternfly": "^4.222.4", "@patternfly/patternfly": "^4.222.4",
"@polymer/iron-form": "^3.0.1", "@polymer/iron-form": "^3.0.1",
"@polymer/paper-input": "^3.2.1", "@polymer/paper-input": "^3.2.1",
@ -36,15 +36,15 @@
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-typescript": "^11.0.0", "@rollup/plugin-typescript": "^11.0.0",
"@sentry/browser": "^7.31.1", "@sentry/browser": "^7.33.0",
"@sentry/tracing": "^7.31.1", "@sentry/tracing": "^7.33.0",
"@squoosh/cli": "^0.7.3", "@squoosh/cli": "^0.7.3",
"@trivago/prettier-plugin-sort-imports": "^4.0.0", "@trivago/prettier-plugin-sort-imports": "^4.0.0",
"@types/chart.js": "^2.9.37", "@types/chart.js": "^2.9.37",
"@types/codemirror": "5.60.6", "@types/codemirror": "5.60.7",
"@types/grecaptcha": "^3.0.4", "@types/grecaptcha": "^3.0.4",
"@typescript-eslint/eslint-plugin": "^5.48.2", "@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.2", "@typescript-eslint/parser": "^5.49.0",
"@webcomponents/webcomponentsjs": "^2.7.0", "@webcomponents/webcomponentsjs": "^2.7.0",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
@ -64,7 +64,7 @@
"mermaid": "^9.3.0", "mermaid": "^9.3.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"prettier": "^2.8.3", "prettier": "^2.8.3",
"pyright": "^1.1.290", "pyright": "^1.1.291",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"rollup": "^2.79.1", "rollup": "^2.79.1",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
@ -1961,9 +1961,9 @@
} }
}, },
"node_modules/@goauthentik/api": { "node_modules/@goauthentik/api": {
"version": "2023.1.0-1674058489", "version": "2023.1.2-1674559422",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.1.0-1674058489.tgz", "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.1.2-1674559422.tgz",
"integrity": "sha512-k100Z1Tx4o7EsIq+tezavHSSddpr51NlGJZyR9QHsaQq6K+BpxZVcozR7xau7zK615JAwljWeG4yfEq9to++rw==" "integrity": "sha512-zkbsIGy9J5QvtL70JH/svY0vGvbJhfYkhBxlt61dxCI3qDaof2RjJvm913OP6N/h8JoSzbyj8uZGJs4y8pHLhw=="
}, },
"node_modules/@hcaptcha/types": { "node_modules/@hcaptcha/types": {
"version": "1.0.3", "version": "1.0.3",
@ -2215,13 +2215,13 @@
} }
}, },
"node_modules/@lingui/babel-plugin-extract-messages": { "node_modules/@lingui/babel-plugin-extract-messages": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.16.1.tgz",
"integrity": "sha512-Wxfp9cJfVjwhVigufIssoDntaTm4a8XXkmsV9gTMCr2xJL6ZowoC80TltBLOu0Dt829e6pdy+TFuwuKwAvnmrA==", "integrity": "sha512-ipt2WDGzSs0zsZ3BysFkDzz/LjUxSx5xh6/ScbXqcYDZc/b0jL87cj9S9csZdsoXLWWfeaPHvIgD5yTSthkM6w==",
"dependencies": { "dependencies": {
"@babel/generator": "^7.11.6", "@babel/generator": "^7.11.6",
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@lingui/conf": "3.16.0", "@lingui/conf": "3.16.1",
"mkdirp": "^1.0.4" "mkdirp": "^1.0.4"
}, },
"engines": { "engines": {
@ -2229,18 +2229,18 @@
} }
}, },
"node_modules/@lingui/cli": { "node_modules/@lingui/cli": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.16.1.tgz",
"integrity": "sha512-6sUVpA6UB4BwNtLjC9aG62QvxkhHwVmDOi9vO8kb9W4SKPBaMdHuaxEsVVBuD1ZWcFZULcNUmPh9kKAnMRkHHw==", "integrity": "sha512-20IezHftBqJe8EYls+fwftni5f2dBWFe+d2J7a6DRKuvuSxBD2UforLPna9dq1Ya84MnZGQBhhY4zSXIXvO+fA==",
"dependencies": { "dependencies": {
"@babel/generator": "^7.11.6", "@babel/generator": "^7.11.6",
"@babel/parser": "^7.11.5", "@babel/parser": "^7.11.5",
"@babel/plugin-syntax-jsx": "^7.10.4", "@babel/plugin-syntax-jsx": "^7.10.4",
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@babel/types": "^7.11.5", "@babel/types": "^7.11.5",
"@lingui/babel-plugin-extract-messages": "3.16.0", "@lingui/babel-plugin-extract-messages": "3.16.1",
"@lingui/conf": "3.16.0", "@lingui/conf": "3.16.1",
"@lingui/core": "3.16.0", "@lingui/core": "3.16.1",
"babel-plugin-macros": "^3.0.1", "babel-plugin-macros": "^3.0.1",
"bcp-47": "^1.0.7", "bcp-47": "^1.0.7",
"chalk": "^4.1.0", "chalk": "^4.1.0",
@ -2342,9 +2342,9 @@
} }
}, },
"node_modules/@lingui/conf": { "node_modules/@lingui/conf": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.16.1.tgz",
"integrity": "sha512-65X3TySGzeYjVNE3YZDpF6LTYxpUiSRSyTJjEGB7ZSPpuL6VDMEUGCDsnur2ix8D+I8w0ife6ks4HPQt6owZmw==", "integrity": "sha512-F8aYpjFXItmJLm09BxDzUZTsK0bB7nxLpMkUmiawVuuyB+66mBanOQ1nQEn8+SdJHCqJo9sI1ZSg+1uFequGzg==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"chalk": "^4.1.0", "chalk": "^4.1.0",
@ -2422,9 +2422,9 @@
} }
}, },
"node_modules/@lingui/core": { "node_modules/@lingui/core": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.16.1.tgz",
"integrity": "sha512-2uZvxHv4IWF7xIRG1o4oXDFCrAhE0945Ses1eALmv/NqQ8BslXWWSq0Zf51qt+ZqQ3RfzCdl4kslZEqdGRe0gw==", "integrity": "sha512-F/oos3IquuA71eVUfVBFY4teq9QuE3enGV65AMb9VB0vUTxsy6HAOIo1A2aZ4wJ0ST8V7FhYshYRU6qXguRjMQ==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@messageformat/parser": "^5.0.0", "@messageformat/parser": "^5.0.0",
@ -2435,20 +2435,20 @@
} }
}, },
"node_modules/@lingui/detect-locale": { "node_modules/@lingui/detect-locale": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/detect-locale/-/detect-locale-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/detect-locale/-/detect-locale-3.16.1.tgz",
"integrity": "sha512-7FQy1ccPnVe0/GNAzTmEDrsPHZV1/nSsK6fRKyuOFUkiItc1bVDmlpVfM4UXerERx6Nez+N99Z6Vjc6rg3LARw==", "integrity": "sha512-zDm9grmwPjQ5FXoIpkt0Fvq2bH+d01lI7zTmNFdTcM4IK4vsgAYqSY2h94W+7A3vyHEeaIULm5XtLBePSLJLcQ==",
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@lingui/macro": { "node_modules/@lingui/macro": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.16.1.tgz",
"integrity": "sha512-3HfP1Bqr4i60P3LoQq/ukhDvel4a5oBSMPRuYBUpwqdnKOAbZuN5vnZmu3TrlXntWTYOvrwa71D8SYGmvevelw==", "integrity": "sha512-CmR6u37Wzb+P4FGJJtbjY3SSX6vQb1IOj3Wcv9DYKvi1nepMvtvc5hcgCQKmqTdDi29fx6re5lmfG8YnthLkwA==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@lingui/conf": "3.16.0", "@lingui/conf": "3.16.1",
"ramda": "^0.27.1" "ramda": "^0.27.1"
}, },
"engines": { "engines": {
@ -2974,14 +2974,14 @@
} }
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.33.0.tgz",
"integrity": "sha512-Rg9F61S1tz1Dv3iUyyGP26bxoi7WJAG2+f2fBbSmFuJ+JTH4Jvu2/F1bBig8Dz01ejzVhbNSUUCfoDhSvksIsQ==", "integrity": "sha512-bvExBTyLb7cLWLkHh0gch2W/oSw08Yo8DgEc+KkikOnvWd/xoEWUsYNydYGzV+bL1jqcOErsZy0fVsbzTmh71g==",
"dependencies": { "dependencies": {
"@sentry/core": "7.31.1", "@sentry/core": "7.33.0",
"@sentry/replay": "7.31.1", "@sentry/replay": "7.33.0",
"@sentry/types": "7.31.1", "@sentry/types": "7.33.0",
"@sentry/utils": "7.31.1", "@sentry/utils": "7.33.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2994,12 +2994,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.33.0.tgz",
"integrity": "sha512-quaNU6z8jabmatBTDi28Wpff2yzfWIp/IU4bbi2QOtEiCNT+TQJXqlRTRMu9xLrX7YzyKCL5X2gbit/85lyWUg==", "integrity": "sha512-mrSgUnXjxHVi0cVea1lv7gC/Y66ya2a3atCHaPEij/+l+3APg5d0Ixt1zMx5YllMiZKf6wpxlZ0uwXcqdAAw+w==",
"dependencies": { "dependencies": {
"@sentry/types": "7.31.1", "@sentry/types": "7.33.0",
"@sentry/utils": "7.31.1", "@sentry/utils": "7.33.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -3012,26 +3012,26 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/replay": { "node_modules/@sentry/replay": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.33.0.tgz",
"integrity": "sha512-sLArvwZn6IwA/bASctyhxN7LhdCXJvMmyTynRfmk7pzuNzBMc5CNlHeIsDpHrfQuH53IKicvl6cHnHyclu5DSA==", "integrity": "sha512-m6xpSdjsNCCGxAkk5ikPFv/sQAfWtieMEXLdeDZE9jnroVozweHpsUhZYhqzTpxVC5SA3jPyTQ6Ods5gRvTBfA==",
"dependencies": { "dependencies": {
"@sentry/core": "7.31.1", "@sentry/core": "7.33.0",
"@sentry/types": "7.31.1", "@sentry/types": "7.33.0",
"@sentry/utils": "7.31.1" "@sentry/utils": "7.33.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@sentry/tracing": { "node_modules/@sentry/tracing": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.33.0.tgz",
"integrity": "sha512-kW6vNwddp2Ycq2JfTzveUEIRF9YQwvl7L6BBoOZt9oVnYlsPipEeyU2Q277LatHldr8hDo2tbz/vz2BQjO5GSw==", "integrity": "sha512-MtcKyW/QJgXGrHf5+205xnIIl7yIT99MzuTkuKzQwmnmy/siD3U0X8RoCaGLzj6kkSIu4m7vyQZoyd3J+5D8lw==",
"dependencies": { "dependencies": {
"@sentry/core": "7.31.1", "@sentry/core": "7.33.0",
"@sentry/types": "7.31.1", "@sentry/types": "7.33.0",
"@sentry/utils": "7.31.1", "@sentry/utils": "7.33.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -3044,19 +3044,19 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/types": { "node_modules/@sentry/types": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.33.0.tgz",
"integrity": "sha512-1uzr2l0AxEnxUX/S0EdmXUQ15/kDsam8Nbdw4Gai8SU764XwQgA/TTjoewVP597CDI/AHKan67Y630/Ylmkx9w==", "integrity": "sha512-5kkmYjtBWSbPxfYGiXdZFPS6xpFBNlXvDqeX4NpCFXz6/LiEDn6tZ61kuCSFb8MZlyqyCX5WsP3aiI2FJfpGIA==",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/utils": { "node_modules/@sentry/utils": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.33.0.tgz",
"integrity": "sha512-ZsIPq29aNdP9q3R7qIzJhZ9WW+4DzE9g5SfGwx3UjTIxoRRBfdUJUbf7S+LKEdvCkKbyoDt6FLt5MiSJV43xBA==", "integrity": "sha512-msp02GV1gOfaN5FjKjWxI00rtbYLXEE5cTGldhs/Dt9KI63dDk1nwPDkSLhg6joqRItAq0thlBh6un717HdWbg==",
"dependencies": { "dependencies": {
"@sentry/types": "7.31.1", "@sentry/types": "7.33.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -3256,9 +3256,9 @@
} }
}, },
"node_modules/@types/codemirror": { "node_modules/@types/codemirror": {
"version": "5.60.6", "version": "5.60.7",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.6.tgz", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.7.tgz",
"integrity": "sha512-JIDPSvkYRlcv/2F0erqD+de2ni/Mz6FJMEGb0vwF6ByQOcHIKfiEfwrO4d6dSRwYeHyNUMpGjev0PyjX2M0XWw==", "integrity": "sha512-QXIC+RPzt/1BGSuD6iFn6UMC9TDp+9hkOANYNPVsjjrDdzKphfRkwQDKGp2YaC54Yhz0g6P5uYTCCibZZEiMAA==",
"dependencies": { "dependencies": {
"@types/tern": "*" "@types/tern": "*"
} }
@ -3404,13 +3404,13 @@
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
"integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==", "integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.48.2", "@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/type-utils": "5.48.2", "@typescript-eslint/type-utils": "5.49.0",
"@typescript-eslint/utils": "5.48.2", "@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0", "natural-compare-lite": "^1.4.0",
@ -3450,13 +3450,13 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.49.0.tgz",
"integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==", "integrity": "sha512-veDlZN9mUhGqU31Qiv2qEp+XrJj5fgZpJ8PW30sHU+j/8/e5ruAhLaVDAeznS7A7i4ucb/s8IozpDtt9NqCkZg==",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.48.2", "@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.48.2", "@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.48.2", "@typescript-eslint/typescript-estree": "5.49.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -3476,12 +3476,12 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
"integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==", "integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "5.48.2", "@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.48.2" "@typescript-eslint/visitor-keys": "5.49.0"
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -3492,12 +3492,12 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
"integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==", "integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "5.48.2", "@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/utils": "5.48.2", "@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
}, },
@ -3518,9 +3518,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
"integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==", "integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==",
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}, },
@ -3530,12 +3530,12 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
"integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==", "integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "5.48.2", "@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.48.2", "@typescript-eslint/visitor-keys": "5.49.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -3570,15 +3570,15 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
"integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==", "integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12", "@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.48.2", "@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.48.2", "@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.48.2", "@typescript-eslint/typescript-estree": "5.49.0",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"semver": "^7.3.7" "semver": "^7.3.7"
@ -3609,11 +3609,11 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
"integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==", "integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "5.48.2", "@typescript-eslint/types": "5.49.0",
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
}, },
"engines": { "engines": {
@ -8170,9 +8170,9 @@
} }
}, },
"node_modules/pyright": { "node_modules/pyright": {
"version": "1.1.290", "version": "1.1.291",
"resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.290.tgz", "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.291.tgz",
"integrity": "sha512-iHT8G2+R/GUfYWxxd4ud5Lj/0H0bE1eWVR0avKKgPtkBeLkUz3sorjKpfuaJdsuJNrunzMtZsmYPD366t39klg==", "integrity": "sha512-wkSlCEhF1OwtJqHuifwxk76UMr1NPqsAbPIlCh7tiZx8VUdZs17NCGNorhyb2M+GFEvp8pod9Xyu1LXN1JL2kQ==",
"bin": { "bin": {
"pyright": "index.js", "pyright": "index.js",
"pyright-langserver": "langserver.index.js" "pyright-langserver": "langserver.index.js"
@ -11437,9 +11437,9 @@
"integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A==" "integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A=="
}, },
"@goauthentik/api": { "@goauthentik/api": {
"version": "2023.1.0-1674058489", "version": "2023.1.2-1674559422",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.1.0-1674058489.tgz", "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.1.2-1674559422.tgz",
"integrity": "sha512-k100Z1Tx4o7EsIq+tezavHSSddpr51NlGJZyR9QHsaQq6K+BpxZVcozR7xau7zK615JAwljWeG4yfEq9to++rw==" "integrity": "sha512-zkbsIGy9J5QvtL70JH/svY0vGvbJhfYkhBxlt61dxCI3qDaof2RjJvm913OP6N/h8JoSzbyj8uZGJs4y8pHLhw=="
}, },
"@hcaptcha/types": { "@hcaptcha/types": {
"version": "1.0.3", "version": "1.0.3",
@ -11650,29 +11650,29 @@
} }
}, },
"@lingui/babel-plugin-extract-messages": { "@lingui/babel-plugin-extract-messages": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.16.1.tgz",
"integrity": "sha512-Wxfp9cJfVjwhVigufIssoDntaTm4a8XXkmsV9gTMCr2xJL6ZowoC80TltBLOu0Dt829e6pdy+TFuwuKwAvnmrA==", "integrity": "sha512-ipt2WDGzSs0zsZ3BysFkDzz/LjUxSx5xh6/ScbXqcYDZc/b0jL87cj9S9csZdsoXLWWfeaPHvIgD5yTSthkM6w==",
"requires": { "requires": {
"@babel/generator": "^7.11.6", "@babel/generator": "^7.11.6",
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@lingui/conf": "3.16.0", "@lingui/conf": "3.16.1",
"mkdirp": "^1.0.4" "mkdirp": "^1.0.4"
} }
}, },
"@lingui/cli": { "@lingui/cli": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.16.1.tgz",
"integrity": "sha512-6sUVpA6UB4BwNtLjC9aG62QvxkhHwVmDOi9vO8kb9W4SKPBaMdHuaxEsVVBuD1ZWcFZULcNUmPh9kKAnMRkHHw==", "integrity": "sha512-20IezHftBqJe8EYls+fwftni5f2dBWFe+d2J7a6DRKuvuSxBD2UforLPna9dq1Ya84MnZGQBhhY4zSXIXvO+fA==",
"requires": { "requires": {
"@babel/generator": "^7.11.6", "@babel/generator": "^7.11.6",
"@babel/parser": "^7.11.5", "@babel/parser": "^7.11.5",
"@babel/plugin-syntax-jsx": "^7.10.4", "@babel/plugin-syntax-jsx": "^7.10.4",
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@babel/types": "^7.11.5", "@babel/types": "^7.11.5",
"@lingui/babel-plugin-extract-messages": "3.16.0", "@lingui/babel-plugin-extract-messages": "3.16.1",
"@lingui/conf": "3.16.0", "@lingui/conf": "3.16.1",
"@lingui/core": "3.16.0", "@lingui/core": "3.16.1",
"babel-plugin-macros": "^3.0.1", "babel-plugin-macros": "^3.0.1",
"bcp-47": "^1.0.7", "bcp-47": "^1.0.7",
"chalk": "^4.1.0", "chalk": "^4.1.0",
@ -11744,9 +11744,9 @@
} }
}, },
"@lingui/conf": { "@lingui/conf": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.16.1.tgz",
"integrity": "sha512-65X3TySGzeYjVNE3YZDpF6LTYxpUiSRSyTJjEGB7ZSPpuL6VDMEUGCDsnur2ix8D+I8w0ife6ks4HPQt6owZmw==", "integrity": "sha512-F8aYpjFXItmJLm09BxDzUZTsK0bB7nxLpMkUmiawVuuyB+66mBanOQ1nQEn8+SdJHCqJo9sI1ZSg+1uFequGzg==",
"requires": { "requires": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"chalk": "^4.1.0", "chalk": "^4.1.0",
@ -11802,9 +11802,9 @@
} }
}, },
"@lingui/core": { "@lingui/core": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.16.1.tgz",
"integrity": "sha512-2uZvxHv4IWF7xIRG1o4oXDFCrAhE0945Ses1eALmv/NqQ8BslXWWSq0Zf51qt+ZqQ3RfzCdl4kslZEqdGRe0gw==", "integrity": "sha512-F/oos3IquuA71eVUfVBFY4teq9QuE3enGV65AMb9VB0vUTxsy6HAOIo1A2aZ4wJ0ST8V7FhYshYRU6qXguRjMQ==",
"requires": { "requires": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@messageformat/parser": "^5.0.0", "@messageformat/parser": "^5.0.0",
@ -11812,17 +11812,17 @@
} }
}, },
"@lingui/detect-locale": { "@lingui/detect-locale": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/detect-locale/-/detect-locale-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/detect-locale/-/detect-locale-3.16.1.tgz",
"integrity": "sha512-7FQy1ccPnVe0/GNAzTmEDrsPHZV1/nSsK6fRKyuOFUkiItc1bVDmlpVfM4UXerERx6Nez+N99Z6Vjc6rg3LARw==" "integrity": "sha512-zDm9grmwPjQ5FXoIpkt0Fvq2bH+d01lI7zTmNFdTcM4IK4vsgAYqSY2h94W+7A3vyHEeaIULm5XtLBePSLJLcQ=="
}, },
"@lingui/macro": { "@lingui/macro": {
"version": "3.16.0", "version": "3.16.1",
"resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.16.0.tgz", "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.16.1.tgz",
"integrity": "sha512-3HfP1Bqr4i60P3LoQq/ukhDvel4a5oBSMPRuYBUpwqdnKOAbZuN5vnZmu3TrlXntWTYOvrwa71D8SYGmvevelw==", "integrity": "sha512-CmR6u37Wzb+P4FGJJtbjY3SSX6vQb1IOj3Wcv9DYKvi1nepMvtvc5hcgCQKmqTdDi29fx6re5lmfG8YnthLkwA==",
"requires": { "requires": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@lingui/conf": "3.16.0", "@lingui/conf": "3.16.1",
"ramda": "^0.27.1" "ramda": "^0.27.1"
} }
}, },
@ -12194,14 +12194,14 @@
} }
}, },
"@sentry/browser": { "@sentry/browser": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.33.0.tgz",
"integrity": "sha512-Rg9F61S1tz1Dv3iUyyGP26bxoi7WJAG2+f2fBbSmFuJ+JTH4Jvu2/F1bBig8Dz01ejzVhbNSUUCfoDhSvksIsQ==", "integrity": "sha512-bvExBTyLb7cLWLkHh0gch2W/oSw08Yo8DgEc+KkikOnvWd/xoEWUsYNydYGzV+bL1jqcOErsZy0fVsbzTmh71g==",
"requires": { "requires": {
"@sentry/core": "7.31.1", "@sentry/core": "7.33.0",
"@sentry/replay": "7.31.1", "@sentry/replay": "7.33.0",
"@sentry/types": "7.31.1", "@sentry/types": "7.33.0",
"@sentry/utils": "7.31.1", "@sentry/utils": "7.33.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -12213,12 +12213,12 @@
} }
}, },
"@sentry/core": { "@sentry/core": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.33.0.tgz",
"integrity": "sha512-quaNU6z8jabmatBTDi28Wpff2yzfWIp/IU4bbi2QOtEiCNT+TQJXqlRTRMu9xLrX7YzyKCL5X2gbit/85lyWUg==", "integrity": "sha512-mrSgUnXjxHVi0cVea1lv7gC/Y66ya2a3atCHaPEij/+l+3APg5d0Ixt1zMx5YllMiZKf6wpxlZ0uwXcqdAAw+w==",
"requires": { "requires": {
"@sentry/types": "7.31.1", "@sentry/types": "7.33.0",
"@sentry/utils": "7.31.1", "@sentry/utils": "7.33.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -12230,23 +12230,23 @@
} }
}, },
"@sentry/replay": { "@sentry/replay": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.33.0.tgz",
"integrity": "sha512-sLArvwZn6IwA/bASctyhxN7LhdCXJvMmyTynRfmk7pzuNzBMc5CNlHeIsDpHrfQuH53IKicvl6cHnHyclu5DSA==", "integrity": "sha512-m6xpSdjsNCCGxAkk5ikPFv/sQAfWtieMEXLdeDZE9jnroVozweHpsUhZYhqzTpxVC5SA3jPyTQ6Ods5gRvTBfA==",
"requires": { "requires": {
"@sentry/core": "7.31.1", "@sentry/core": "7.33.0",
"@sentry/types": "7.31.1", "@sentry/types": "7.33.0",
"@sentry/utils": "7.31.1" "@sentry/utils": "7.33.0"
} }
}, },
"@sentry/tracing": { "@sentry/tracing": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.33.0.tgz",
"integrity": "sha512-kW6vNwddp2Ycq2JfTzveUEIRF9YQwvl7L6BBoOZt9oVnYlsPipEeyU2Q277LatHldr8hDo2tbz/vz2BQjO5GSw==", "integrity": "sha512-MtcKyW/QJgXGrHf5+205xnIIl7yIT99MzuTkuKzQwmnmy/siD3U0X8RoCaGLzj6kkSIu4m7vyQZoyd3J+5D8lw==",
"requires": { "requires": {
"@sentry/core": "7.31.1", "@sentry/core": "7.33.0",
"@sentry/types": "7.31.1", "@sentry/types": "7.33.0",
"@sentry/utils": "7.31.1", "@sentry/utils": "7.33.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -12258,16 +12258,16 @@
} }
}, },
"@sentry/types": { "@sentry/types": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.33.0.tgz",
"integrity": "sha512-1uzr2l0AxEnxUX/S0EdmXUQ15/kDsam8Nbdw4Gai8SU764XwQgA/TTjoewVP597CDI/AHKan67Y630/Ylmkx9w==" "integrity": "sha512-5kkmYjtBWSbPxfYGiXdZFPS6xpFBNlXvDqeX4NpCFXz6/LiEDn6tZ61kuCSFb8MZlyqyCX5WsP3aiI2FJfpGIA=="
}, },
"@sentry/utils": { "@sentry/utils": {
"version": "7.31.1", "version": "7.33.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.31.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.33.0.tgz",
"integrity": "sha512-ZsIPq29aNdP9q3R7qIzJhZ9WW+4DzE9g5SfGwx3UjTIxoRRBfdUJUbf7S+LKEdvCkKbyoDt6FLt5MiSJV43xBA==", "integrity": "sha512-msp02GV1gOfaN5FjKjWxI00rtbYLXEE5cTGldhs/Dt9KI63dDk1nwPDkSLhg6joqRItAq0thlBh6un717HdWbg==",
"requires": { "requires": {
"@sentry/types": "7.31.1", "@sentry/types": "7.33.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -12430,9 +12430,9 @@
} }
}, },
"@types/codemirror": { "@types/codemirror": {
"version": "5.60.6", "version": "5.60.7",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.6.tgz", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.7.tgz",
"integrity": "sha512-JIDPSvkYRlcv/2F0erqD+de2ni/Mz6FJMEGb0vwF6ByQOcHIKfiEfwrO4d6dSRwYeHyNUMpGjev0PyjX2M0XWw==", "integrity": "sha512-QXIC+RPzt/1BGSuD6iFn6UMC9TDp+9hkOANYNPVsjjrDdzKphfRkwQDKGp2YaC54Yhz0g6P5uYTCCibZZEiMAA==",
"requires": { "requires": {
"@types/tern": "*" "@types/tern": "*"
} }
@ -12577,13 +12577,13 @@
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
"integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==", "integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
"requires": { "requires": {
"@typescript-eslint/scope-manager": "5.48.2", "@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/type-utils": "5.48.2", "@typescript-eslint/type-utils": "5.49.0",
"@typescript-eslint/utils": "5.48.2", "@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0", "natural-compare-lite": "^1.4.0",
@ -12603,48 +12603,48 @@
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.49.0.tgz",
"integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==", "integrity": "sha512-veDlZN9mUhGqU31Qiv2qEp+XrJj5fgZpJ8PW30sHU+j/8/e5ruAhLaVDAeznS7A7i4ucb/s8IozpDtt9NqCkZg==",
"requires": { "requires": {
"@typescript-eslint/scope-manager": "5.48.2", "@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.48.2", "@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.48.2", "@typescript-eslint/typescript-estree": "5.49.0",
"debug": "^4.3.4" "debug": "^4.3.4"
} }
}, },
"@typescript-eslint/scope-manager": { "@typescript-eslint/scope-manager": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
"integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==", "integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
"requires": { "requires": {
"@typescript-eslint/types": "5.48.2", "@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.48.2" "@typescript-eslint/visitor-keys": "5.49.0"
} }
}, },
"@typescript-eslint/type-utils": { "@typescript-eslint/type-utils": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
"integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==", "integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
"requires": { "requires": {
"@typescript-eslint/typescript-estree": "5.48.2", "@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/utils": "5.48.2", "@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
} }
}, },
"@typescript-eslint/types": { "@typescript-eslint/types": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
"integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==" "integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg=="
}, },
"@typescript-eslint/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
"integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==", "integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
"requires": { "requires": {
"@typescript-eslint/types": "5.48.2", "@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.48.2", "@typescript-eslint/visitor-keys": "5.49.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -12663,15 +12663,15 @@
} }
}, },
"@typescript-eslint/utils": { "@typescript-eslint/utils": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
"integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==", "integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
"requires": { "requires": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12", "@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.48.2", "@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.48.2", "@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.48.2", "@typescript-eslint/typescript-estree": "5.49.0",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"semver": "^7.3.7" "semver": "^7.3.7"
@ -12688,11 +12688,11 @@
} }
}, },
"@typescript-eslint/visitor-keys": { "@typescript-eslint/visitor-keys": {
"version": "5.48.2", "version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
"integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==", "integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
"requires": { "requires": {
"@typescript-eslint/types": "5.48.2", "@typescript-eslint/types": "5.49.0",
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
}, },
"dependencies": { "dependencies": {
@ -16055,9 +16055,9 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
}, },
"pyright": { "pyright": {
"version": "1.1.290", "version": "1.1.291",
"resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.290.tgz", "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.291.tgz",
"integrity": "sha512-iHT8G2+R/GUfYWxxd4ud5Lj/0H0bE1eWVR0avKKgPtkBeLkUz3sorjKpfuaJdsuJNrunzMtZsmYPD366t39klg==" "integrity": "sha512-wkSlCEhF1OwtJqHuifwxk76UMr1NPqsAbPIlCh7tiZx8VUdZs17NCGNorhyb2M+GFEvp8pod9Xyu1LXN1JL2kQ=="
}, },
"qrjs": { "qrjs": {
"version": "0.1.2", "version": "0.1.2",

View File

@ -65,13 +65,13 @@
"@codemirror/legacy-modes": "^6.3.1", "@codemirror/legacy-modes": "^6.3.1",
"@formatjs/intl-listformat": "^7.1.7", "@formatjs/intl-listformat": "^7.1.7",
"@fortawesome/fontawesome-free": "^6.2.1", "@fortawesome/fontawesome-free": "^6.2.1",
"@goauthentik/api": "^2023.1.0-1674058489", "@goauthentik/api": "^2023.1.2-1674559422",
"@hcaptcha/types": "^1.0.3", "@hcaptcha/types": "^1.0.3",
"@jackfranklin/rollup-plugin-markdown": "^0.4.0", "@jackfranklin/rollup-plugin-markdown": "^0.4.0",
"@lingui/cli": "^3.16.0", "@lingui/cli": "^3.16.1",
"@lingui/core": "^3.16.0", "@lingui/core": "^3.16.0",
"@lingui/detect-locale": "^3.16.0", "@lingui/detect-locale": "^3.16.1",
"@lingui/macro": "^3.16.0", "@lingui/macro": "^3.16.1",
"@patternfly/patternfly": "^4.222.4", "@patternfly/patternfly": "^4.222.4",
"@polymer/iron-form": "^3.0.1", "@polymer/iron-form": "^3.0.1",
"@polymer/paper-input": "^3.2.1", "@polymer/paper-input": "^3.2.1",
@ -80,15 +80,15 @@
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-typescript": "^11.0.0", "@rollup/plugin-typescript": "^11.0.0",
"@sentry/browser": "^7.31.1", "@sentry/browser": "^7.33.0",
"@sentry/tracing": "^7.31.1", "@sentry/tracing": "^7.33.0",
"@squoosh/cli": "^0.7.3", "@squoosh/cli": "^0.7.3",
"@trivago/prettier-plugin-sort-imports": "^4.0.0", "@trivago/prettier-plugin-sort-imports": "^4.0.0",
"@types/chart.js": "^2.9.37", "@types/chart.js": "^2.9.37",
"@types/codemirror": "5.60.6", "@types/codemirror": "5.60.7",
"@types/grecaptcha": "^3.0.4", "@types/grecaptcha": "^3.0.4",
"@typescript-eslint/eslint-plugin": "^5.48.2", "@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.2", "@typescript-eslint/parser": "^5.49.0",
"@webcomponents/webcomponentsjs": "^2.7.0", "@webcomponents/webcomponentsjs": "^2.7.0",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
@ -108,7 +108,7 @@
"mermaid": "^9.3.0", "mermaid": "^9.3.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"prettier": "^2.8.3", "prettier": "^2.8.3",
"pyright": "^1.1.290", "pyright": "^1.1.291",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"rollup": "^2.79.1", "rollup": "^2.79.1",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",

View File

@ -13,9 +13,11 @@ import { TablePage } from "@goauthentik/elements/table/TablePage";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import { BlueprintInstance, BlueprintInstanceStatusEnum, ManagedApi } from "@goauthentik/api"; import { BlueprintInstance, BlueprintInstanceStatusEnum, ManagedApi } from "@goauthentik/api";
export function BlueprintStatus(blueprint?: BlueprintInstance): string { export function BlueprintStatus(blueprint?: BlueprintInstance): string {
@ -32,6 +34,7 @@ export function BlueprintStatus(blueprint?: BlueprintInstance): string {
} }
return t`Unknown`; return t`Unknown`;
} }
@customElement("ak-blueprint-list") @customElement("ak-blueprint-list")
export class BlueprintListPage extends TablePage<BlueprintInstance> { export class BlueprintListPage extends TablePage<BlueprintInstance> {
searchEnabled(): boolean { searchEnabled(): boolean {
@ -47,11 +50,16 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
return "pf-icon pf-icon-blueprint"; return "pf-icon pf-icon-blueprint";
} }
expandable = true;
checkbox = true; checkbox = true;
@property() @property()
order = "name"; order = "name";
static get styles(): CSSResult[] {
return super.styles.concat(PFDescriptionList);
}
async apiEndpoint(page: number): Promise<PaginatedResponse<BlueprintInstance>> { async apiEndpoint(page: number): Promise<PaginatedResponse<BlueprintInstance>> {
return new ManagedApi(DEFAULT_CONFIG).managedBlueprintsList({ return new ManagedApi(DEFAULT_CONFIG).managedBlueprintsList({
ordering: this.order, ordering: this.order,
@ -96,9 +104,34 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
</ak-forms-delete-bulk>`; </ak-forms-delete-bulk>`;
} }
renderExpanded(item: BlueprintInstance): TemplateResult {
return html`<td role="cell" colspan="4">
<div class="pf-c-table__expandable-row-content">
<dl class="pf-c-description-list pf-m-horizontal">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Path`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<pre>${item.path}</pre>
</div>
</dd>
</div>
</dl>
</div>
</td>`;
}
row(item: BlueprintInstance): TemplateResult[] { row(item: BlueprintInstance): TemplateResult[] {
let description = undefined;
const descKey = "blueprints.goauthentik.io/description";
if (Object.hasOwn(item.metadata.labels, descKey)) {
description = item.metadata.labels[descKey];
}
return [ return [
html`${item.name}`, html`<div>${item.name}</div>
${description ? html`<small>${description}</small>` : html``}`,
html`${BlueprintStatus(item)}`, html`${BlueprintStatus(item)}`,
html`${item.lastApplied.toLocaleString()}`, html`${item.lastApplied.toLocaleString()}`,
html`<ak-label color=${item.enabled ? PFColor.Green : PFColor.Red}> html`<ak-label color=${item.enabled ? PFColor.Green : PFColor.Red}>

View File

@ -132,6 +132,17 @@ export class PromptForm extends ModelForm<Prompt, string> {
renderForm(): TemplateResult { renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal"> return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Unique name of this field, used for selecting fields in prompt stages.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Field Key`} ?required=${true} name="fieldKey"> <ak-form-element-horizontal label=${t`Field Key`} ?required=${true} name="fieldKey">
<input <input
type="text" type="text"

View File

@ -1,7 +1,6 @@
import "@goauthentik/admin/stages/prompt/PromptForm"; import "@goauthentik/admin/stages/prompt/PromptForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config"; import { uiConfig } from "@goauthentik/common/ui/config";
import { truncate } from "@goauthentik/common/utils";
import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/ModalButton";
import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/DeleteBulkForm";
@ -35,7 +34,7 @@ export class PromptListPage extends TablePage<Prompt> {
checkbox = true; checkbox = true;
@property() @property()
order = "order"; order = "name";
async apiEndpoint(page: number): Promise<PaginatedResponse<Prompt>> { async apiEndpoint(page: number): Promise<PaginatedResponse<Prompt>> {
return new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsList({ return new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsList({
@ -48,8 +47,8 @@ export class PromptListPage extends TablePage<Prompt> {
columns(): TableColumn[] { columns(): TableColumn[] {
return [ return [
new TableColumn(t`Name`, "name"),
new TableColumn(t`Field`, "field_key"), new TableColumn(t`Field`, "field_key"),
new TableColumn(t`Label`, "label"),
new TableColumn(t`Type`, "type"), new TableColumn(t`Type`, "type"),
new TableColumn(t`Order`, "order"), new TableColumn(t`Order`, "order"),
new TableColumn(t`Stages`), new TableColumn(t`Stages`),
@ -81,8 +80,8 @@ export class PromptListPage extends TablePage<Prompt> {
row(item: Prompt): TemplateResult[] { row(item: Prompt): TemplateResult[] {
return [ return [
html`${item.fieldKey}`, html`${item.name}`,
html`${truncate(item.label, 20)}`, html`<code>${item.fieldKey}</code>`,
html`${item.type}`, html`${item.type}`,
html`${item.order}`, html`${item.order}`,
html`${item.promptstageSet?.map((stage) => { html`${item.promptstageSet?.map((stage) => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -27,7 +27,7 @@ import "@goauthentik/flow/stages/password/PasswordStage";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, css, html, render } from "lit"; import { CSSResult, TemplateResult, css, html, render } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { until } from "lit/directives/until.js"; import { until } from "lit/directives/until.js";
@ -44,6 +44,7 @@ import {
CapabilitiesEnum, CapabilitiesEnum,
ChallengeChoices, ChallengeChoices,
ChallengeTypes, ChallengeTypes,
ContextualFlowInfo,
CurrentTenant, CurrentTenant,
FlowChallengeResponseRequest, FlowChallengeResponseRequest,
FlowErrorChallenge, FlowErrorChallenge,
@ -95,9 +96,28 @@ export class FlowExecutor extends AKElement implements StageHost {
@property({ attribute: false }) @property({ attribute: false })
tenant!: CurrentTenant; tenant!: CurrentTenant;
@property({ attribute: false }) @state()
inspectorOpen: boolean; inspectorOpen: boolean;
_flowInfo?: ContextualFlowInfo;
@state()
set flowInfo(value: ContextualFlowInfo | undefined) {
this._flowInfo = value;
if (!value) {
return;
}
this.shadowRoot
?.querySelectorAll<HTMLDivElement>(".pf-c-background-image")
.forEach((bg) => {
bg.style.setProperty("--ak-flow-background", `url('${value?.background}')`);
});
}
get flowInfo(): ContextualFlowInfo | undefined {
return this._flowInfo;
}
ws: WebsocketClient; ws: WebsocketClient;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
@ -168,14 +188,6 @@ export class FlowExecutor extends AKElement implements StageHost {
tenant().then((tenant) => (this.tenant = tenant)); tenant().then((tenant) => (this.tenant = tenant));
} }
setBackground(url: string): void {
this.shadowRoot
?.querySelectorAll<HTMLDivElement>(".pf-c-background-image")
.forEach((bg) => {
bg.style.setProperty("--ak-flow-background", `url('${url}')`);
});
}
submit(payload?: FlowChallengeResponseRequest): Promise<boolean> { submit(payload?: FlowChallengeResponseRequest): Promise<boolean> {
if (!payload) return Promise.reject(); if (!payload) return Promise.reject();
if (!this.challenge) return Promise.reject(); if (!this.challenge) return Promise.reject();
@ -198,6 +210,9 @@ export class FlowExecutor extends AKElement implements StageHost {
); );
} }
this.challenge = data; this.challenge = data;
if (this.challenge.flowInfo) {
this.flowInfo = this.challenge.flowInfo;
}
if (this.challenge.responseErrors) { if (this.challenge.responseErrors) {
return false; return false;
} }
@ -231,9 +246,8 @@ export class FlowExecutor extends AKElement implements StageHost {
); );
} }
this.challenge = challenge; this.challenge = challenge;
// Only set background on first update, flow won't change throughout execution if (this.challenge.flowInfo) {
if (this.challenge?.flowInfo?.background) { this.flowInfo = this.challenge.flowInfo;
this.setBackground(this.challenge.flowInfo.background);
} }
}) })
.catch((e: Error | ResponseError) => { .catch((e: Error | ResponseError) => {
@ -531,13 +545,11 @@ export class FlowExecutor extends AKElement implements StageHost {
>${t`Powered by authentik`}</a >${t`Powered by authentik`}</a
> >
</li> </li>
${this.challenge?.flowInfo?.background?.startsWith( ${this.flowInfo?.background?.startsWith("/static")
"/static",
)
? html` ? html`
<li> <li>
<a <a
href="https://unsplash.com/@chrishenryphoto" href="https://unsplash.com/@saishmenon"
>${t`Background image`}</a >${t`Background image`}</a
> >
</li> </li>

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View File

@ -0,0 +1,151 @@
---
title: "SaaS should not be the default: Why data breaches signal return to self-hosting"
description: More companies are realizing that SaaS isnt, and shouldnt be, the default.
slug: 2023-01-24-saas-should-not-be-the-default
authors:
- name: Nick Moore
title: Freelance writer
url: https://nickmoore.me/
image_url: https://nickmoore.me/assets/images/image01.jpg
tags: [sso, self-hosting]
hide_table_of_contents: false
---
“We made a mistake” so said authentication provider [Okta](https://support.okta.com/help/s/article/Frequently-Asked-Questions-Regarding-January-2022-Compromise?language=en_US) on March 25, 2022 two months after an attack on one of Oktas vendors (Sitel, a contact center) in January. During Oktas initial investigation, the company didnt warn its customers about the attack nor about its potential damage.
“At that time,” Okta admitted later, “We didnt recognize that there was a risk to Okta and our customers.”
On March 22, three days before the admission, the group responsible for the attack LAPSUS$ shared screenshots online that evidenced the success of their attack. As users, customers, and onlookers reacted, Okta co-founder and CEO Todd McKinnon [tweeted about the attack](https://twitter.com/toddmckinnon/status/1506184721922859010?s=20&t=o7e6RA25El2IEd7EMQD3Xg), claiming that the attack was “investigated and contained” but, more controversially, framing the attack as “an attempt.”
<!--truncate-->
Many disagreed with that framing considering, as the news progressed, that the attack had succeeded and had affected 2.5% of Okta customers ([about 375 companies](https://www.bleepingcomputer.com/news/security/okta-confirms-25-percent-customers-impacted-by-hack-in-january/)). Worse, LAPSUS$ itself disagreed, claiming they had “logged in to a superuser portal with the ability to reset the Password and MFA of ~95% of clients.”
Data breaches are not uncommon but in this case, the coverup became worse than the crime. In the days and weeks after, most criticism of Okta didnt focus on the attack itself but on the companys response. Okta had two months to talk about the attack before LAPSUS$ forced them to and its unclear whether Okta ever would have talked about it at all without the circulation of those screenshots.
Eventually, Okta admitted its faults. On March 23, David Bradbury, Chief Security Officer at Okta, [wrote that](https://www.okta.com/blog/2022/03/oktas-investigation-of-the-january-2022-compromise/): “I am greatly disappointed by the long period of time that transpired between our notification to Sitel and the issuance of the complete investigation report.”
The Okta case is one example in a line of many. Its a particularly galling case because Okta manages authentication for so many companies making it a frontline security product but the pattern itself is not rare.
A major consequence of the rise of SaaS software is a misalignment of incentives between SaaS vendors and customers. We dont have to put on tinfoil hats to realize that vendors have a strong incentive to ignore or even suppress bad news so as to safeguard their relationships with current and future customers.
As honest and as well-intentioned as a vendor might be, that incentive misalignment is still there. This tension exposes the leading edge of an emerging trend and potentially major shift: Companies are reconsidering the value of self-hosting their software so as to have greater control over security and cost.
### 5 incentives SaaS vendors have to be secretive about security
This is not a secret nor a conspiracy theory: SaaS vendors have a compelling array of incentives to hide security flaws in their services and suppress the publicity of successful data breaches.
The very model of delivering software as a service means that vendors are incentivized to maintain relationships with their customers so as to encourage them to maintain their subscriptions. That incentive leads to good things, such as prompt customer service and product iteration. But it can also lead to bad things, such as hiding mistakes and flaws.
Its hard, bordering on impossible, to claim that any given company suppressed news about a data breach. But we can infer its likely that it happens given three things:
- The SaaS industry is [massive and growing](https://www.grandviewresearch.com/industry-analysis/saas-market-report), meaning there are many companies out there that _could_ suffer a data breach and _could_ suppress news about it.
- The media industry is inherently limited and cant discover and report on every data breach.
- The number of data breaches has [consistently risen](https://www.statista.com/statistics/273550/data-breaches-recorded-in-the-united-states-by-number-of-breaches-and-records-exposed/) from 2005 to 2021.
Given these three dynamics, its likely some significant portion of vendors have tried, or at least hoped, for news about a data breach to not break headlines. Is it ethical? Likely not. But is it rewarding? If it all works out, yes. Lets look, then, at the five biggest incentives companies have to suppress data breach news.
#### 1. Fines
With the passing of the General Data Protection Regulation (GDPR) in Europe, along with a slew of other regulations, many of which are still emerging, fines have become a significant concern for companies.
GDPR fines are designed, in the [words of the EU](https://gdpr.eu/fines/), to “make non-compliance a costly mistake for both large and small businesses.”
The “less severe infringements” can cost companies up to €10 million (almost $11 million) or up to 2% of the companys annual revenue ”whichever amount is _higher_” [emphasis ours]. The “more serious infringements” can cost companies €20 million (about $21.5 million) or 4% of the companys annual revenue again, “whichever amount is higher.”
#### 2. Reputation
At first glance, the reputation cost of a data breach might seem minimal. Even headline-breaking data breaches dont always seem to impair companies.
You couldnt infer, for example, when the infamous Experian data breach occurred looking at its stock price alone.
![alt_text](./image2.png "image_tooltip")
(It happened in September of 2017 and a [class action lawsuit](https://www.ftc.gov/enforcement/refunds/equifax-data-breach-settlement) resulted in payments starting in December of 2022).
The problem with considering the potential of reputation damage is that its hard to predict. There are a few factors that make news coverage of a data breach more likely, such as whether a company targets average users or business users and whether a company stores obviously sensitive data or not, but predictability remains hard.
Your company neednt trend on Twitter to suffer reputation damage, however. According to [Impravata research](https://security.imprivata.com/rs/413-FZZ-310/images/IM_Report_Third-Party-Remote-Access-Security.pdf), 63% of companies dont do security evaluations on prospective vendors because they rely instead on the reputation of the vendors in question.
The incentive to suppress bad news and avoid a bad reputation also worsens with time. The same research shows that 55% of companies consider a “history of _frequent_ data breach incidents” [emphasis ours] to be a major indicator of risk. That means a company might be transparent about it first breach and gradually more secretive as it suffers more attacks.
#### 3. Legal issues
Beyond sheer fines, regional, national, and international governments can also levy lawsuits against companies and individuals. Joe Sullivan, for example, a former CTO at Uber, was convicted of [covering up a 2016 data breach](https://www.washingtonpost.com/technology/2022/10/05/uber-obstruction-sullivan-hacking/) in 2022.
Even if individuals arent jailed and the company itself survives a lawsuit just fine, the consequences can still be meaningful. The previously cited Imprivata research shows that 40% of companies consider legal actions against a vendor to be another risk factor.
#### 4. Professional reputation
Parallel to the previously mentioned risk of more general reputation damage is the risk of damage to a companys professional reputation. Even if a data breach doesnt make headlines, employees, investors, and partners in your industry might still take heed.
The risk here gets worse when you consider the implications of a data breach. Many people, perhaps not entirely fairly, might doubt whether a company runs a good operation if it suffers repeated data breaches. Consider a representative [Glassdoor review of Uber](http://www.glassdoor.com/Reviews/Employee-Review-Uber-RVW39883443.htm):
![alt_text](./image3.png "image_tooltip")
Companies can also start negative feedback loops wherein repeated security issues give them a reputation as having a bad security team, meaning good security engineers might avoid working for the company to avoid association with that reputation.
#### 5. Contract cancellation
Fines arent the only form of monetary loss. Many companies build security risks into their vendor contracts, making it easy to sever the relationship or recoup their losses after a breach.
The previously cited Imprivata research shows that 59% of companies demand contracts that obligate vendors to “adhere to security and privacy practices.” The same proportion of companies 59% dont do security evaluations because they rely on the consequences of the security agreements in the contract.
### Whats old is new again: Why self-hosted software is making a comeback
Depending on your age and experience in the industry, the prospect of self-hosted software returning can range from plausible to laughable. The instinct to doubt makes sense SaaS became the dominant model of software delivery for a variety of valid reasons.
When the SaaS model emerged, it was clear that, in general, SaaS products were easier to use and often more effective than their self-hosted counterparts. SaaS products, for example, are:
- Easy to purchase often requiring little more than an account and a credit card.
- Easy to run, install, and upgrade.
- Easy to maintain especially given companies can rely not only on the resources of the SaaS vendor but on the distributed infrastructure of the cloud vendor the SaaS vendor is using.
That said, there are also compelling reasons to use self-hosted products. For example, with self-hosted products, companies can:
- Know where their data is located.
- Customize the application to their unique workflows.
- Shift financing from opex to capex, which often results in net cost savings.
- Trust in shared alignment. If you own and self-host a product, youre incentivized, in a way even the best SaaS vendor isnt, to keep it secure.
Authentication, what we specialize in here at Authentik, is a great example. The industry standard used to be a self-hosted products most commonly Microsoft ADFS but they were it was notoriously unwieldy, which gave companies like Auth0 and Okta a chance to take over the market.
The technology industry is cyclical, however, and our bet is that by applying lessons learned from SaaS, vendors can offer self-hosted products that are as good or better than SaaS products. Customers can then have their cake and eat it too.
#### Technology is cyclical, not regressive or progressive
At first glance, the idea of companies shifting back to a self-hosted model seems silly didnt we learn our lessons the first time? Its easy to assume that the technology industry progresses in a linear, upward fashion and infer that anything from the past is necessarily worse.
And while that might be true for specific products (floppy discs arent coming back, Im afraid to say) business and technology models can and have returned from the dead.
Marianne Bellotti, author of the book _Kill It with Fire: Manage Aging Computer Systems (and Future Proof Modern Ones)_, raises the example of thick and thin clients. Decades ago, most companies ran applications on bulky mainframes but before the founding of AWS in 2006, companies had shifted toward running applications on personal computers. But as the cloud grew, the mainframe model returned with companies “time-sharing,” in Bellottis words, on public clouds in much the same way they did on mainframes.
“Technology doesnt advance in a straight line,” argues Bellotti, “because a straight line is not actually efficient.” Instead, she writes, “Technology advances not by building on what came before, but by pivoting from it.” And there are significant, growing, multiplying reasons to not only reconsider self-hosting but reconsider private data centers and on-premises infrastructure.
#### Early signs of an unwinding trend
Its hard to believe given years of discourse and “thought leadership” about the cloud but there are signs of change. And to be clear, the claim here is not that AWS will suddenly collapse and IT admins will need to rapidly re-learn server racking skills; the claim is that there are reasons to reconsider self-hosting and evidence that more and more companies will do that reconsideration.
Consider the recent decisions of three established companies: 37signals, Dropbox, and Retool.
On Twitter, 37signals and Basecamp co-founder DHH [summarized the results](https://twitter.com/dhh/status/1613508201953038337?s=20&t=QFmwWhR0YSCygvItPwtC8w) of a recent accounting 37signals did across its properties. 37signals spent, in total, $3,201,564.24 on cloud in 2022 and in a [subsequent tweet](https://twitter.com/dhh/status/1613558939760689153?s=20&t=QFmwWhR0YSCygvItPwtC8w), DHH compared that cost to purchasing “insanely powerful iron” from Dell that included “288 vCPU, 15 TB NVM, 1.3TB RAM for $1,287/month over 3 years.”
![alt_text](./image1.png "image_tooltip")
In a [full post](https://dev.37signals.com/our-cloud-spend-in-2022/) on the 37signals blog, senior site reliability engineer Fernando Álvarez provided more details, writing that “In 2023, we hope to dramatically cut that bill by moving a lot of services and dependencies out of the cloud and onto our own hardware.”
Years prior to this planned shift, in 2015, Dropbox decided to “[reverse migrate](https://www.datacenterknowledge.com/manage/dropbox-s-reverse-migration-cloud-own-data-centers-five-years)” from the cloud (AWS, in this case) to privately owned data centers. Before the end of the year, Dropbox relocated 90% of its customer data to an in-house network of data centers. At the time, the shift broke headlines because it seemed so unique.
Five years on, as Scott Fulton writes, Dropboxs decision “is starting to look more like a pioneer expedition.” Dropbox is able to save money and manage their resources more closely. Fulton argues theres no reason this choice “should only fit Dropbox.” Given the ability to effectively plan capacity, Fulton writes that many companies could also “avoid the breaking point of cloud-based service affordability.”
This trend also emerges on a customer-facing level. In 2021, low code platform Retool announced a [self-hosted plan](https://twitter.com/retool/status/1404835350250344449?s=20&t=VMzl65BkICVb3v2HxIXsEw), enabling customers to host Retool inside their infrastructure. Self-hosting, again, is not new, nor is the presence of customers requesting a self-hosted option. The difference here is that Retool, a relatively new company, founded in 2017 and growing fast, found reason to prioritize building a self-hosted option. Retool even [says that](https://twitter.com/retool/status/1404835454948495360?s=20&t=VMzl65BkICVb3v2HxIXsEw) “Self-hosting has been a top request from our self-serve customers.”
Retool cited a couple of [key use cases](https://twitter.com/retool/status/1404835751833989125?s=20&t=VMzl65BkICVb3v2HxIXsEw), including companies working within a regulated industry and companies hosting sensitive data. Retool also made it clear, though, that self-hosting is typically burdensome and offering this plan required the company to modernize the deployment process and make deployment easy by integrating Docker and Kubernetes.
### SaaS should not be the default
David Bradbury, Oktas Chief Security Officer, concludes his [post](https://www.okta.com/blog/2022/03/oktas-investigation-of-the-january-2022-compromise/) explaining the companys investigation of the LAPSUS$ incident and their response to it in a familiar way: “As with all security incidents, there are many opportunities for us to improve our processes and our communications. Im confident that we are moving in the right direction and this incident will only serve to strengthen our commitment to security.”
You dont have to impugn Oktas commitment or accuse them of suppressing news about this breach to see the problem. SaaS companies, due to the very structure of their business and delivery models, cant be as aligned with your companys needs as you are. SaaS companies will always, at best, be “moving in the right direction,” whereas your company, if it self-hosts its software, wont have to worry about misaligned incentives.
There might be a paradigm shift in how the technology industry hosts its workloads and delivers its software. There might not be. Either way, more companies are realizing that SaaS isnt, and shouldnt be, the default.

View File

@ -34,6 +34,8 @@ Any additional `.yaml` file in `/blueprints` will be discovered and automaticall
To disable existing blueprints, an empty file can be mounted over the existing blueprint. To disable existing blueprints, an empty file can be mounted over the existing blueprint.
File-based blueprints are automatically removed once they become unavailable, however none of the objects created by those blueprints afre affected by this.
## Storage - OCI ## Storage - OCI
Blueprints can also be stored in remote [OCI](https://opencontainers.org/) compliant registries. This includes GitHub Container Registry, Docker hub and many other registries. Blueprints can also be stored in remote [OCI](https://opencontainers.org/) compliant registries. This includes GitHub Container Registry, Docker hub and many other registries.

View File

@ -60,3 +60,7 @@ Used by authentik's packaged blueprints to keep globals up-to-date. Should only
#### `blueprints.goauthentik.io/instantiate`: #### `blueprints.goauthentik.io/instantiate`:
Configure if this blueprint should automatically be instantiated (defaults to `"true"`). When set to `"false"`, blueprints are listed and available to be instantiated via API/Browser. Configure if this blueprint should automatically be instantiated (defaults to `"true"`). When set to `"false"`, blueprints are listed and available to be instantiated via API/Browser.
#### `blueprints.goauthentik.io/description`:
Optionally set a description, which can be seen in the web interface.

View File

@ -13,6 +13,7 @@
"@docusaurus/preset-classic": "2.2.0", "@docusaurus/preset-classic": "2.2.0",
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"disqus-react": "^1.1.5",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"react": "^17.0.2", "react": "^17.0.2",
@ -5485,6 +5486,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/disqus-react": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/disqus-react/-/disqus-react-1.1.5.tgz",
"integrity": "sha512-9fdG5m6c3wJzlCDLaMheuUagMVj3s5qgUSXdekpCsvzYOKG21AiuOoqyDzA0oXrpPnYzgpnsvPYqZ+i0hJPGZw==",
"peerDependencies": {
"react": "^15.6.1 || ^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^15.6.1 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/dns-equal": { "node_modules/dns-equal": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@ -11512,9 +11522,9 @@
} }
}, },
"node_modules/ua-parser-js": { "node_modules/ua-parser-js": {
"version": "0.7.32", "version": "0.7.33",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
"integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -16549,6 +16559,12 @@
"path-type": "^4.0.0" "path-type": "^4.0.0"
} }
}, },
"disqus-react": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/disqus-react/-/disqus-react-1.1.5.tgz",
"integrity": "sha512-9fdG5m6c3wJzlCDLaMheuUagMVj3s5qgUSXdekpCsvzYOKG21AiuOoqyDzA0oXrpPnYzgpnsvPYqZ+i0hJPGZw==",
"requires": {}
},
"dns-equal": { "dns-equal": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@ -20873,9 +20889,9 @@
"peer": true "peer": true
}, },
"ua-parser-js": { "ua-parser-js": {
"version": "0.7.32", "version": "0.7.33",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
"integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==" "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw=="
}, },
"unherit": { "unherit": {
"version": "1.1.3", "version": "1.1.3",

View File

@ -19,6 +19,7 @@
"@docusaurus/preset-classic": "2.2.0", "@docusaurus/preset-classic": "2.2.0",
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"disqus-react": "^1.1.5",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"react": "^17.0.2", "react": "^17.0.2",

View File

@ -187,8 +187,8 @@ function Comparison() {
<td className="result passed"> <td className="result passed">
<Check></Check> <Check></Check>
</td> </td>
<td className="result failed"> <td className="result passed">
<X></X> <Check></Check>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -345,8 +345,8 @@ function Comparison() {
<td className="result passed"> <td className="result passed">
<Check></Check> <Check></Check>
</td> </td>
<td className="result failed"> <td className="result passed">
<X></X> <Check></Check>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@ -0,0 +1,76 @@
import React from "react";
import clsx from "clsx";
import {
HtmlClassNameProvider,
ThemeClassNames,
} from "@docusaurus/theme-common";
import {
BlogPostProvider,
useBlogPost,
} from "@docusaurus/theme-common/internal";
import BlogLayout from "@theme/BlogLayout";
import BlogPostItem from "@theme/BlogPostItem";
import BlogPostPaginator from "@theme/BlogPostPaginator";
import BlogPostPageMetadata from "@theme/BlogPostPage/Metadata";
import TOC from "@theme/TOC";
import { DiscussionEmbed } from "disqus-react";
function BlogPostPageContent({ sidebar, children }) {
const { metadata, toc } = useBlogPost();
const { nextItem, prevItem, frontMatter } = metadata;
const {
hide_table_of_contents: hideTableOfContents,
toc_min_heading_level: tocMinHeadingLevel,
toc_max_heading_level: tocMaxHeadingLevel,
} = frontMatter;
return (
<BlogLayout
sidebar={sidebar}
toc={
!hideTableOfContents && toc.length > 0 ? (
<TOC
toc={toc}
minHeadingLevel={tocMinHeadingLevel}
maxHeadingLevel={tocMaxHeadingLevel}
/>
) : undefined
}
>
<BlogPostItem>{children}</BlogPostItem>
{(nextItem || prevItem) && (
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
)}
</BlogLayout>
);
}
export default function BlogPostPage(props) {
const BlogPostContent = props.content;
const title = props.content.frontMatter.title.substring(0, 200);
const fmtId = title.replace(/^\//, "").replaceAll(/[\s\/]/gi, "-");
const disqusId = fmtId == "" ? "main" : fmtId;
return (
<BlogPostProvider content={props.content} isBlogPostPage>
<HtmlClassNameProvider
className={clsx(
ThemeClassNames.wrapper.blogPages,
ThemeClassNames.page.blogPostPage
)}
>
<BlogPostPageMetadata />
<BlogPostPageContent sidebar={props.sidebar}>
<BlogPostContent />
<DiscussionEmbed
shortname="goauthentik-io"
config={{
url: "https://goauthentik.io" + props.route.path,
identifier: disqusId,
title: title,
}}
/>
</BlogPostPageContent>
</HtmlClassNameProvider>
</BlogPostProvider>
);
}