Merge branch 'main' into dev
* main: web: clean up some repetitive types (#9241) core: fix logic for token expiration (#9426) ci: fix ci pipeline (#9427) translate: Updates for file locale/en/LC_MESSAGES/django.po in ru (#9424) web: Add resolved and integrity fields back to package-lock.json (#9419) translate: Updates for file locale/en/LC_MESSAGES/django.po in ru (#9407) stages/identification: don't check source component (#9410) core: bump selenium from 4.19.0 to 4.20.0 (#9411) core: bump black from 24.4.0 to 24.4.1 (#9412) ci: bump golangci/golangci-lint-action from 4 to 5 (#9413) core: bump goauthentik.io/api/v3 from 3.2024023.2 to 3.2024040.1 (#9414) web: bump @sentry/browser from 7.112.1 to 7.112.2 in /web in the sentry group (#9416) sources/oauth: ensure all UI sources return a valid source (#9401) web: markdown: display markdown even when frontmatter is missing (#9404)
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							| @ -29,7 +29,7 @@ jobs: | ||||
|       - name: Generate API | ||||
|         run: make gen-client-go | ||||
|       - name: golangci-lint | ||||
|         uses: golangci/golangci-lint-action@v4 | ||||
|         uses: golangci/golangci-lint-action@v5 | ||||
|         with: | ||||
|           version: v1.54.2 | ||||
|           args: --timeout 5000s --verbose | ||||
|  | ||||
							
								
								
									
										8
									
								
								.github/workflows/ci-web.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/ci-web.yml
									
									
									
									
										vendored
									
									
								
							| @ -34,6 +34,13 @@ jobs: | ||||
|       - name: Eslint | ||||
|         working-directory: ${{ matrix.project }}/ | ||||
|         run: npm run lint | ||||
|   lint-lockfile: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - working-directory: web/ | ||||
|         run: | | ||||
|           [ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ] | ||||
|   lint-build: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
| @ -95,6 +102,7 @@ jobs: | ||||
|         run: npm run lit-analyse | ||||
|   ci-web-mark: | ||||
|     needs: | ||||
|       - lint-lockfile | ||||
|       - lint-eslint | ||||
|       - lint-prettier | ||||
|       - lint-lit-analyse | ||||
|  | ||||
							
								
								
									
										8
									
								
								.github/workflows/ci-website.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/ci-website.yml
									
									
									
									
										vendored
									
									
								
							| @ -12,6 +12,13 @@ on: | ||||
|       - version-* | ||||
|  | ||||
| jobs: | ||||
|   lint-lockfile: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - working-directory: website/ | ||||
|         run: | | ||||
|           [ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ] | ||||
|   lint-prettier: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
| @ -62,6 +69,7 @@ jobs: | ||||
|         run: npm run ${{ matrix.job }} | ||||
|   ci-website-mark: | ||||
|     needs: | ||||
|       - lint-lockfile | ||||
|       - lint-prettier | ||||
|       - test | ||||
|       - build | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
|  | ||||
| from typing import Any | ||||
|  | ||||
| from django.utils.timezone import now | ||||
| from django_filters.rest_framework import DjangoFilterBackend | ||||
| from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer | ||||
| from guardian.shortcuts import assign_perm, get_anonymous_user | ||||
| @ -27,7 +28,6 @@ from authentik.core.models import ( | ||||
|     TokenIntents, | ||||
|     User, | ||||
|     default_token_duration, | ||||
|     token_expires_from_timedelta, | ||||
| ) | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.events.utils import model_to_dict | ||||
| @ -68,15 +68,17 @@ class TokenSerializer(ManagedSerializer, ModelSerializer): | ||||
|             max_token_lifetime_dt = default_token_duration() | ||||
|             if max_token_lifetime is not None: | ||||
|                 try: | ||||
|                     max_token_lifetime_dt = timedelta_from_string(max_token_lifetime) | ||||
|                     max_token_lifetime_dt = now() + timedelta_from_string(max_token_lifetime) | ||||
|                 except ValueError: | ||||
|                     max_token_lifetime_dt = default_token_duration() | ||||
|                     pass | ||||
|  | ||||
|             if "expires" in attrs and attrs.get("expires") > token_expires_from_timedelta( | ||||
|                 max_token_lifetime_dt | ||||
|             ): | ||||
|             if "expires" in attrs and attrs.get("expires") > max_token_lifetime_dt: | ||||
|                 raise ValidationError( | ||||
|                     {"expires": f"Token expires exceeds maximum lifetime ({max_token_lifetime})."} | ||||
|                     { | ||||
|                         "expires": ( | ||||
|                             f"Token expires exceeds maximum lifetime ({max_token_lifetime_dt} UTC)." | ||||
|                         ) | ||||
|                     } | ||||
|                 ) | ||||
|         elif attrs.get("intent") == TokenIntents.INTENT_API: | ||||
|             # For API tokens, expires cannot be overridden | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| """authentik core models""" | ||||
|  | ||||
| from datetime import datetime, timedelta | ||||
| from datetime import datetime | ||||
| from hashlib import sha256 | ||||
| from typing import Any, Optional, Self | ||||
| from uuid import uuid4 | ||||
| @ -68,11 +68,6 @@ def default_token_duration() -> datetime: | ||||
|     return now() + timedelta_from_string(token_duration) | ||||
|  | ||||
|  | ||||
| def token_expires_from_timedelta(dt: timedelta) -> datetime: | ||||
|     """Return a `datetime.datetime` object with the duration of the Token""" | ||||
|     return now() + dt | ||||
|  | ||||
|  | ||||
| def default_token_key() -> str: | ||||
|     """Default token key""" | ||||
|     current_tenant = get_current_tenant() | ||||
|  | ||||
							
								
								
									
										37
									
								
								authentik/sources/oauth/tests/test_type_apple.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								authentik/sources/oauth/tests/test_type_apple.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| """Apple Type tests""" | ||||
|  | ||||
| from django.test import RequestFactory, TestCase | ||||
| from guardian.shortcuts import get_anonymous_user | ||||
|  | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.lib.tests.utils import dummy_get_response | ||||
| from authentik.root.middleware import SessionMiddleware | ||||
| from authentik.sources.oauth.models import OAuthSource | ||||
| from authentik.sources.oauth.types.registry import registry | ||||
|  | ||||
|  | ||||
| class TestTypeApple(TestCase): | ||||
|     """OAuth Source tests""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.source = OAuthSource.objects.create( | ||||
|             name="test", | ||||
|             slug="test", | ||||
|             provider_type="apple", | ||||
|             authorization_url="", | ||||
|             profile_url="", | ||||
|             consumer_key=generate_id(), | ||||
|         ) | ||||
|         self.factory = RequestFactory() | ||||
|  | ||||
|     def test_login_challenge(self): | ||||
|         """Test login_challenge""" | ||||
|         request = self.factory.get("/") | ||||
|         request.user = get_anonymous_user() | ||||
|  | ||||
|         middleware = SessionMiddleware(dummy_get_response) | ||||
|         middleware.process_request(request) | ||||
|         request.session.save() | ||||
|         oauth_type = registry.find_type("apple") | ||||
|         challenge = oauth_type().login_challenge(self.source, request) | ||||
|         self.assertTrue(challenge.is_valid(raise_exception=True)) | ||||
| @ -125,7 +125,7 @@ class AppleType(SourceType): | ||||
|         ) | ||||
|         args = apple_client.get_redirect_args() | ||||
|         return AppleLoginChallenge( | ||||
|             instance={ | ||||
|             data={ | ||||
|                 "client_id": apple_client.get_client_id(), | ||||
|                 "scope": "name email", | ||||
|                 "redirect_uri": args["redirect_uri"], | ||||
|  | ||||
| @ -66,7 +66,7 @@ class PlexSource(Source): | ||||
|             icon = static("authentik/sources/plex.svg") | ||||
|         return UILoginButton( | ||||
|             challenge=PlexAuthenticationChallenge( | ||||
|                 { | ||||
|                 data={ | ||||
|                     "type": ChallengeTypes.NATIVE.value, | ||||
|                     "component": "ak-source-plex", | ||||
|                     "client_id": self.client_id, | ||||
|  | ||||
| @ -40,6 +40,11 @@ class TestPlexSource(TestCase): | ||||
|             slug="test", | ||||
|         ) | ||||
|  | ||||
|     def test_login_challenge(self): | ||||
|         """Test login_challenge""" | ||||
|         ui_login_button = self.source.ui_login_button(None) | ||||
|         self.assertTrue(ui_login_button.challenge.is_valid(raise_exception=True)) | ||||
|  | ||||
|     def test_get_user_info(self): | ||||
|         """Test get_user_info""" | ||||
|         token = generate_key() | ||||
|  | ||||
| @ -23,7 +23,7 @@ LOGGER = get_logger() | ||||
|  | ||||
| VALID_SCHEMA_NAME = re.compile(r"^t_[a-z0-9]{1,61}$") | ||||
|  | ||||
| DEFAULT_TOKEN_DURATION = "minutes=30"  # nosec | ||||
| DEFAULT_TOKEN_DURATION = "days=1"  # nosec | ||||
| DEFAULT_TOKEN_LENGTH = 60 | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							| @ -28,7 +28,7 @@ require ( | ||||
| 	github.com/spf13/cobra v1.8.0 | ||||
| 	github.com/stretchr/testify v1.9.0 | ||||
| 	github.com/wwt/guac v1.3.2 | ||||
| 	goauthentik.io/api/v3 v3.2024023.2 | ||||
| 	goauthentik.io/api/v3 v3.2024040.1 | ||||
| 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | ||||
| 	golang.org/x/oauth2 v0.19.0 | ||||
| 	golang.org/x/sync v0.7.0 | ||||
|  | ||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							| @ -294,8 +294,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y | ||||
| go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= | ||||
| go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= | ||||
| go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= | ||||
| goauthentik.io/api/v3 v3.2024023.2 h1:lSVaZAKTpsDhtw11wnkGjPalkDzv9H2VKEJllBi2aXs= | ||||
| goauthentik.io/api/v3 v3.2024023.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||
| goauthentik.io/api/v3 v3.2024040.1 h1:0Mp8XLYuscQEWVTR2lNk74WLKDpOVHX0mlbvbvcC6fw= | ||||
| goauthentik.io/api/v3 v3.2024040.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
|  | ||||
							
								
								
									
										3205
									
								
								locale/ru/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3205
									
								
								locale/ru/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										52
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										52
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							| @ -392,33 +392,33 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "black" | ||||
| version = "24.4.0" | ||||
| version = "24.4.1" | ||||
| description = "The uncompromising code formatter." | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "black-24.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6ad001a9ddd9b8dfd1b434d566be39b1cd502802c8d38bbb1ba612afda2ef436"}, | ||||
|     {file = "black-24.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3a3a092b8b756c643fe45f4624dbd5a389f770a4ac294cf4d0fce6af86addaf"}, | ||||
|     {file = "black-24.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae79397f367ac8d7adb6c779813328f6d690943f64b32983e896bcccd18cbad"}, | ||||
|     {file = "black-24.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:71d998b73c957444fb7c52096c3843875f4b6b47a54972598741fe9a7f737fcb"}, | ||||
|     {file = "black-24.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5537f456a22cf5cfcb2707803431d2feeb82ab3748ade280d6ccd0b40ed2e8"}, | ||||
|     {file = "black-24.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64e60a7edd71fd542a10a9643bf369bfd2644de95ec71e86790b063aa02ff745"}, | ||||
|     {file = "black-24.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd5b4f76056cecce3e69b0d4c228326d2595f506797f40b9233424e2524c070"}, | ||||
|     {file = "black-24.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:64578cf99b6b46a6301bc28bdb89f9d6f9b592b1c5837818a177c98525dbe397"}, | ||||
|     {file = "black-24.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f95cece33329dc4aa3b0e1a771c41075812e46cf3d6e3f1dfe3d91ff09826ed2"}, | ||||
|     {file = "black-24.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4396ca365a4310beef84d446ca5016f671b10f07abdba3e4e4304218d2c71d33"}, | ||||
|     {file = "black-24.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d99dfdf37a2a00a6f7a8dcbd19edf361d056ee51093b2445de7ca09adac965"}, | ||||
|     {file = "black-24.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:21f9407063ec71c5580b8ad975653c66508d6a9f57bd008bb8691d273705adcd"}, | ||||
|     {file = "black-24.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:652e55bb722ca026299eb74e53880ee2315b181dfdd44dca98e43448620ddec1"}, | ||||
|     {file = "black-24.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f2966b9b2b3b7104fca9d75b2ee856fe3fdd7ed9e47c753a4bb1a675f2caab8"}, | ||||
|     {file = "black-24.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bb9ca06e556a09f7f7177bc7cb604e5ed2d2df1e9119e4f7d2f1f7071c32e5d"}, | ||||
|     {file = "black-24.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4e71cdebdc8efeb6deaf5f2deb28325f8614d48426bed118ecc2dcaefb9ebf3"}, | ||||
|     {file = "black-24.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6644f97a7ef6f401a150cca551a1ff97e03c25d8519ee0bbc9b0058772882665"}, | ||||
|     {file = "black-24.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75a2d0b4f5eb81f7eebc31f788f9830a6ce10a68c91fbe0fade34fff7a2836e6"}, | ||||
|     {file = "black-24.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb949f56a63c5e134dfdca12091e98ffb5fd446293ebae123d10fc1abad00b9e"}, | ||||
|     {file = "black-24.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:7852b05d02b5b9a8c893ab95863ef8986e4dda29af80bbbda94d7aee1abf8702"}, | ||||
|     {file = "black-24.4.0-py3-none-any.whl", hash = "sha256:74eb9b5420e26b42c00a3ff470dc0cd144b80a766128b1771d07643165e08d0e"}, | ||||
|     {file = "black-24.4.0.tar.gz", hash = "sha256:f07b69fda20578367eaebbd670ff8fc653ab181e1ff95d84497f9fa20e7d0641"}, | ||||
|     {file = "black-24.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f7749fd0d97ff9415975a1432fac7df89bf13c3833cea079e55fa004d5f28c0"}, | ||||
|     {file = "black-24.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859f3cc5d2051adadf8fd504a01e02b0fd866d7549fff54bc9202d524d2e8bd7"}, | ||||
|     {file = "black-24.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59271c9c29dfa97f7fda51f56c7809b3f78e72fd8d2205189bbd23022a0618b6"}, | ||||
|     {file = "black-24.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:5ed9c34cba223149b5a0144951a0f33d65507cf82c5449cb3c35fe4b515fea9a"}, | ||||
|     {file = "black-24.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dae3ae59d6f2dc93700fd5034a3115434686e66fd6e63d4dcaa48d19880f2b0"}, | ||||
|     {file = "black-24.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5f8698974a81af83283eb47644f2711b5261138d6d9180c863fce673cbe04b13"}, | ||||
|     {file = "black-24.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f404b6e77043b23d0321fb7772522b876b6de737ad3cb97d6b156638d68ce81"}, | ||||
|     {file = "black-24.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:c94e52b766477bdcd010b872ba0714d5458536dc9d0734eff6583ba7266ffd89"}, | ||||
|     {file = "black-24.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:962d9e953872cdb83b97bb737ad47244ce2938054dc946685a4cad98520dab38"}, | ||||
|     {file = "black-24.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d8e3b2486b7dd522b1ab2ba1ec4907f0aa8f5e10a33c4271fb331d1d10b70c"}, | ||||
|     {file = "black-24.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed77e214b785148f57e43ca425b6e0850165144aa727d66ac604e56a70bb7825"}, | ||||
|     {file = "black-24.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:4ef4764437d7eba8386689cd06e1fb5341ee0ae2e9e22582b21178782de7ed94"}, | ||||
|     {file = "black-24.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:92b183f8eef5baf7b20a513abcf982ad616f544f593f6688bb2850d2982911f1"}, | ||||
|     {file = "black-24.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:945abd7b3572add997757c94295bb3e73c6ffaf3366b1f26cb2356a4bffd1dc3"}, | ||||
|     {file = "black-24.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db5154b9e5b478031371d8bc41ff37b33855fa223a6cfba456c9b73fb96f77d4"}, | ||||
|     {file = "black-24.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:afc84c33c1a9aaf3d73140cee776b4ddf73ff429ffe6b7c56dc1c9c10725856d"}, | ||||
|     {file = "black-24.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0889f4eb8b3bdf8b189e41a71cf0dbb8141a98346cd1a2695dea5995d416e940"}, | ||||
|     {file = "black-24.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5bb0143f175db45a55227eefd63e90849d96c266330ba31719e9667d0d5ec3b9"}, | ||||
|     {file = "black-24.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:713a04a78e78f28ef7e8df7a16fe075670ea164860fcef3885e4f3dffc0184b3"}, | ||||
|     {file = "black-24.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:171959bc879637a8cdbc53dc3fddae2a83e151937a28cf605fd175ce61e0e94a"}, | ||||
|     {file = "black-24.4.1-py3-none-any.whl", hash = "sha256:ecbab810604fe02c70b3a08afd39beb599f7cc9afd13e81f5336014133b4fe35"}, | ||||
|     {file = "black-24.4.1.tar.gz", hash = "sha256:5241612dc8cad5b6fd47432b8bd04db80e07cfbc53bb69e9ae18985063bcb8dd"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3587,13 +3587,13 @@ django-query = ["django (>=3.2)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "selenium" | ||||
| version = "4.19.0" | ||||
| version = "4.20.0" | ||||
| description = "" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "selenium-4.19.0-py3-none-any.whl", hash = "sha256:5b4f49240d61e687a73f7968ae2517d403882aae3550eae2a229c745e619f1d9"}, | ||||
|     {file = "selenium-4.19.0.tar.gz", hash = "sha256:d9dfd6d0b021d71d0a48b865fe7746490ba82b81e9c87b212360006629eb1853"}, | ||||
|     {file = "selenium-4.20.0-py3-none-any.whl", hash = "sha256:b1d0c33b38ca27d0499183e48e1dd09ff26973481f5d3ef2983073813ae6588d"}, | ||||
|     {file = "selenium-4.20.0.tar.gz", hash = "sha256:0bd564ee166980d419a8aaf4ac00289bc152afcf2eadca5efe8c8e36711853fd"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
|  | ||||
							
								
								
									
										2034
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2034
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -46,7 +46,7 @@ | ||||
|         "@open-wc/lit-helpers": "^0.7.0", | ||||
|         "@patternfly/elements": "^3.0.1", | ||||
|         "@patternfly/patternfly": "^4.224.2", | ||||
|         "@sentry/browser": "^7.112.1", | ||||
|         "@sentry/browser": "^7.112.2", | ||||
|         "@webcomponents/webcomponentsjs": "^2.8.0", | ||||
|         "base64-js": "^1.5.1", | ||||
|         "chart.js": "^4.4.2", | ||||
|  | ||||
| @ -214,28 +214,23 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage> | ||||
|                         name="sources" | ||||
|                     > | ||||
|                         <select class="pf-c-form-control" multiple> | ||||
|                             ${this.sources?.results.map((source) => { | ||||
|                                 let selected = Array.from(this.instance?.sources || []).some( | ||||
|                                     (su) => { | ||||
|                                         return su == source.pk; | ||||
|                                     }, | ||||
|                                 ); | ||||
|                                 // Creating a new instance, auto-select built-in source | ||||
|                                 // Only when no other sources exist | ||||
|                                 if ( | ||||
|                                     !this.instance && | ||||
|                                     source.component === "" && | ||||
|                                     (this.sources?.results || []).length < 2 | ||||
|                                 ) { | ||||
|                                     selected = true; | ||||
|                                 } | ||||
|                                 return html`<option | ||||
|                                     value=${ifDefined(source.pk)} | ||||
|                                     ?selected=${selected} | ||||
|                                 > | ||||
|                                     ${source.name} | ||||
|                                 </option>`; | ||||
|                             })} | ||||
|                             ${this.sources?.results | ||||
|                                 .filter((source) => { | ||||
|                                     return source.component !== ""; | ||||
|                                 }) | ||||
|                                 .map((source) => { | ||||
|                                     const selected = Array.from(this.instance?.sources || []).some( | ||||
|                                         (su) => { | ||||
|                                             return su == source.pk; | ||||
|                                         }, | ||||
|                                     ); | ||||
|                                     return html`<option | ||||
|                                         value=${ifDefined(source.pk)} | ||||
|                                         ?selected=${selected} | ||||
|                                     > | ||||
|                                         ${source.name} | ||||
|                                     </option>`; | ||||
|                                 })} | ||||
|                         </select> | ||||
|                         <p class="pf-c-form__helper-text"> | ||||
|                             ${msg( | ||||
|  | ||||
| @ -111,6 +111,21 @@ export function dateTimeLocal(date: Date): string { | ||||
|     return `${parts[0]}:${parts[1]}`; | ||||
| } | ||||
|  | ||||
| export function dateToUTC(date: Date): Date { | ||||
|     // Sigh...so our API is UTC/can take TZ info in the ISO format as it should. | ||||
|     // datetime-local fields (which is almost the only date-time input we use) | ||||
|     // can return its value as a UTC timestamp...however the generated API client | ||||
|     // _requires_ a Date object, only to then convert it to an ISO string anyways | ||||
|     // JS Dates don't include timezone info in the ISO string, so that just sends | ||||
|     // the local time as UTC...which is wrong | ||||
|     // Instead we have to do this, convert the given date to a UTC timestamp, | ||||
|     // then subtract the timezone offset to create an "invalid" date (correct time&date) | ||||
|     // but it still "thinks" it's in local TZ | ||||
|     const timestamp = date.getTime(); | ||||
|     const offset = -1 * (new Date().getTimezoneOffset() * 60000); | ||||
|     return new Date(timestamp - offset); | ||||
| } | ||||
|  | ||||
| // Lit is extremely well-typed with regard to CSS, and Storybook's `build` does not currently have a | ||||
| // coherent way of importing CSS-as-text into CSSStyleSheet. It works well when Storybook is running | ||||
| // in `dev,` but in `build` it fails. Storied components will have to map their textual CSS imports | ||||
|  | ||||
| @ -1,23 +1,22 @@ | ||||
| import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts"; | ||||
| import type { ReactiveElementHost } from "@goauthentik/elements/types.js"; | ||||
|  | ||||
| import { ContextProvider } from "@lit/context"; | ||||
| import { ReactiveController, ReactiveControllerHost } from "lit"; | ||||
| import type { ReactiveController } from "lit"; | ||||
|  | ||||
| import type { CurrentBrand } from "@goauthentik/api"; | ||||
| import { CoreApi } from "@goauthentik/api"; | ||||
|  | ||||
| import type { AkInterface } from "./Interface"; | ||||
|  | ||||
| type ReactiveElementHost = Partial<ReactiveControllerHost> & AkInterface; | ||||
|  | ||||
| export class BrandContextController implements ReactiveController { | ||||
|     host!: ReactiveElementHost; | ||||
|     host!: ReactiveElementHost<AkInterface>; | ||||
|  | ||||
|     context!: ContextProvider<{ __context__: CurrentBrand | undefined }>; | ||||
|  | ||||
|     constructor(host: ReactiveElementHost) { | ||||
|     constructor(host: ReactiveElementHost<AkInterface>) { | ||||
|         this.host = host; | ||||
|         this.context = new ContextProvider(this.host, { | ||||
|             context: authentikBrandContext, | ||||
|  | ||||
| @ -2,23 +2,22 @@ import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { globalAK } from "@goauthentik/common/global"; | ||||
| import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; | ||||
| import type { ReactiveElementHost } from "@goauthentik/elements/types.js"; | ||||
|  | ||||
| import { ContextProvider } from "@lit/context"; | ||||
| import { ReactiveController, ReactiveControllerHost } from "lit"; | ||||
| import type { ReactiveController } from "lit"; | ||||
|  | ||||
| import type { Config } from "@goauthentik/api"; | ||||
| import { RootApi } from "@goauthentik/api"; | ||||
|  | ||||
| import type { AkInterface } from "./Interface"; | ||||
|  | ||||
| type ReactiveElementHost = Partial<ReactiveControllerHost> & AkInterface; | ||||
|  | ||||
| export class ConfigContextController implements ReactiveController { | ||||
|     host!: ReactiveElementHost; | ||||
|     host!: ReactiveElementHost<AkInterface>; | ||||
|  | ||||
|     context!: ContextProvider<{ __context__: Config | undefined }>; | ||||
|  | ||||
|     constructor(host: ReactiveElementHost) { | ||||
|     constructor(host: ReactiveElementHost<AkInterface>) { | ||||
|         this.host = host; | ||||
|         this.context = new ContextProvider(this.host, { | ||||
|             context: authentikConfigContext, | ||||
|  | ||||
| @ -1,23 +1,22 @@ | ||||
| import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/authentik/common/constants"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts"; | ||||
| import type { ReactiveElementHost } from "@goauthentik/elements/types.js"; | ||||
|  | ||||
| import { ContextProvider } from "@lit/context"; | ||||
| import { ReactiveController, ReactiveControllerHost } from "lit"; | ||||
| import type { ReactiveController } from "lit"; | ||||
|  | ||||
| import type { LicenseSummary } from "@goauthentik/api"; | ||||
| import { EnterpriseApi } from "@goauthentik/api"; | ||||
|  | ||||
| import type { AkEnterpriseInterface } from "./Interface"; | ||||
|  | ||||
| type ReactiveElementHost = Partial<ReactiveControllerHost> & AkEnterpriseInterface; | ||||
|  | ||||
| export class EnterpriseContextController implements ReactiveController { | ||||
|     host!: ReactiveElementHost; | ||||
|     host!: ReactiveElementHost<AkEnterpriseInterface>; | ||||
|  | ||||
|     context!: ContextProvider<{ __context__: LicenseSummary | undefined }>; | ||||
|  | ||||
|     constructor(host: ReactiveElementHost) { | ||||
|     constructor(host: ReactiveElementHost<AkEnterpriseInterface>) { | ||||
|         this.host = host; | ||||
|         this.context = new ContextProvider(this.host, { | ||||
|             context: authentikEnterpriseContext, | ||||
|  | ||||
| @ -1,13 +1,11 @@ | ||||
| import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; | ||||
| import type { Constructor } from "@goauthentik/elements/types.js"; | ||||
|  | ||||
| import { consume } from "@lit/context"; | ||||
| import type { LitElement } from "lit"; | ||||
|  | ||||
| import type { Config } from "@goauthentik/api"; | ||||
|  | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
| type Constructor<T = object> = new (...args: any[]) => T; | ||||
|  | ||||
| export function WithAuthentikConfig<T extends Constructor<LitElement>>( | ||||
|     superclass: T, | ||||
|     subscribe = true, | ||||
|  | ||||
| @ -1,14 +1,12 @@ | ||||
| import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts"; | ||||
| import type { AbstractConstructor } from "@goauthentik/elements/types.js"; | ||||
|  | ||||
| import { consume } from "@lit/context"; | ||||
| import type { LitElement } from "lit"; | ||||
|  | ||||
| import type { CurrentBrand } from "@goauthentik/api"; | ||||
|  | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
| type Constructor<T = object> = abstract new (...args: any[]) => T; | ||||
|  | ||||
| export function WithBrandConfig<T extends Constructor<LitElement>>( | ||||
| export function WithBrandConfig<T extends AbstractConstructor<LitElement>>( | ||||
|     superclass: T, | ||||
|     subscribe = true, | ||||
| ) { | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; | ||||
| import type { AbstractConstructor } from "@goauthentik/elements/types.js"; | ||||
|  | ||||
| import { consume } from "@lit/context"; | ||||
| import type { LitElement } from "lit"; | ||||
| @ -6,9 +7,6 @@ import type { LitElement } from "lit"; | ||||
| import { CapabilitiesEnum } from "@goauthentik/api"; | ||||
| import { Config } from "@goauthentik/api"; | ||||
|  | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
| type Constructor<T = object> = abstract new (...args: any[]) => T; | ||||
|  | ||||
| // Using a unique, lexically scoped, and locally static symbol as the field name for the context | ||||
| // means that it's inaccessible to any child class looking for it. It's one of the strongest privacy | ||||
| // guarantees in JavaScript. | ||||
| @ -45,7 +43,7 @@ class WCC { | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| export function WithCapabilitiesConfig<T extends Constructor<LitElement>>( | ||||
| export function WithCapabilitiesConfig<T extends AbstractConstructor<LitElement>>( | ||||
|     superclass: T, | ||||
|     subscribe = true, | ||||
| ) { | ||||
|  | ||||
| @ -1,13 +1,11 @@ | ||||
| import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts"; | ||||
| import { Constructor } from "@goauthentik/elements/types.js"; | ||||
|  | ||||
| import { consume } from "@lit/context"; | ||||
| import type { LitElement } from "lit"; | ||||
|  | ||||
| import type { LicenseSummary } from "@goauthentik/api"; | ||||
|  | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
| type Constructor<T = object> = abstract new (...args: any[]) => T; | ||||
|  | ||||
| export function WithLicenseSummary<T extends Constructor<LitElement>>( | ||||
|     superclass: T, | ||||
|     subscribe = true, | ||||
|  | ||||
| @ -87,7 +87,7 @@ export class Markdown extends AKElement { | ||||
|             const parsedContent = matter(this.md); | ||||
|             const parsedHTML = this.converter.makeHtml(parsedContent.content); | ||||
|             const replacers = [...this.defaultReplacers, ...this.replacers]; | ||||
|             this.docTitle = parsedContent.data["title"] ?? ""; | ||||
|             this.docTitle = parsedContent?.data?.title ?? ""; | ||||
|             this.docHtml = replacers.reduce( | ||||
|                 (html, replacer) => replacer(html, { path: this.meta }), | ||||
|                 parsedHTML, | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { EVENT_REFRESH } from "@goauthentik/common/constants"; | ||||
| import { MessageLevel } from "@goauthentik/common/messages"; | ||||
| import { camelToSnake, convertToSlug } from "@goauthentik/common/utils"; | ||||
| import { camelToSnake, convertToSlug, dateToUTC } from "@goauthentik/common/utils"; | ||||
| import { AKElement } from "@goauthentik/elements/Base"; | ||||
| import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect"; | ||||
| @ -104,7 +104,7 @@ export function serializeForm<T extends KeyUnknown>( | ||||
|             inputElement.tagName.toLowerCase() === "input" && | ||||
|             inputElement.type === "datetime-local" | ||||
|         ) { | ||||
|             assignValue(inputElement, new Date(inputElement.valueAsNumber), json); | ||||
|             assignValue(inputElement, dateToUTC(new Date(inputElement.valueAsNumber)), json); | ||||
|         } else if ( | ||||
|             inputElement.tagName.toLowerCase() === "input" && | ||||
|             "type" in inputElement.dataset && | ||||
| @ -112,7 +112,7 @@ export function serializeForm<T extends KeyUnknown>( | ||||
|         ) { | ||||
|             // Workaround for Firefox <93, since 92 and older don't support | ||||
|             // datetime-local fields | ||||
|             assignValue(inputElement, new Date(inputElement.value), json); | ||||
|             assignValue(inputElement, dateToUTC(new Date(inputElement.value)), json); | ||||
|         } else if ( | ||||
|             inputElement.tagName.toLowerCase() === "input" && | ||||
|             inputElement.type === "checkbox" | ||||
|  | ||||
							
								
								
									
										11
									
								
								web/src/elements/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								web/src/elements/types.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| import { AKElement } from "@goauthentik/elements/Base"; | ||||
|  | ||||
| import { ReactiveControllerHost } from "lit"; | ||||
|  | ||||
| export type ReactiveElementHost<T = AKElement> = Partial<ReactiveControllerHost> & T; | ||||
|  | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
| export type Constructor<T = object> = new (...args: any[]) => T; | ||||
|  | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
| export type AbstractConstructor<T = object> = abstract new (...args: any[]) => T; | ||||
| @ -1,3 +1,4 @@ | ||||
| import { dateTimeLocal } from "@goauthentik/authentik/common/utils"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; | ||||
| @ -44,11 +45,8 @@ export class UserTokenForm extends ModelForm<Token, string> { | ||||
|     renderForm(): TemplateResult { | ||||
|         const now = new Date(); | ||||
|         const expiringDate = this.instance?.expires | ||||
|             ? new Date( | ||||
|                   this.instance.expires.getTime() - | ||||
|                       this.instance.expires.getTimezoneOffset() * 60000, | ||||
|               ) | ||||
|             : new Date(now.getTime() + 30 * 60000 - now.getTimezoneOffset() * 60000); | ||||
|             ? new Date(this.instance.expires.getTime()) | ||||
|             : new Date(now.getTime() + 30 * 60000); | ||||
|  | ||||
|         return html` <ak-form-element-horizontal | ||||
|                 label=${msg("Identifier")} | ||||
| @ -73,8 +71,8 @@ export class UserTokenForm extends ModelForm<Token, string> { | ||||
|                 ? html`<ak-form-element-horizontal label=${msg("Expiring")} name="expires"> | ||||
|                       <input | ||||
|                           type="datetime-local" | ||||
|                           value="${expiringDate.toISOString().slice(0, -8)}" | ||||
|                           min="${now.toISOString().slice(0, -8)}" | ||||
|                           value="${dateTimeLocal(expiringDate)}" | ||||
|                           min="${dateTimeLocal(now)}" | ||||
|                           class="pf-c-form-control" | ||||
|                       /> | ||||
|                   </ak-form-element-horizontal>` | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Ken Sternberg
					Ken Sternberg