Compare commits
	
		
			36 Commits
		
	
	
		
			providers/
			...
			providers/
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| faf8bf591f | |||
| 52115f9345 | |||
| b476551f13 | |||
| f9563c25cd | |||
| 0067e6e155 | |||
| ce183929d4 | |||
| 2fdf345271 | |||
| bbcf8418b4 | |||
| dc57be46f4 | |||
| d68b3ba516 | |||
| a9c46cfcbd | |||
| c50353ebf6 | |||
| db6be9e1b6 | |||
| a74892886d | |||
| 74cd4c2236 | |||
| ef3bd7e77b | |||
| 3f5ad2baa4 | |||
| 24805f087b | |||
| 9464b422a3 | |||
| da6d4ede51 | |||
| cecad5bfd3 | |||
| bc4b07d57b | |||
| e85d2d0096 | |||
| be1dd3103b | |||
| 5dfde5e1d3 | |||
| 7cb1e6d81e | |||
| d7c3129b1c | |||
| 2a1d33021b | |||
| f273e49ae6 | |||
| cc31957900 | |||
| b1ccdecc8e | |||
| 34031003a4 | |||
| 055e1d1025 | |||
| 59a804273e | |||
| bce70a1796 | |||
| e86c40a00c | 
| @ -1,5 +1,5 @@ | ||||
| [bumpversion] | ||||
| current_version = 2025.6.1 | ||||
| current_version = 2025.6.2 | ||||
| tag = True | ||||
| commit = True | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))? | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -202,7 +202,7 @@ jobs: | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: web/dist | ||||
|           key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b | ||||
|           key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b | ||||
|       - name: prepare web ui | ||||
|         if: steps.cache-web.outputs.cache-hit != 'true' | ||||
|         working-directory: web | ||||
|  | ||||
| @ -77,7 +77,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \ | ||||
| # Stage 4: Download uv | ||||
| FROM ghcr.io/astral-sh/uv:0.7.13 AS uv | ||||
| # Stage 5: Base python image | ||||
| FROM ghcr.io/goauthentik/fips-python:3.13.4-slim-bookworm-fips AS python-base | ||||
| FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base | ||||
|  | ||||
| ENV VENV_PATH="/ak-root/.venv" \ | ||||
|     PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \ | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| from os import environ | ||||
|  | ||||
| __version__ = "2025.6.1" | ||||
| __version__ = "2025.6.2" | ||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -15,7 +15,6 @@ class OAuth2Error(SentryIgnoredException): | ||||
|  | ||||
|     error: str | ||||
|     description: str | ||||
|     cause: str | None = None | ||||
|  | ||||
|     def create_dict(self): | ||||
|         """Return error as dict for JSON Rendering""" | ||||
| @ -35,10 +34,6 @@ class OAuth2Error(SentryIgnoredException): | ||||
|             **kwargs, | ||||
|         ) | ||||
|  | ||||
|     def with_cause(self, cause: str): | ||||
|         self.cause = cause | ||||
|         return self | ||||
|  | ||||
|  | ||||
| class RedirectUriError(OAuth2Error): | ||||
|     """The request fails due to a missing, invalid, or mismatching | ||||
|  | ||||
| @ -12,7 +12,7 @@ from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.lib.utils.time import timedelta_from_string | ||||
| from authentik.providers.oauth2.constants import SCOPE_OFFLINE_ACCESS, SCOPE_OPENID, TOKEN_TYPE | ||||
| from authentik.providers.oauth2.constants import TOKEN_TYPE | ||||
| from authentik.providers.oauth2.errors import AuthorizeError, ClientIdError, RedirectUriError | ||||
| from authentik.providers.oauth2.models import ( | ||||
|     AccessToken, | ||||
| @ -43,7 +43,7 @@ class TestAuthorize(OAuthTestCase): | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid/Foo")], | ||||
|         ) | ||||
|         with self.assertRaises(AuthorizeError) as cm: | ||||
|         with self.assertRaises(AuthorizeError): | ||||
|             request = self.factory.get( | ||||
|                 "/", | ||||
|                 data={ | ||||
| @ -53,7 +53,6 @@ class TestAuthorize(OAuthTestCase): | ||||
|                 }, | ||||
|             ) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         self.assertEqual(cm.exception.error, "unsupported_response_type") | ||||
|  | ||||
|     def test_invalid_client_id(self): | ||||
|         """Test invalid client ID""" | ||||
| @ -69,7 +68,7 @@ class TestAuthorize(OAuthTestCase): | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid/Foo")], | ||||
|         ) | ||||
|         with self.assertRaises(AuthorizeError) as cm: | ||||
|         with self.assertRaises(AuthorizeError): | ||||
|             request = self.factory.get( | ||||
|                 "/", | ||||
|                 data={ | ||||
| @ -80,30 +79,19 @@ class TestAuthorize(OAuthTestCase): | ||||
|                 }, | ||||
|             ) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         self.assertEqual(cm.exception.error, "request_not_supported") | ||||
|  | ||||
|     def test_invalid_redirect_uri_missing(self): | ||||
|         """test missing redirect URI""" | ||||
|         OAuth2Provider.objects.create( | ||||
|             name=generate_id(), | ||||
|             client_id="test", | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid")], | ||||
|         ) | ||||
|         with self.assertRaises(RedirectUriError) as cm: | ||||
|             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         self.assertEqual(cm.exception.cause, "redirect_uri_missing") | ||||
|  | ||||
|     def test_invalid_redirect_uri(self): | ||||
|         """test invalid redirect URI""" | ||||
|         """test missing/invalid redirect URI""" | ||||
|         OAuth2Provider.objects.create( | ||||
|             name=generate_id(), | ||||
|             client_id="test", | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid")], | ||||
|         ) | ||||
|         with self.assertRaises(RedirectUriError) as cm: | ||||
|         with self.assertRaises(RedirectUriError): | ||||
|             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         with self.assertRaises(RedirectUriError): | ||||
|             request = self.factory.get( | ||||
|                 "/", | ||||
|                 data={ | ||||
| @ -113,7 +101,6 @@ class TestAuthorize(OAuthTestCase): | ||||
|                 }, | ||||
|             ) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         self.assertEqual(cm.exception.cause, "redirect_uri_no_match") | ||||
|  | ||||
|     def test_blocked_redirect_uri(self): | ||||
|         """test missing/invalid redirect URI""" | ||||
| @ -121,9 +108,9 @@ class TestAuthorize(OAuthTestCase): | ||||
|             name=generate_id(), | ||||
|             client_id="test", | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "data:localhost")], | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "data:local.invalid")], | ||||
|         ) | ||||
|         with self.assertRaises(RedirectUriError) as cm: | ||||
|         with self.assertRaises(RedirectUriError): | ||||
|             request = self.factory.get( | ||||
|                 "/", | ||||
|                 data={ | ||||
| @ -133,7 +120,6 @@ class TestAuthorize(OAuthTestCase): | ||||
|                 }, | ||||
|             ) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         self.assertEqual(cm.exception.cause, "redirect_uri_forbidden_scheme") | ||||
|  | ||||
|     def test_invalid_redirect_uri_empty(self): | ||||
|         """test missing/invalid redirect URI""" | ||||
| @ -143,6 +129,9 @@ class TestAuthorize(OAuthTestCase): | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[], | ||||
|         ) | ||||
|         with self.assertRaises(RedirectUriError): | ||||
|             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         request = self.factory.get( | ||||
|             "/", | ||||
|             data={ | ||||
| @ -161,9 +150,12 @@ class TestAuthorize(OAuthTestCase): | ||||
|             name=generate_id(), | ||||
|             client_id="test", | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.REGEX, "http://local.invalid?")], | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid?")], | ||||
|         ) | ||||
|         with self.assertRaises(RedirectUriError) as cm: | ||||
|         with self.assertRaises(RedirectUriError): | ||||
|             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         with self.assertRaises(RedirectUriError): | ||||
|             request = self.factory.get( | ||||
|                 "/", | ||||
|                 data={ | ||||
| @ -173,7 +165,6 @@ class TestAuthorize(OAuthTestCase): | ||||
|                 }, | ||||
|             ) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         self.assertEqual(cm.exception.cause, "redirect_uri_no_match") | ||||
|  | ||||
|     def test_redirect_uri_invalid_regex(self): | ||||
|         """test missing/invalid redirect URI (invalid regex)""" | ||||
| @ -181,9 +172,12 @@ class TestAuthorize(OAuthTestCase): | ||||
|             name=generate_id(), | ||||
|             client_id="test", | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.REGEX, "+")], | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "+")], | ||||
|         ) | ||||
|         with self.assertRaises(RedirectUriError) as cm: | ||||
|         with self.assertRaises(RedirectUriError): | ||||
|             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         with self.assertRaises(RedirectUriError): | ||||
|             request = self.factory.get( | ||||
|                 "/", | ||||
|                 data={ | ||||
| @ -193,22 +187,23 @@ class TestAuthorize(OAuthTestCase): | ||||
|                 }, | ||||
|             ) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         self.assertEqual(cm.exception.cause, "redirect_uri_no_match") | ||||
|  | ||||
|     def test_redirect_uri_regex(self): | ||||
|         """test valid redirect URI (regex)""" | ||||
|     def test_empty_redirect_uri(self): | ||||
|         """test empty redirect URI (configure in provider)""" | ||||
|         OAuth2Provider.objects.create( | ||||
|             name=generate_id(), | ||||
|             client_id="test", | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.REGEX, ".+")], | ||||
|         ) | ||||
|         with self.assertRaises(RedirectUriError): | ||||
|             request = self.factory.get("/", data={"response_type": "code", "client_id": "test"}) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         request = self.factory.get( | ||||
|             "/", | ||||
|             data={ | ||||
|                 "response_type": "code", | ||||
|                 "client_id": "test", | ||||
|                 "redirect_uri": "http://foo.bar.baz", | ||||
|                 "redirect_uri": "http://localhost", | ||||
|             }, | ||||
|         ) | ||||
|         OAuthAuthorizationParams.from_request(request) | ||||
| @ -263,7 +258,7 @@ class TestAuthorize(OAuthTestCase): | ||||
|             GrantTypes.IMPLICIT, | ||||
|         ) | ||||
|         # Implicit without openid scope | ||||
|         with self.assertRaises(AuthorizeError) as cm: | ||||
|         with self.assertRaises(AuthorizeError): | ||||
|             request = self.factory.get( | ||||
|                 "/", | ||||
|                 data={ | ||||
| @ -290,7 +285,7 @@ class TestAuthorize(OAuthTestCase): | ||||
|         self.assertEqual( | ||||
|             OAuthAuthorizationParams.from_request(request).grant_type, GrantTypes.HYBRID | ||||
|         ) | ||||
|         with self.assertRaises(AuthorizeError) as cm: | ||||
|         with self.assertRaises(AuthorizeError): | ||||
|             request = self.factory.get( | ||||
|                 "/", | ||||
|                 data={ | ||||
| @ -300,7 +295,6 @@ class TestAuthorize(OAuthTestCase): | ||||
|                 }, | ||||
|             ) | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         self.assertEqual(cm.exception.error, "unsupported_response_type") | ||||
|  | ||||
|     def test_full_code(self): | ||||
|         """Test full authorization""" | ||||
| @ -393,8 +387,7 @@ class TestAuthorize(OAuthTestCase): | ||||
|             self.assertEqual( | ||||
|                 response.url, | ||||
|                 ( | ||||
|                     f"http://localhost#access_token={token.token}" | ||||
|                     f"&id_token={provider.encode(token.id_token.to_dict())}" | ||||
|                     f"http://localhost#id_token={provider.encode(token.id_token.to_dict())}" | ||||
|                     f"&token_type={TOKEN_TYPE}" | ||||
|                     f"&expires_in={int(expires)}&state={state}" | ||||
|                 ), | ||||
| @ -569,7 +562,6 @@ class TestAuthorize(OAuthTestCase): | ||||
|                 "url": "http://localhost", | ||||
|                 "title": f"Redirecting to {app.name}...", | ||||
|                 "attrs": { | ||||
|                     "access_token": token.token, | ||||
|                     "id_token": provider.encode(token.id_token.to_dict()), | ||||
|                     "token_type": TOKEN_TYPE, | ||||
|                     "expires_in": "3600", | ||||
| @ -621,54 +613,3 @@ class TestAuthorize(OAuthTestCase): | ||||
|                 }, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     def test_openid_missing_invalid(self): | ||||
|         """test request requiring an OpenID scope to be set""" | ||||
|         OAuth2Provider.objects.create( | ||||
|             name=generate_id(), | ||||
|             client_id="test", | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://localhost")], | ||||
|         ) | ||||
|         request = self.factory.get( | ||||
|             "/", | ||||
|             data={ | ||||
|                 "response_type": "id_token", | ||||
|                 "client_id": "test", | ||||
|                 "redirect_uri": "http://localhost", | ||||
|                 "scope": "", | ||||
|             }, | ||||
|         ) | ||||
|         with self.assertRaises(AuthorizeError) as cm: | ||||
|             OAuthAuthorizationParams.from_request(request) | ||||
|         self.assertEqual(cm.exception.cause, "scope_openid_missing") | ||||
|  | ||||
|     @apply_blueprint("system/providers-oauth2.yaml") | ||||
|     def test_offline_access_invalid(self): | ||||
|         """test request for offline_access with invalid response type""" | ||||
|         provider = OAuth2Provider.objects.create( | ||||
|             name=generate_id(), | ||||
|             client_id="test", | ||||
|             authorization_flow=create_test_flow(), | ||||
|             redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://localhost")], | ||||
|         ) | ||||
|         provider.property_mappings.set( | ||||
|             ScopeMapping.objects.filter( | ||||
|                 managed__in=[ | ||||
|                     "goauthentik.io/providers/oauth2/scope-openid", | ||||
|                     "goauthentik.io/providers/oauth2/scope-offline_access", | ||||
|                 ] | ||||
|             ) | ||||
|         ) | ||||
|         request = self.factory.get( | ||||
|             "/", | ||||
|             data={ | ||||
|                 "response_type": "id_token", | ||||
|                 "client_id": "test", | ||||
|                 "redirect_uri": "http://localhost", | ||||
|                 "scope": f"{SCOPE_OPENID} {SCOPE_OFFLINE_ACCESS}", | ||||
|                 "nonce": generate_id(), | ||||
|             }, | ||||
|         ) | ||||
|         parsed = OAuthAuthorizationParams.from_request(request) | ||||
|         self.assertNotIn(SCOPE_OFFLINE_ACCESS, parsed.scope) | ||||
|  | ||||
| @ -150,12 +150,12 @@ class OAuthAuthorizationParams: | ||||
|         self.check_redirect_uri() | ||||
|         self.check_grant() | ||||
|         self.check_scope(github_compat) | ||||
|         self.check_nonce() | ||||
|         self.check_code_challenge() | ||||
|         if self.request: | ||||
|             raise AuthorizeError( | ||||
|                 self.redirect_uri, "request_not_supported", self.grant_type, self.state | ||||
|             ) | ||||
|         self.check_nonce() | ||||
|         self.check_code_challenge() | ||||
|  | ||||
|     def check_grant(self): | ||||
|         """Check grant""" | ||||
| @ -190,7 +190,7 @@ class OAuthAuthorizationParams: | ||||
|         allowed_redirect_urls = self.provider.redirect_uris | ||||
|         if not self.redirect_uri: | ||||
|             LOGGER.warning("Missing redirect uri.") | ||||
|             raise RedirectUriError("", allowed_redirect_urls).with_cause("redirect_uri_missing") | ||||
|             raise RedirectUriError("", allowed_redirect_urls) | ||||
|  | ||||
|         if len(allowed_redirect_urls) < 1: | ||||
|             LOGGER.info("Setting redirect for blank redirect_uris", redirect=self.redirect_uri) | ||||
| @ -219,14 +219,10 @@ class OAuthAuthorizationParams: | ||||
|                         provider=self.provider, | ||||
|                     ) | ||||
|         if not match_found: | ||||
|             raise RedirectUriError(self.redirect_uri, allowed_redirect_urls).with_cause( | ||||
|                 "redirect_uri_no_match" | ||||
|             ) | ||||
|             raise RedirectUriError(self.redirect_uri, allowed_redirect_urls) | ||||
|         # Check against forbidden schemes | ||||
|         if urlparse(self.redirect_uri).scheme in FORBIDDEN_URI_SCHEMES: | ||||
|             raise RedirectUriError(self.redirect_uri, allowed_redirect_urls).with_cause( | ||||
|                 "redirect_uri_forbidden_scheme" | ||||
|             ) | ||||
|             raise RedirectUriError(self.redirect_uri, allowed_redirect_urls) | ||||
|  | ||||
|     def check_scope(self, github_compat=False): | ||||
|         """Ensure openid scope is set in Hybrid flows, or when requesting an id_token""" | ||||
| @ -255,9 +251,7 @@ class OAuthAuthorizationParams: | ||||
|             or self.response_type in [ResponseTypes.ID_TOKEN, ResponseTypes.ID_TOKEN_TOKEN] | ||||
|         ): | ||||
|             LOGGER.warning("Missing 'openid' scope.") | ||||
|             raise AuthorizeError( | ||||
|                 self.redirect_uri, "invalid_scope", self.grant_type, self.state | ||||
|             ).with_cause("scope_openid_missing") | ||||
|             raise AuthorizeError(self.redirect_uri, "invalid_scope", self.grant_type, self.state) | ||||
|         if SCOPE_OFFLINE_ACCESS in self.scope: | ||||
|             # https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess | ||||
|             # Don't explicitly request consent with offline_access, as the spec allows for | ||||
| @ -292,9 +286,7 @@ class OAuthAuthorizationParams: | ||||
|             return | ||||
|         if not self.nonce: | ||||
|             LOGGER.warning("Missing nonce for OpenID Request") | ||||
|             raise AuthorizeError( | ||||
|                 self.redirect_uri, "invalid_request", self.grant_type, self.state | ||||
|             ).with_cause("none_missing") | ||||
|             raise AuthorizeError(self.redirect_uri, "invalid_request", self.grant_type, self.state) | ||||
|  | ||||
|     def check_code_challenge(self): | ||||
|         """PKCE validation of the transformation method.""" | ||||
| @ -353,10 +345,10 @@ class AuthorizationFlowInitView(PolicyAccessView): | ||||
|                 self.request, github_compat=self.github_compat | ||||
|             ) | ||||
|         except AuthorizeError as error: | ||||
|             LOGGER.warning(error.description, redirect_uri=error.redirect_uri, cause=error.cause) | ||||
|             LOGGER.warning(error.description, redirect_uri=error.redirect_uri) | ||||
|             raise RequestValidationError(error.get_response(self.request)) from None | ||||
|         except OAuth2Error as error: | ||||
|             LOGGER.warning(error.description, cause=error.cause) | ||||
|             LOGGER.warning(error.description) | ||||
|             raise RequestValidationError( | ||||
|                 bad_request_message(self.request, error.description, title=error.error) | ||||
|             ) from None | ||||
| @ -638,7 +630,6 @@ class OAuthFulfillmentStage(StageView): | ||||
|         if self.params.response_type in [ | ||||
|             ResponseTypes.ID_TOKEN_TOKEN, | ||||
|             ResponseTypes.CODE_ID_TOKEN_TOKEN, | ||||
|             ResponseTypes.ID_TOKEN, | ||||
|             ResponseTypes.CODE_TOKEN, | ||||
|         ]: | ||||
|             query_fragment["access_token"] = token.token | ||||
|  | ||||
| @ -190,6 +190,7 @@ class SAMLProviderSerializer(ProviderSerializer): | ||||
|             "sign_response", | ||||
|             "sp_binding", | ||||
|             "default_relay_state", | ||||
|             "default_name_id_policy", | ||||
|             "url_download_metadata", | ||||
|             "url_sso_post", | ||||
|             "url_sso_redirect", | ||||
|  | ||||
| @ -0,0 +1,31 @@ | ||||
| # Generated by Django 5.1.11 on 2025-06-18 09:27 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_providers_saml", "0018_alter_samlprovider_acs_url"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="samlprovider", | ||||
|             name="default_name_id_policy", | ||||
|             field=models.TextField( | ||||
|                 choices=[ | ||||
|                     ("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"), | ||||
|                     ("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"), | ||||
|                     ("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", "X509"), | ||||
|                     ( | ||||
|                         "urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName", | ||||
|                         "Windows", | ||||
|                     ), | ||||
|                     ("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"), | ||||
|                     ("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "Unspecified"), | ||||
|                 ], | ||||
|                 default="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -12,6 +12,7 @@ from authentik.core.models import PropertyMapping, Provider | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.lib.models import DomainlessURLValidator | ||||
| from authentik.lib.utils.time import timedelta_string_validator | ||||
| from authentik.sources.saml.models import SAMLNameIDPolicy | ||||
| from authentik.sources.saml.processors.constants import ( | ||||
|     DSA_SHA1, | ||||
|     ECDSA_SHA1, | ||||
| @ -179,6 +180,9 @@ class SAMLProvider(Provider): | ||||
|     default_relay_state = models.TextField( | ||||
|         default="", blank=True, help_text=_("Default relay_state value for IDP-initiated logins") | ||||
|     ) | ||||
|     default_name_id_policy = models.TextField( | ||||
|         choices=SAMLNameIDPolicy.choices, default=SAMLNameIDPolicy.UNSPECIFIED | ||||
|     ) | ||||
|  | ||||
|     sign_assertion = models.BooleanField(default=True) | ||||
|     sign_response = models.BooleanField(default=False) | ||||
|  | ||||
| @ -205,6 +205,13 @@ class AssertionProcessor: | ||||
|     def get_name_id(self) -> Element: | ||||
|         """Get NameID Element""" | ||||
|         name_id = Element(f"{{{NS_SAML_ASSERTION}}}NameID") | ||||
|         # For requests that don't specify a NameIDPolicy, check if we | ||||
|         # can fall back to the provider default | ||||
|         if ( | ||||
|             self.auth_n_request.name_id_policy == SAML_NAME_ID_FORMAT_UNSPECIFIED | ||||
|             and self.provider.default_name_id_policy != SAML_NAME_ID_FORMAT_UNSPECIFIED | ||||
|         ): | ||||
|             self.auth_n_request.name_id_policy = self.provider.default_name_id_policy | ||||
|         name_id.attrib["Format"] = self.auth_n_request.name_id_policy | ||||
|         # persistent is used as a fallback, so always generate it | ||||
|         persistent = self.http_request.user.uid | ||||
|  | ||||
| @ -13,6 +13,7 @@ from authentik.lib.xml import lxml_from_string | ||||
| from authentik.providers.saml.exceptions import CannotHandleAssertion | ||||
| from authentik.providers.saml.models import SAMLProvider | ||||
| from authentik.providers.saml.utils.encoding import decode_base64_and_inflate | ||||
| from authentik.sources.saml.models import SAMLNameIDPolicy | ||||
| from authentik.sources.saml.processors.constants import ( | ||||
|     DSA_SHA1, | ||||
|     NS_MAP, | ||||
| @ -175,7 +176,9 @@ class AuthNRequestParser: | ||||
|  | ||||
|     def idp_initiated(self) -> AuthNRequest: | ||||
|         """Create IdP Initiated AuthNRequest""" | ||||
|         relay_state = None | ||||
|         request = AuthNRequest(relay_state=None) | ||||
|         if self.provider.default_relay_state != "": | ||||
|             relay_state = self.provider.default_relay_state | ||||
|         return AuthNRequest(relay_state=relay_state) | ||||
|             request.relay_state = self.provider.default_relay_state | ||||
|         if self.provider.default_name_id_policy != SAMLNameIDPolicy.UNSPECIFIED: | ||||
|             request.name_id_policy = self.provider.default_name_id_policy | ||||
|         return request | ||||
|  | ||||
| @ -13,6 +13,7 @@ from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider | ||||
| from authentik.providers.saml.utils.encoding import PEM_FOOTER, PEM_HEADER | ||||
| from authentik.sources.saml.models import SAMLNameIDPolicy | ||||
| from authentik.sources.saml.processors.constants import ( | ||||
|     NS_MAP, | ||||
|     NS_SAML_METADATA, | ||||
| @ -46,6 +47,7 @@ class ServiceProviderMetadata: | ||||
|  | ||||
|     auth_n_request_signed: bool | ||||
|     assertion_signed: bool | ||||
|     name_id_policy: SAMLNameIDPolicy | ||||
|  | ||||
|     signing_keypair: CertificateKeyPair | None = None | ||||
|  | ||||
| @ -60,6 +62,7 @@ class ServiceProviderMetadata: | ||||
|         provider.issuer = self.entity_id | ||||
|         provider.sp_binding = self.acs_binding | ||||
|         provider.acs_url = self.acs_location | ||||
|         provider.default_name_id_policy = self.name_id_policy | ||||
|         if self.signing_keypair and self.auth_n_request_signed: | ||||
|             self.signing_keypair.name = f"Provider {name} - SAML Signing Certificate" | ||||
|             self.signing_keypair.save() | ||||
| @ -148,6 +151,11 @@ class ServiceProviderMetadataParser: | ||||
|         if signing_keypair: | ||||
|             self.check_signature(root, signing_keypair) | ||||
|  | ||||
|         name_id_format = descriptor.findall(f"{{{NS_SAML_METADATA}}}NameIDFormat") | ||||
|         name_id_policy = SAMLNameIDPolicy.UNSPECIFIED | ||||
|         if len(name_id_format) > 0: | ||||
|             name_id_policy = SAMLNameIDPolicy(name_id_format[0].text) | ||||
|  | ||||
|         return ServiceProviderMetadata( | ||||
|             entity_id=entity_id, | ||||
|             acs_binding=acs_binding, | ||||
| @ -155,4 +163,5 @@ class ServiceProviderMetadataParser: | ||||
|             auth_n_request_signed=auth_n_request_signed, | ||||
|             assertion_signed=assertion_signed, | ||||
|             signing_keypair=signing_keypair, | ||||
|             name_id_policy=name_id_policy, | ||||
|         ) | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
|                      cacheDuration="PT604800S" | ||||
|                      entityID="http://localhost:8080/saml/metadata"> | ||||
|     <md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> | ||||
|         <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat> | ||||
|         <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat> | ||||
|         <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" | ||||
|                                      Location="http://localhost:8080/saml/acs" | ||||
|                                      index="1" /> | ||||
|  | ||||
| @ -14,6 +14,7 @@ from authentik.lib.xml import lxml_from_string | ||||
| from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider | ||||
| from authentik.providers.saml.processors.metadata import MetadataProcessor | ||||
| from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser | ||||
| from authentik.sources.saml.models import SAMLNameIDPolicy | ||||
| from authentik.sources.saml.processors.constants import ECDSA_SHA256, NS_MAP, NS_SAML_METADATA | ||||
|  | ||||
|  | ||||
| @ -86,6 +87,7 @@ class TestServiceProviderMetadataParser(TestCase): | ||||
|         self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs") | ||||
|         self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata") | ||||
|         self.assertEqual(provider.sp_binding, SAMLBindings.POST) | ||||
|         self.assertEqual(provider.default_name_id_policy, SAMLNameIDPolicy.EMAIL) | ||||
|         self.assertEqual( | ||||
|             len(provider.property_mappings.all()), | ||||
|             len(SAMLPropertyMapping.objects.exclude(managed__isnull=True)), | ||||
|  | ||||
| @ -166,6 +166,7 @@ SPECTACULAR_SETTINGS = { | ||||
|         "UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification", | ||||
|         "UserTypeEnum": "authentik.core.models.UserTypes", | ||||
|         "OutgoingSyncDeleteAction": "authentik.lib.sync.outgoing.models.OutgoingSyncDeleteAction", | ||||
|         "SAMLNameIDPolicyEnum": "authentik.sources.saml.models.SAMLNameIDPolicy", | ||||
|     }, | ||||
|     "ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False, | ||||
|     "ENUM_GENERATE_CHOICE_DESCRIPTION": False, | ||||
|  | ||||
| @ -0,0 +1,32 @@ | ||||
| # Generated by Django 5.1.11 on 2025-06-18 09:27 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_sources_saml", "0019_migrate_usersamlsourceconnection_identifier"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name="samlsource", | ||||
|             name="name_id_policy", | ||||
|             field=models.TextField( | ||||
|                 choices=[ | ||||
|                     ("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"), | ||||
|                     ("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"), | ||||
|                     ("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", "X509"), | ||||
|                     ( | ||||
|                         "urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName", | ||||
|                         "Windows", | ||||
|                     ), | ||||
|                     ("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"), | ||||
|                     ("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "Unspecified"), | ||||
|                 ], | ||||
|                 default="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", | ||||
|                 help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -39,6 +39,7 @@ from authentik.sources.saml.processors.constants import ( | ||||
|     SAML_NAME_ID_FORMAT_EMAIL, | ||||
|     SAML_NAME_ID_FORMAT_PERSISTENT, | ||||
|     SAML_NAME_ID_FORMAT_TRANSIENT, | ||||
|     SAML_NAME_ID_FORMAT_UNSPECIFIED, | ||||
|     SAML_NAME_ID_FORMAT_WINDOWS, | ||||
|     SAML_NAME_ID_FORMAT_X509, | ||||
|     SHA1, | ||||
| @ -73,6 +74,7 @@ class SAMLNameIDPolicy(models.TextChoices): | ||||
|     X509 = SAML_NAME_ID_FORMAT_X509 | ||||
|     WINDOWS = SAML_NAME_ID_FORMAT_WINDOWS | ||||
|     TRANSIENT = SAML_NAME_ID_FORMAT_TRANSIENT | ||||
|     UNSPECIFIED = SAML_NAME_ID_FORMAT_UNSPECIFIED | ||||
|  | ||||
|  | ||||
| class SAMLSource(Source): | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|     "$schema": "http://json-schema.org/draft-07/schema", | ||||
|     "$id": "https://goauthentik.io/blueprints/schema.json", | ||||
|     "type": "object", | ||||
|     "title": "authentik 2025.6.1 Blueprint schema", | ||||
|     "title": "authentik 2025.6.2 Blueprint schema", | ||||
|     "required": [ | ||||
|         "version", | ||||
|         "entries" | ||||
| @ -9233,6 +9233,18 @@ | ||||
|                     "type": "string", | ||||
|                     "title": "Default relay state", | ||||
|                     "description": "Default relay_state value for IDP-initiated logins" | ||||
|                 }, | ||||
|                 "default_name_id_policy": { | ||||
|                     "type": "string", | ||||
|                     "enum": [ | ||||
|                         "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", | ||||
|                         "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", | ||||
|                         "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", | ||||
|                         "urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName", | ||||
|                         "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", | ||||
|                         "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" | ||||
|                     ], | ||||
|                     "title": "Default name id policy" | ||||
|                 } | ||||
|             }, | ||||
|             "required": [] | ||||
| @ -11655,7 +11667,8 @@ | ||||
|                         "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", | ||||
|                         "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", | ||||
|                         "urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName", | ||||
|                         "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" | ||||
|                         "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", | ||||
|                         "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" | ||||
|                     ], | ||||
|                     "title": "Name id policy", | ||||
|                     "description": "NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent." | ||||
|  | ||||
| @ -31,7 +31,7 @@ services: | ||||
|     volumes: | ||||
|       - redis:/data | ||||
|   server: | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.1} | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2} | ||||
|     restart: unless-stopped | ||||
|     command: server | ||||
|     environment: | ||||
| @ -55,7 +55,7 @@ services: | ||||
|       redis: | ||||
|         condition: service_healthy | ||||
|   worker: | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.1} | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2} | ||||
|     restart: unless-stopped | ||||
|     command: worker | ||||
|     environment: | ||||
|  | ||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							| @ -18,7 +18,7 @@ require ( | ||||
| 	github.com/gorilla/sessions v1.4.0 | ||||
| 	github.com/gorilla/websocket v1.5.3 | ||||
| 	github.com/grafana/pyroscope-go v1.2.2 | ||||
| 	github.com/jellydator/ttlcache/v3 v3.3.0 | ||||
| 	github.com/jellydator/ttlcache/v3 v3.4.0 | ||||
| 	github.com/mitchellh/mapstructure v1.5.0 | ||||
| 	github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 | ||||
| 	github.com/pires/go-proxyproto v0.8.1 | ||||
| @ -29,7 +29,7 @@ require ( | ||||
| 	github.com/spf13/cobra v1.9.1 | ||||
| 	github.com/stretchr/testify v1.10.0 | ||||
| 	github.com/wwt/guac v1.3.2 | ||||
| 	goauthentik.io/api/v3 v3.2025061.2 | ||||
| 	goauthentik.io/api/v3 v3.2025062.1 | ||||
| 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | ||||
| 	golang.org/x/oauth2 v0.30.0 | ||||
| 	golang.org/x/sync v0.15.0 | ||||
|  | ||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							| @ -203,8 +203,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 | ||||
| github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= | ||||
| github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= | ||||
| github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= | ||||
| github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc= | ||||
| github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= | ||||
| github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= | ||||
| github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= | ||||
| github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= | ||||
| github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= | ||||
| github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||||
| @ -298,8 +298,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.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||||
| go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||||
| goauthentik.io/api/v3 v3.2025061.2 h1:bKmrl82Gz6J8lz3f+QIH9g+MEkl3MvkMXF34GktesA0= | ||||
| goauthentik.io/api/v3 v3.2025061.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||
| goauthentik.io/api/v3 v3.2025062.1 h1:spvILDpDDWJNO3pM6QGqmryx6NvSchr1E8H60J/XUCA= | ||||
| goauthentik.io/api/v3 v3.2025062.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= | ||||
|  | ||||
| @ -33,4 +33,4 @@ func UserAgent() string { | ||||
| 	return fmt.Sprintf("authentik@%s", FullVersion()) | ||||
| } | ||||
|  | ||||
| const VERSION = "2025.6.1" | ||||
| const VERSION = "2025.6.2" | ||||
|  | ||||
| @ -26,7 +26,7 @@ Parameters: | ||||
|     Description: authentik Docker image | ||||
|   AuthentikVersion: | ||||
|     Type: String | ||||
|     Default: 2025.6.1 | ||||
|     Default: 2025.6.2 | ||||
|     Description: authentik Docker image tag | ||||
|   AuthentikServerCPU: | ||||
|     Type: Number | ||||
|  | ||||
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,12 +1,12 @@ | ||||
| { | ||||
|     "name": "@goauthentik/authentik", | ||||
|     "version": "2025.6.1", | ||||
|     "version": "2025.6.2", | ||||
|     "lockfileVersion": 3, | ||||
|     "requires": true, | ||||
|     "packages": { | ||||
|         "": { | ||||
|             "name": "@goauthentik/authentik", | ||||
|             "version": "2025.6.1", | ||||
|             "version": "2025.6.2", | ||||
|             "devDependencies": { | ||||
|                 "@trivago/prettier-plugin-sort-imports": "^5.2.2", | ||||
|                 "prettier": "^3.3.3", | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@goauthentik/authentik", | ||||
|     "version": "2025.6.1", | ||||
|     "version": "2025.6.2", | ||||
|     "private": true, | ||||
|     "type": "module", | ||||
|     "devDependencies": { | ||||
|  | ||||
							
								
								
									
										182
									
								
								packages/eslint-config/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										182
									
								
								packages/eslint-config/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -216,9 +216,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@eslint/config-array": { | ||||
|             "version": "0.20.0", | ||||
|             "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", | ||||
|             "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", | ||||
|             "version": "0.20.1", | ||||
|             "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", | ||||
|             "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", | ||||
|             "license": "Apache-2.0", | ||||
|             "dependencies": { | ||||
|                 "@eslint/object-schema": "^2.1.6", | ||||
| @ -274,9 +274,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@eslint/js": { | ||||
|             "version": "9.28.0", | ||||
|             "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", | ||||
|             "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", | ||||
|             "version": "9.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", | ||||
|             "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -576,17 +576,17 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/eslint-plugin": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", | ||||
|             "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", | ||||
|             "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/regexpp": "^4.10.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/type-utils": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/type-utils": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "graphemer": "^1.4.0", | ||||
|                 "ignore": "^7.0.0", | ||||
|                 "natural-compare": "^1.4.0", | ||||
| @ -600,7 +600,7 @@ | ||||
|                 "url": "https://opencollective.com/typescript-eslint" | ||||
|             }, | ||||
|             "peerDependencies": { | ||||
|                 "@typescript-eslint/parser": "^8.34.0", | ||||
|                 "@typescript-eslint/parser": "^8.34.1", | ||||
|                 "eslint": "^8.57.0 || ^9.0.0", | ||||
|                 "typescript": ">=4.8.4 <5.9.0" | ||||
|             } | ||||
| @ -616,16 +616,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/parser": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", | ||||
|             "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", | ||||
|             "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -641,14 +641,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/project-service": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", | ||||
|             "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", | ||||
|             "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/tsconfig-utils": "^8.34.0", | ||||
|                 "@typescript-eslint/types": "^8.34.0", | ||||
|                 "@typescript-eslint/tsconfig-utils": "^8.34.1", | ||||
|                 "@typescript-eslint/types": "^8.34.1", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -663,14 +663,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/scope-manager": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", | ||||
|             "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", | ||||
|             "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0" | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -681,9 +681,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/tsconfig-utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -698,14 +698,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/type-utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "ts-api-utils": "^2.1.0" | ||||
|             }, | ||||
| @ -722,9 +722,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/types": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", | ||||
|             "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", | ||||
|             "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -736,16 +736,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", | ||||
|             "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", | ||||
|             "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/project-service": "8.34.0", | ||||
|                 "@typescript-eslint/tsconfig-utils": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/project-service": "8.34.1", | ||||
|                 "@typescript-eslint/tsconfig-utils": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "fast-glob": "^3.3.2", | ||||
|                 "is-glob": "^4.0.3", | ||||
| @ -765,9 +765,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { | ||||
|             "version": "2.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", | ||||
|             "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", | ||||
|             "version": "2.0.2", | ||||
|             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", | ||||
|             "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
| @ -804,16 +804,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/eslint-utils": "^4.7.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0" | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -828,14 +828,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/visitor-keys": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", | ||||
|             "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", | ||||
|             "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "eslint-visitor-keys": "^4.2.0" | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "eslint-visitor-keys": "^4.2.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -846,9 +846,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/acorn": { | ||||
|             "version": "8.14.1", | ||||
|             "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", | ||||
|             "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", | ||||
|             "version": "8.15.0", | ||||
|             "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", | ||||
|             "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", | ||||
|             "license": "MIT", | ||||
|             "bin": { | ||||
|                 "acorn": "bin/acorn" | ||||
| @ -1554,18 +1554,18 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/eslint": { | ||||
|             "version": "9.28.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", | ||||
|             "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", | ||||
|             "version": "9.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", | ||||
|             "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/eslint-utils": "^4.2.0", | ||||
|                 "@eslint-community/regexpp": "^4.12.1", | ||||
|                 "@eslint/config-array": "^0.20.0", | ||||
|                 "@eslint/config-array": "^0.20.1", | ||||
|                 "@eslint/config-helpers": "^0.2.1", | ||||
|                 "@eslint/core": "^0.14.0", | ||||
|                 "@eslint/eslintrc": "^3.3.1", | ||||
|                 "@eslint/js": "9.28.0", | ||||
|                 "@eslint/js": "9.29.0", | ||||
|                 "@eslint/plugin-kit": "^0.3.1", | ||||
|                 "@humanfs/node": "^0.16.6", | ||||
|                 "@humanwhocodes/module-importer": "^1.0.1", | ||||
| @ -1577,9 +1577,9 @@ | ||||
|                 "cross-spawn": "^7.0.6", | ||||
|                 "debug": "^4.3.2", | ||||
|                 "escape-string-regexp": "^4.0.0", | ||||
|                 "eslint-scope": "^8.3.0", | ||||
|                 "eslint-visitor-keys": "^4.2.0", | ||||
|                 "espree": "^10.3.0", | ||||
|                 "eslint-scope": "^8.4.0", | ||||
|                 "eslint-visitor-keys": "^4.2.1", | ||||
|                 "espree": "^10.4.0", | ||||
|                 "esquery": "^1.5.0", | ||||
|                 "esutils": "^2.0.2", | ||||
|                 "fast-deep-equal": "^3.1.3", | ||||
| @ -1792,9 +1792,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/eslint-scope": { | ||||
|             "version": "8.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", | ||||
|             "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", | ||||
|             "version": "8.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", | ||||
|             "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", | ||||
|             "license": "BSD-2-Clause", | ||||
|             "dependencies": { | ||||
|                 "esrecurse": "^4.3.0", | ||||
| @ -1808,9 +1808,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/eslint-visitor-keys": { | ||||
|             "version": "4.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", | ||||
|             "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", | ||||
|             "version": "4.2.1", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", | ||||
|             "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", | ||||
|             "license": "Apache-2.0", | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -1820,14 +1820,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/espree": { | ||||
|             "version": "10.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", | ||||
|             "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", | ||||
|             "version": "10.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", | ||||
|             "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", | ||||
|             "license": "BSD-2-Clause", | ||||
|             "dependencies": { | ||||
|                 "acorn": "^8.14.0", | ||||
|                 "acorn": "^8.15.0", | ||||
|                 "acorn-jsx": "^5.3.2", | ||||
|                 "eslint-visitor-keys": "^4.2.0" | ||||
|                 "eslint-visitor-keys": "^4.2.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -4035,15 +4035,15 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/typescript-eslint": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", | ||||
|             "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", | ||||
|             "integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/eslint-plugin": "8.34.0", | ||||
|                 "@typescript-eslint/parser": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0" | ||||
|                 "@typescript-eslint/eslint-plugin": "8.34.1", | ||||
|                 "@typescript-eslint/parser": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| [project] | ||||
| name = "authentik" | ||||
| version = "2025.6.1" | ||||
| version = "2025.6.2" | ||||
| description = "" | ||||
| authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }] | ||||
| requires-python = "==3.13.*" | ||||
| @ -48,7 +48,7 @@ dependencies = [ | ||||
|     "packaging==25.0", | ||||
|     "paramiko==3.5.1", | ||||
|     "psycopg[c,pool]==3.2.9", | ||||
|     "pydantic==2.11.5", | ||||
|     "pydantic==2.11.7", | ||||
|     "pydantic-scim==0.0.8", | ||||
|     "pyjwt==2.10.1", | ||||
|     "pyrad==2.4", | ||||
| @ -68,7 +68,7 @@ dependencies = [ | ||||
|     "urllib3<3", | ||||
|     "uvicorn[standard]==0.34.3", | ||||
|     "watchdog==6.0.0", | ||||
|     "webauthn==2.5.2", | ||||
|     "webauthn==2.6.0", | ||||
|     "wsproto==1.2.0", | ||||
|     "xmlsec==1.3.15", | ||||
|     "zxcvbn==4.5.0", | ||||
|  | ||||
							
								
								
									
										43
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								schema.yml
									
									
									
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| openapi: 3.0.3 | ||||
| info: | ||||
|   title: authentik | ||||
|   version: 2025.6.1 | ||||
|   version: 2025.6.2 | ||||
|   description: Making authentication simple. | ||||
|   contact: | ||||
|     email: hello@goauthentik.io | ||||
| @ -22454,6 +22454,17 @@ paths: | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|       - in: query | ||||
|         name: default_name_id_policy | ||||
|         schema: | ||||
|           type: string | ||||
|           enum: | ||||
|           - urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName | ||||
|           - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress | ||||
|           - urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified | ||||
|           - urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName | ||||
|           - urn:oasis:names:tc:SAML:2.0:nameid-format:persistent | ||||
|           - urn:oasis:names:tc:SAML:2.0:nameid-format:transient | ||||
|       - in: query | ||||
|         name: default_relay_state | ||||
|         schema: | ||||
| @ -29670,6 +29681,7 @@ paths: | ||||
|           enum: | ||||
|           - urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName | ||||
|           - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress | ||||
|           - urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified | ||||
|           - urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName | ||||
|           - urn:oasis:names:tc:SAML:2.0:nameid-format:persistent | ||||
|           - urn:oasis:names:tc:SAML:2.0:nameid-format:transient | ||||
| @ -48745,14 +48757,6 @@ components: | ||||
|       - mode | ||||
|       - name | ||||
|       - user_attribute | ||||
|     NameIdPolicyEnum: | ||||
|       enum: | ||||
|       - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress | ||||
|       - urn:oasis:names:tc:SAML:2.0:nameid-format:persistent | ||||
|       - urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName | ||||
|       - urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName | ||||
|       - urn:oasis:names:tc:SAML:2.0:nameid-format:transient | ||||
|       type: string | ||||
|     NetworkBindingEnum: | ||||
|       enum: | ||||
|       - no_binding | ||||
| @ -54501,6 +54505,8 @@ components: | ||||
|         default_relay_state: | ||||
|           type: string | ||||
|           description: Default relay_state value for IDP-initiated logins | ||||
|         default_name_id_policy: | ||||
|           $ref: '#/components/schemas/SAMLNameIDPolicyEnum' | ||||
|     PatchedSAMLSourcePropertyMappingRequest: | ||||
|       type: object | ||||
|       description: SAMLSourcePropertyMapping Serializer | ||||
| @ -54594,7 +54600,7 @@ components: | ||||
|             be a security risk, as no validation of the request ID is done. | ||||
|         name_id_policy: | ||||
|           allOf: | ||||
|           - $ref: '#/components/schemas/NameIdPolicyEnum' | ||||
|           - $ref: '#/components/schemas/SAMLNameIDPolicyEnum' | ||||
|           description: NameID Policy sent to the IdP. Can be unset, in which case | ||||
|             no Policy is sent. | ||||
|         binding_type: | ||||
| @ -57305,6 +57311,15 @@ components: | ||||
|       required: | ||||
|       - download_url | ||||
|       - metadata | ||||
|     SAMLNameIDPolicyEnum: | ||||
|       enum: | ||||
|       - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress | ||||
|       - urn:oasis:names:tc:SAML:2.0:nameid-format:persistent | ||||
|       - urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName | ||||
|       - urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName | ||||
|       - urn:oasis:names:tc:SAML:2.0:nameid-format:transient | ||||
|       - urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified | ||||
|       type: string | ||||
|     SAMLPropertyMapping: | ||||
|       type: object | ||||
|       description: SAMLPropertyMapping Serializer | ||||
| @ -57522,6 +57537,8 @@ components: | ||||
|         default_relay_state: | ||||
|           type: string | ||||
|           description: Default relay_state value for IDP-initiated logins | ||||
|         default_name_id_policy: | ||||
|           $ref: '#/components/schemas/SAMLNameIDPolicyEnum' | ||||
|         url_download_metadata: | ||||
|           type: string | ||||
|           description: Get metadata download URL | ||||
| @ -57694,6 +57711,8 @@ components: | ||||
|         default_relay_state: | ||||
|           type: string | ||||
|           description: Default relay_state value for IDP-initiated logins | ||||
|         default_name_id_policy: | ||||
|           $ref: '#/components/schemas/SAMLNameIDPolicyEnum' | ||||
|       required: | ||||
|       - acs_url | ||||
|       - authorization_flow | ||||
| @ -57802,7 +57821,7 @@ components: | ||||
|             be a security risk, as no validation of the request ID is done. | ||||
|         name_id_policy: | ||||
|           allOf: | ||||
|           - $ref: '#/components/schemas/NameIdPolicyEnum' | ||||
|           - $ref: '#/components/schemas/SAMLNameIDPolicyEnum' | ||||
|           description: NameID Policy sent to the IdP. Can be unset, in which case | ||||
|             no Policy is sent. | ||||
|         binding_type: | ||||
| @ -57992,7 +58011,7 @@ components: | ||||
|             be a security risk, as no validation of the request ID is done. | ||||
|         name_id_policy: | ||||
|           allOf: | ||||
|           - $ref: '#/components/schemas/NameIdPolicyEnum' | ||||
|           - $ref: '#/components/schemas/SAMLNameIDPolicyEnum' | ||||
|           description: NameID Policy sent to the IdP. Can be unset, in which case | ||||
|             no Policy is sent. | ||||
|         binding_type: | ||||
|  | ||||
| @ -7,7 +7,7 @@ services: | ||||
|     network_mode: host | ||||
|     restart: always | ||||
|   mailpit: | ||||
|     image: docker.io/axllent/mailpit:v1.26.0 | ||||
|     image: docker.io/axllent/mailpit:v1.26.1 | ||||
|     ports: | ||||
|       - 1025:1025 | ||||
|       - 8025:8025 | ||||
|  | ||||
							
								
								
									
										18
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @ -165,7 +165,7 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "authentik" | ||||
| version = "2025.6.1" | ||||
| version = "2025.6.2" | ||||
| source = { editable = "." } | ||||
| dependencies = [ | ||||
|     { name = "argon2-cffi" }, | ||||
| @ -309,7 +309,7 @@ requires-dist = [ | ||||
|     { name = "packaging", specifier = "==25.0" }, | ||||
|     { name = "paramiko", specifier = "==3.5.1" }, | ||||
|     { name = "psycopg", extras = ["c", "pool"], specifier = "==3.2.9" }, | ||||
|     { name = "pydantic", specifier = "==2.11.5" }, | ||||
|     { name = "pydantic", specifier = "==2.11.7" }, | ||||
|     { name = "pydantic-scim", specifier = "==0.0.8" }, | ||||
|     { name = "pyjwt", specifier = "==2.10.1" }, | ||||
|     { name = "pyrad", specifier = "==2.4" }, | ||||
| @ -329,7 +329,7 @@ requires-dist = [ | ||||
|     { name = "urllib3", specifier = "<3" }, | ||||
|     { name = "uvicorn", extras = ["standard"], specifier = "==0.34.3" }, | ||||
|     { name = "watchdog", specifier = "==6.0.0" }, | ||||
|     { name = "webauthn", specifier = "==2.5.2" }, | ||||
|     { name = "webauthn", specifier = "==2.6.0" }, | ||||
|     { name = "wsproto", specifier = "==1.2.0" }, | ||||
|     { name = "xmlsec", specifier = "==1.3.15" }, | ||||
|     { name = "zxcvbn", specifier = "==4.5.0" }, | ||||
| @ -2463,7 +2463,7 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "pydantic" | ||||
| version = "2.11.5" | ||||
| version = "2.11.7" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "annotated-types" }, | ||||
| @ -2471,9 +2471,9 @@ dependencies = [ | ||||
|     { name = "typing-extensions" }, | ||||
|     { name = "typing-inspection" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, | ||||
| ] | ||||
|  | ||||
| [package.optional-dependencies] | ||||
| @ -3391,7 +3391,7 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "webauthn" | ||||
| version = "2.5.2" | ||||
| version = "2.6.0" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "asn1crypto" }, | ||||
| @ -3399,9 +3399,9 @@ dependencies = [ | ||||
|     { name = "cryptography" }, | ||||
|     { name = "pyopenssl" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/8d/92/8d2a4eec83d8e7feacdaad37c6eb6eb922100cecce5c14a41d8069a59a03/webauthn-2.5.2.tar.gz", hash = "sha256:09c13dfc1c68c810f32fa4d89b1d37acb9f9ae9091c9d7019e313be4525a95ef", size = 124114, upload-time = "2025-03-07T19:44:05.243Z" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/63/38/5792cb2034673c162a721df0ad65825699516ee0c938a65670ad3cdabf6c/webauthn-2.6.0.tar.gz", hash = "sha256:13cf5b009a64cef569599ffecf24550df1d7c0cd4fbaea870f937148484a80b4", size = 123608, upload-time = "2025-06-16T22:25:26.76Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/7f/fe/f6ae41de9f383439e30b303a67f6f45d2fceabedaedc34c62f74d58c5c73/webauthn-2.5.2-py3-none-any.whl", hash = "sha256:44246e496e617eb5e2f51165046b9f0197fcdf470f69cd6734061a27ba365f8e", size = 71624, upload-time = "2025-03-07T19:44:03.728Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/56/c5/b1bba7f6a50caca77f37003e098f48f8dc68d990aba8a03ac8376016430b/webauthn-2.6.0-py3-none-any.whl", hash = "sha256:459973eb5780c1f41bec42b682acf587456b185733398a0b99a0714705b79447", size = 71189, upload-time = "2025-06-16T22:25:25.535Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
|  | ||||
							
								
								
									
										1703
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1703
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -93,7 +93,7 @@ | ||||
|         "@floating-ui/dom": "^1.6.11", | ||||
|         "@formatjs/intl-listformat": "^7.7.11", | ||||
|         "@fortawesome/fontawesome-free": "^6.7.2", | ||||
|         "@goauthentik/api": "^2025.6.1-1749515784", | ||||
|         "@goauthentik/api": "^2025.6.2-1750112513", | ||||
|         "@lit/context": "^1.1.2", | ||||
|         "@lit/localize": "^0.12.2", | ||||
|         "@lit/reactive-element": "^2.0.4", | ||||
| @ -102,10 +102,9 @@ | ||||
|         "@open-wc/lit-helpers": "^0.7.0", | ||||
|         "@patternfly/elements": "^4.1.0", | ||||
|         "@patternfly/patternfly": "^4.224.2", | ||||
|         "@sentry/browser": "^9.28.1", | ||||
|         "@sentry/browser": "^9.30.0", | ||||
|         "@spotlightjs/spotlight": "^3.0.0", | ||||
|         "@webcomponents/webcomponentsjs": "^2.8.0", | ||||
|         "base64-js": "^1.5.1", | ||||
|         "change-case": "^5.4.4", | ||||
|         "chart.js": "^4.4.9", | ||||
|         "chartjs-adapter-date-fns": "^3.0.0", | ||||
| @ -137,6 +136,7 @@ | ||||
|         "trusted-types": "^2.0.0", | ||||
|         "ts-pattern": "^5.7.1", | ||||
|         "unist-util-visit": "^5.0.0", | ||||
|         "webauthn-polyfills": "^0.1.7", | ||||
|         "webcomponent-qr-code": "^1.2.0", | ||||
|         "yaml": "^2.8.0" | ||||
|     }, | ||||
| @ -170,16 +170,16 @@ | ||||
|         "@types/react-dom": "^19.1.5", | ||||
|         "@typescript-eslint/eslint-plugin": "^8.8.0", | ||||
|         "@typescript-eslint/parser": "^8.8.0", | ||||
|         "@wdio/browser-runner": "9.4", | ||||
|         "@wdio/cli": "9.4", | ||||
|         "@wdio/spec-reporter": "^9.1.2", | ||||
|         "@wdio/browser-runner": "9.15", | ||||
|         "@wdio/cli": "9.15", | ||||
|         "@wdio/spec-reporter": "^9.15.0", | ||||
|         "@web/test-runner": "^0.20.2", | ||||
|         "chromedriver": "^136.0.3", | ||||
|         "esbuild": "^0.25.5", | ||||
|         "esbuild-plugin-copy": "^2.1.1", | ||||
|         "esbuild-plugin-polyfill-node": "^0.3.0", | ||||
|         "esbuild-plugins-node-modules-polyfill": "^1.7.0", | ||||
|         "eslint": "^9.28.0", | ||||
|         "eslint": "^9.29.0", | ||||
|         "eslint-plugin-lit": "^2.1.1", | ||||
|         "eslint-plugin-wc": "^3.0.1", | ||||
|         "github-slugger": "^2.0.0", | ||||
| @ -194,7 +194,7 @@ | ||||
|         "storybook-addon-mock": "^5.0.0", | ||||
|         "turnstile-types": "^1.2.3", | ||||
|         "typescript": "^5.8.3", | ||||
|         "typescript-eslint": "^8.34.0", | ||||
|         "typescript-eslint": "^8.34.1", | ||||
|         "vite-plugin-lit-css": "^2.0.0", | ||||
|         "vite-tsconfig-paths": "^5.0.1", | ||||
|         "wireit": "^0.14.12" | ||||
|  | ||||
							
								
								
									
										2
									
								
								web/packages/core/types/node.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								web/packages/core/types/node.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -14,7 +14,7 @@ declare module "module" { | ||||
|          * const relativeDirname = dirname(fileURLToPath(import.meta.url)); | ||||
|          * ``` | ||||
|          */ | ||||
|         // eslint-disable-next-line no-var | ||||
|  | ||||
|         var __dirname: string; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -11,11 +11,11 @@ | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@goauthentik/api": "^2024.6.0-1719577139", | ||||
|         "base64-js": "^1.5.1", | ||||
|         "bootstrap": "^4.6.1", | ||||
|         "formdata-polyfill": "^4.0.10", | ||||
|         "jquery": "^3.7.1", | ||||
|         "weakmap-polyfill": "^2.0.4" | ||||
|         "weakmap-polyfill": "^2.0.4", | ||||
|         "webauthn-polyfills": "^0.1.7" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@goauthentik/core": "^1.0.0", | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { fromByteArray } from "base64-js"; | ||||
| import "formdata-polyfill"; | ||||
| import $ from "jquery"; | ||||
| import "weakmap-polyfill"; | ||||
| import "webauthn-polyfills"; | ||||
|  | ||||
| import { | ||||
|     type AuthenticatorValidationChallenge, | ||||
| @ -257,47 +257,9 @@ class AutosubmitStage extends Stage<AutosubmitChallenge> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| export interface Assertion { | ||||
|     id: string; | ||||
|     rawId: string; | ||||
|     type: string; | ||||
|     registrationClientExtensions: string; | ||||
|     response: { | ||||
|         clientDataJSON: string; | ||||
|         attestationObject: string; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export interface AuthAssertion { | ||||
|     id: string; | ||||
|     rawId: string; | ||||
|     type: string; | ||||
|     assertionClientExtensions: string; | ||||
|     response: { | ||||
|         clientDataJSON: string; | ||||
|         authenticatorData: string; | ||||
|         signature: string; | ||||
|         userHandle: string | null; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge> { | ||||
|     deviceChallenge?: DeviceChallenge; | ||||
|  | ||||
|     b64enc(buf: Uint8Array): string { | ||||
|         return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); | ||||
|     } | ||||
|  | ||||
|     b64RawEnc(buf: Uint8Array): string { | ||||
|         return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_"); | ||||
|     } | ||||
|  | ||||
|     u8arr(input: string): Uint8Array { | ||||
|         return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) => | ||||
|             c.charCodeAt(0), | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     checkWebAuthnSupport(): boolean { | ||||
|         if ("credentials" in navigator) { | ||||
|             return true; | ||||
| @ -310,98 +272,6 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge> | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Transforms items in the credentialCreateOptions generated on the server | ||||
|      * into byte arrays expected by the navigator.credentials.create() call | ||||
|      */ | ||||
|     transformCredentialCreateOptions( | ||||
|         credentialCreateOptions: PublicKeyCredentialCreationOptions, | ||||
|         userId: string, | ||||
|     ): PublicKeyCredentialCreationOptions { | ||||
|         const user = credentialCreateOptions.user; | ||||
|         // Because json can't contain raw bytes, the server base64-encodes the User ID | ||||
|         // So to get the base64 encoded byte array, we first need to convert it to a regular | ||||
|         // string, then a byte array, re-encode it and wrap that in an array. | ||||
|         const stringId = decodeURIComponent(window.atob(userId)); | ||||
|         user.id = this.u8arr(this.b64enc(this.u8arr(stringId))); | ||||
|         const challenge = this.u8arr(credentialCreateOptions.challenge.toString()); | ||||
|  | ||||
|         return Object.assign({}, credentialCreateOptions, { | ||||
|             challenge, | ||||
|             user, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Transforms the binary data in the credential into base64 strings | ||||
|      * for posting to the server. | ||||
|      * @param {PublicKeyCredential} newAssertion | ||||
|      */ | ||||
|     transformNewAssertionForServer(newAssertion: PublicKeyCredential): Assertion { | ||||
|         const attObj = new Uint8Array( | ||||
|             (newAssertion.response as AuthenticatorAttestationResponse).attestationObject, | ||||
|         ); | ||||
|         const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON); | ||||
|         const rawId = new Uint8Array(newAssertion.rawId); | ||||
|  | ||||
|         const registrationClientExtensions = newAssertion.getClientExtensionResults(); | ||||
|         return { | ||||
|             id: newAssertion.id, | ||||
|             rawId: this.b64enc(rawId), | ||||
|             type: newAssertion.type, | ||||
|             registrationClientExtensions: JSON.stringify(registrationClientExtensions), | ||||
|             response: { | ||||
|                 clientDataJSON: this.b64enc(clientDataJSON), | ||||
|                 attestationObject: this.b64enc(attObj), | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     transformCredentialRequestOptions( | ||||
|         credentialRequestOptions: PublicKeyCredentialRequestOptions, | ||||
|     ): PublicKeyCredentialRequestOptions { | ||||
|         const challenge = this.u8arr(credentialRequestOptions.challenge.toString()); | ||||
|  | ||||
|         const allowCredentials = (credentialRequestOptions.allowCredentials || []).map( | ||||
|             (credentialDescriptor) => { | ||||
|                 const id = this.u8arr(credentialDescriptor.id.toString()); | ||||
|                 return Object.assign({}, credentialDescriptor, { id }); | ||||
|             }, | ||||
|         ); | ||||
|  | ||||
|         return Object.assign({}, credentialRequestOptions, { | ||||
|             challenge, | ||||
|             allowCredentials, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Encodes the binary data in the assertion into strings for posting to the server. | ||||
|      * @param {PublicKeyCredential} newAssertion | ||||
|      */ | ||||
|     transformAssertionForServer(newAssertion: PublicKeyCredential): AuthAssertion { | ||||
|         const response = newAssertion.response as AuthenticatorAssertionResponse; | ||||
|         const authData = new Uint8Array(response.authenticatorData); | ||||
|         const clientDataJSON = new Uint8Array(response.clientDataJSON); | ||||
|         const rawId = new Uint8Array(newAssertion.rawId); | ||||
|         const sig = new Uint8Array(response.signature); | ||||
|         const assertionClientExtensions = newAssertion.getClientExtensionResults(); | ||||
|  | ||||
|         return { | ||||
|             id: newAssertion.id, | ||||
|             rawId: this.b64enc(rawId), | ||||
|             type: newAssertion.type, | ||||
|             assertionClientExtensions: JSON.stringify(assertionClientExtensions), | ||||
|  | ||||
|             response: { | ||||
|                 clientDataJSON: this.b64RawEnc(clientDataJSON), | ||||
|                 signature: this.b64RawEnc(sig), | ||||
|                 authenticatorData: this.b64RawEnc(authData), | ||||
|                 userHandle: null, | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         if (this.challenge.deviceChallenges.length === 1) { | ||||
|             this.deviceChallenge = this.challenge.deviceChallenges[0]; | ||||
| @ -505,8 +375,8 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge> | ||||
|             `); | ||||
|         navigator.credentials | ||||
|             .get({ | ||||
|                 publicKey: this.transformCredentialRequestOptions( | ||||
|                     this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptions, | ||||
|                 publicKey: PublicKeyCredential.parseRequestOptionsFromJSON( | ||||
|                     this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptionsJSON, | ||||
|                 ), | ||||
|             }) | ||||
|             .then((assertion) => { | ||||
| @ -514,15 +384,9 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge> | ||||
|                     throw new Error("No assertion"); | ||||
|                 } | ||||
|                 try { | ||||
|                     // we now have an authentication assertion! encode the byte arrays contained | ||||
|                     // in the assertion data as strings for posting to the server | ||||
|                     const transformedAssertionForServer = this.transformAssertionForServer( | ||||
|                         assertion as PublicKeyCredential, | ||||
|                     ); | ||||
|  | ||||
|                     // post the assertion to the server for verification. | ||||
|                     this.executor.submit({ | ||||
|                         webauthn: transformedAssertionForServer, | ||||
|                         webauthn: (assertion as PublicKeyCredential).toJSON(), | ||||
|                     }); | ||||
|                 } catch (err) { | ||||
|                     throw new Error(`Error when validating assertion on server: ${err}`); | ||||
|  | ||||
| @ -88,7 +88,8 @@ export class RecentEventsCard extends Table<Event> { | ||||
|         } | ||||
|  | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No Events found.")}> | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No Events found.")}</span> | ||||
|                 <div slot="body">${msg("No matching events could be found.")}</div> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
| @ -5,6 +5,7 @@ import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-file-input"; | ||||
| import "@goauthentik/components/ak-radio-input"; | ||||
| import "@goauthentik/components/ak-slug-input"; | ||||
| import "@goauthentik/components/ak-switch-input"; | ||||
| import "@goauthentik/components/ak-text-input"; | ||||
| import "@goauthentik/components/ak-textarea-input"; | ||||
| @ -130,14 +131,14 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio | ||||
|                 required | ||||
|                 help=${msg("Application's display Name.")} | ||||
|             ></ak-text-input> | ||||
|             <ak-text-input | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 help=${msg("Internal application name used in URLs.")} | ||||
|                 input-hint="code" | ||||
|             ></ak-text-input> | ||||
|             ></ak-slug-input> | ||||
|             <ak-text-input | ||||
|                 name="group" | ||||
|                 value=${ifDefined(this.instance?.group)} | ||||
|  | ||||
| @ -117,13 +117,11 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep { | ||||
|                     ?invalid=${this.errors.has("name")} | ||||
|                     .errorMessages=${errors.name ?? this.errorMessages("name")} | ||||
|                     help=${msg("Application's display Name.")} | ||||
|                     id="ak-application-wizard-details-name" | ||||
|                 ></ak-text-input> | ||||
|                 <ak-slug-input | ||||
|                     name="slug" | ||||
|                     value=${ifDefined(app.slug)} | ||||
|                     label=${msg("Slug")} | ||||
|                     source="#ak-application-wizard-details-name" | ||||
|                     required | ||||
|                     ?invalid=${errors.slug ?? this.errors.has("slug")} | ||||
|                     .errorMessages=${this.errorMessages("slug")} | ||||
|  | ||||
| @ -115,7 +115,8 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep { | ||||
|                     .columns=${COLUMNS} | ||||
|                     .content=${[]} | ||||
|                 ></ak-select-table> | ||||
|                 <ak-empty-state header=${msg("No bound policies.")} icon="pf-icon-module"> | ||||
|                 <ak-empty-state icon="pf-icon-module" | ||||
|                     ><span slot="header">${msg("No bound policies.")} </span> | ||||
|                     <div slot="body">${msg("No policies are currently bound to this object.")}</div> | ||||
|                     <div slot="primary"> | ||||
|                         <button | ||||
|  | ||||
| @ -135,7 +135,8 @@ export class BoundStagesList extends Table<FlowStageBinding> { | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No Stages bound")} icon="pf-icon-module"> | ||||
|             html`<ak-empty-state icon="pf-icon-module"> | ||||
|                 <span slot="header">${msg("No Stages bound")}</span> | ||||
|                 <div slot="body">${msg("No stages are currently bound to this flow.")}</div> | ||||
|                 <div slot="primary"> | ||||
|                     <ak-stage-wizard | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/util | ||||
| import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes"; | ||||
| import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/forms/FormGroup"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; | ||||
| @ -91,17 +92,16 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) { | ||||
|                 /> | ||||
|                 <p class="pf-c-form__helper-text">${msg("Shown as the Title in Flow pages.")}</p> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control pf-m-monospace" | ||||
|                     autocomplete="off" | ||||
|                     spellcheck="false" | ||||
|                     required | ||||
|                 /> | ||||
|                 <p class="pf-c-form__helper-text">${msg("Visible in the URL.")}</p> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 help=${msg("Visible in the URL.")} | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|  | ||||
|             <ak-form-element-horizontal label=${msg("Designation")} required name="designation"> | ||||
|                 <select class="pf-c-form-control"> | ||||
|                     <option value="" ?selected=${this.instance?.designation === undefined}> | ||||
|  | ||||
| @ -198,7 +198,8 @@ export class BoundPoliciesList extends Table<PolicyBinding> { | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No Policies bound.")} icon="pf-icon-module"> | ||||
|             html`<ak-empty-state icon="pf-icon-module" | ||||
|                 ><span slot="header">${msg("No Policies bound.")}</span> | ||||
|                 <div slot="body">${msg("No policies are currently bound to this object.")}</div> | ||||
|                 <div slot="primary"> | ||||
|                     <ak-policy-wizard | ||||
|  | ||||
| @ -16,6 +16,7 @@ import { | ||||
|     FlowsInstancesListDesignationEnum, | ||||
|     PropertymappingsApi, | ||||
|     PropertymappingsProviderSamlListRequest, | ||||
|     SAMLNameIDPolicyEnum, | ||||
|     SAMLPropertyMapping, | ||||
|     SAMLProvider, | ||||
|     SpBindingEnum, | ||||
| @ -316,6 +317,54 @@ export function renderForm( | ||||
|                         "When using IDP-initiated logins, the relay state will be set to this value.", | ||||
|                     )} | ||||
|                 ></ak-text-input> | ||||
|                 <ak-form-element-horizontal | ||||
|                     label=${msg("Default NameID Policy")} | ||||
|                     required | ||||
|                     name="defaultNameIdPolicy" | ||||
|                 > | ||||
|                     <select class="pf-c-form-control"> | ||||
|                         <option | ||||
|                             value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent} | ||||
|                             ?selected=${provider?.defaultNameIdPolicy === | ||||
|                             SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent} | ||||
|                         > | ||||
|                             ${msg("Persistent")} | ||||
|                         </option> | ||||
|                         <option | ||||
|                             value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress} | ||||
|                             ?selected=${provider?.defaultNameIdPolicy === | ||||
|                             SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress} | ||||
|                         > | ||||
|                             ${msg("Email address")} | ||||
|                         </option> | ||||
|                         <option | ||||
|                             value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName} | ||||
|                             ?selected=${provider?.defaultNameIdPolicy === | ||||
|                             SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName} | ||||
|                         > | ||||
|                             ${msg("Windows")} | ||||
|                         </option> | ||||
|                         <option | ||||
|                             value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName} | ||||
|                             ?selected=${provider?.defaultNameIdPolicy === | ||||
|                             SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName} | ||||
|                         > | ||||
|                             ${msg("X509 Subject")} | ||||
|                         </option> | ||||
|                         <option | ||||
|                             value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient} | ||||
|                             ?selected=${provider?.defaultNameIdPolicy === | ||||
|                             SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient} | ||||
|                         > | ||||
|                             ${msg("Transient")} | ||||
|                         </option> | ||||
|                     </select> | ||||
|                     <p class="pf-c-form__helper-text"> | ||||
|                         ${msg( | ||||
|                             "Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).", | ||||
|                         )} | ||||
|                     </p> | ||||
|                 </ak-form-element-horizontal> | ||||
|  | ||||
|                 <ak-radio-input | ||||
|                     name="digestAlgorithm" | ||||
|  | ||||
| @ -9,6 +9,7 @@ import { | ||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-secret-text-input.js"; | ||||
| import "@goauthentik/components/ak-secret-textarea-input.js"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/components/ak-switch-input"; | ||||
| import "@goauthentik/components/ak-text-input"; | ||||
| import "@goauthentik/components/ak-textarea-input"; | ||||
| @ -87,12 +88,13 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke | ||||
|                 value=${ifDefined(this.instance?.name)} | ||||
|                 required | ||||
|             ></ak-text-input> | ||||
|             <ak-text-input | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 label=${msg("Slug")} | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|             ></ak-text-input> | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|             <ak-switch-input | ||||
|                 name="enabled" | ||||
|                 ?checked=${this.instance?.enabled ?? true} | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { placeholderHelperText } from "@goauthentik/admin/helperText"; | ||||
| import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-secret-text-input.js"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| import "@goauthentik/elements/forms/FormGroup"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| @ -54,14 +55,15 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> { | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|  | ||||
|             <ak-form-element-horizontal name="enabled"> | ||||
|                 <label class="pf-c-switch"> | ||||
|                     <input | ||||
|  | ||||
| @ -10,6 +10,7 @@ import { | ||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-radio-input"; | ||||
| import "@goauthentik/components/ak-secret-textarea-input.js"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/CodeMirror"; | ||||
| import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| @ -267,16 +268,13 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control pf-m-monospace" | ||||
|                     autocomplete="off" | ||||
|                     spellcheck="false" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|             <ak-form-element-horizontal name="enabled"> | ||||
|                 <label class="pf-c-switch"> | ||||
|                     <input | ||||
|  | ||||
| @ -10,6 +10,7 @@ import { | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex"; | ||||
| import { ascii_letters, digits, randomString } from "@goauthentik/common/utils"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js"; | ||||
| @ -183,14 +184,15 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|  | ||||
|             <ak-form-element-horizontal name="enabled"> | ||||
|                 <label class="pf-c-switch"> | ||||
|                     <input | ||||
|  | ||||
| @ -9,6 +9,7 @@ import { | ||||
|     UserMatchingModeToLabel, | ||||
| } from "@goauthentik/admin/sources/oauth/utils"; | ||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| import "@goauthentik/elements/forms/FormGroup"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| @ -25,7 +26,7 @@ import { | ||||
|     DigestAlgorithmEnum, | ||||
|     FlowsInstancesListDesignationEnum, | ||||
|     GroupMatchingModeEnum, | ||||
|     NameIdPolicyEnum, | ||||
|     SAMLNameIDPolicyEnum, | ||||
|     SAMLSource, | ||||
|     SignatureAlgorithmEnum, | ||||
|     SourcesApi, | ||||
| @ -89,14 +90,15 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|  | ||||
|             <ak-form-element-horizontal name="enabled"> | ||||
|                 <label class="pf-c-switch"> | ||||
|                     <input | ||||
| @ -351,37 +353,37 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo | ||||
|                     > | ||||
|                         <select class="pf-c-form-control"> | ||||
|                             <option | ||||
|                                 value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent} | ||||
|                                 value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent} | ||||
|                                 ?selected=${this.instance?.nameIdPolicy === | ||||
|                                 NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent} | ||||
|                                 SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent} | ||||
|                             > | ||||
|                                 ${msg("Persistent")} | ||||
|                             </option> | ||||
|                             <option | ||||
|                                 value=${NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress} | ||||
|                                 value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress} | ||||
|                                 ?selected=${this.instance?.nameIdPolicy === | ||||
|                                 NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress} | ||||
|                                 SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress} | ||||
|                             > | ||||
|                                 ${msg("Email address")} | ||||
|                             </option> | ||||
|                             <option | ||||
|                                 value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName} | ||||
|                                 value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName} | ||||
|                                 ?selected=${this.instance?.nameIdPolicy === | ||||
|                                 NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName} | ||||
|                                 SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName} | ||||
|                             > | ||||
|                                 ${msg("Windows")} | ||||
|                             </option> | ||||
|                             <option | ||||
|                                 value=${NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName} | ||||
|                                 value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName} | ||||
|                                 ?selected=${this.instance?.nameIdPolicy === | ||||
|                                 NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName} | ||||
|                                 SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName} | ||||
|                             > | ||||
|                                 ${msg("X509 Subject")} | ||||
|                             </option> | ||||
|                             <option | ||||
|                                 value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient} | ||||
|                                 value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient} | ||||
|                                 ?selected=${this.instance?.nameIdPolicy === | ||||
|                                 NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient} | ||||
|                                 SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient} | ||||
|                             > | ||||
|                                 ${msg("Transient")} | ||||
|                             </option> | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { placeholderHelperText } from "@goauthentik/admin/helperText"; | ||||
| import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-slug-input.js"; | ||||
| import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js"; | ||||
| import "@goauthentik/elements/forms/FormGroup"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| @ -48,14 +49,15 @@ export class SCIMSourceForm extends BaseSourceForm<SCIMSource> { | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Slug")} required name="slug"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.slug)}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-slug-input | ||||
|                 name="slug" | ||||
|                 value=${ifDefined(this.instance?.slug)} | ||||
|                 label=${msg("Slug")} | ||||
|                 required | ||||
|                 input-hint="code" | ||||
|             ></ak-slug-input> | ||||
|  | ||||
|             <ak-form-element-horizontal name="enabled"> | ||||
|                 <div class="pf-c-check"> | ||||
|                     <input | ||||
|  | ||||
| @ -41,14 +41,27 @@ export class InvitationForm extends ModelForm<Invitation, string> { | ||||
|     } | ||||
|  | ||||
|     renderForm(): TemplateResult { | ||||
|         return html` <ak-form-element-horizontal slugMode label=${msg("Name")} required name="name"> | ||||
|         const checkSlug = (ev: InputEvent) => { | ||||
|             if (ev && ev.target && ev.target instanceof HTMLInputElement) { | ||||
|                 ev.target.value = (ev.target.value ?? "").replace(/[^a-z0-9-]/g, ""); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return html` <ak-form-element-horizontal label=${msg("Name")} required name="name"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     id="admin-stages-invitation-name" | ||||
|                     value="${this.instance?.name || ""}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                     @input=${(ev: InputEvent) => checkSlug(ev)} | ||||
|                     data-ak-slug="true" | ||||
|                 /> | ||||
|                 <p class="pf-c-form__helper-text"> | ||||
|                     ${msg( | ||||
|                         "The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.", | ||||
|                     )} | ||||
|                 </p> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Expires")} required name="expires"> | ||||
|                 <input | ||||
|  | ||||
| @ -1,21 +1,5 @@ | ||||
| import * as base64js from "base64-js"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
|  | ||||
| export function b64enc(buf: Uint8Array): string { | ||||
|     return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); | ||||
| } | ||||
|  | ||||
| export function b64RawEnc(buf: Uint8Array): string { | ||||
|     return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_"); | ||||
| } | ||||
|  | ||||
| export function u8arr(input: string): Uint8Array { | ||||
|     return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) => | ||||
|         c.charCodeAt(0), | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export function checkWebAuthnSupport() { | ||||
|     if ("credentials" in navigator) { | ||||
|         return; | ||||
| @ -25,121 +9,3 @@ export function checkWebAuthnSupport() { | ||||
|     } | ||||
|     throw new Error(msg("WebAuthn not supported by browser.")); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Transforms items in the credentialCreateOptions generated on the server | ||||
|  * into byte arrays expected by the navigator.credentials.create() call | ||||
|  */ | ||||
| export function transformCredentialCreateOptions( | ||||
|     credentialCreateOptions: PublicKeyCredentialCreationOptions, | ||||
|     userId: string, | ||||
| ): PublicKeyCredentialCreationOptions { | ||||
|     const user = credentialCreateOptions.user; | ||||
|     // Because json can't contain raw bytes, the server base64-encodes the User ID | ||||
|     // So to get the base64 encoded byte array, we first need to convert it to a regular | ||||
|     // string, then a byte array, re-encode it and wrap that in an array. | ||||
|     const stringId = decodeURIComponent(window.atob(userId)); | ||||
|     user.id = u8arr(b64enc(u8arr(stringId))); | ||||
|     const challenge = u8arr(credentialCreateOptions.challenge.toString()); | ||||
|  | ||||
|     return { | ||||
|         ...credentialCreateOptions, | ||||
|         challenge, | ||||
|         user, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export interface Assertion { | ||||
|     id: string; | ||||
|     rawId: string; | ||||
|     type: string; | ||||
|     registrationClientExtensions: string; | ||||
|     response: { | ||||
|         clientDataJSON: string; | ||||
|         attestationObject: string; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Transforms the binary data in the credential into base64 strings | ||||
|  * for posting to the server. | ||||
|  * @param {PublicKeyCredential} newAssertion | ||||
|  */ | ||||
| export function transformNewAssertionForServer(newAssertion: PublicKeyCredential): Assertion { | ||||
|     const attObj = new Uint8Array( | ||||
|         (newAssertion.response as AuthenticatorAttestationResponse).attestationObject, | ||||
|     ); | ||||
|     const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON); | ||||
|     const rawId = new Uint8Array(newAssertion.rawId); | ||||
|  | ||||
|     const registrationClientExtensions = newAssertion.getClientExtensionResults(); | ||||
|     return { | ||||
|         id: newAssertion.id, | ||||
|         rawId: b64enc(rawId), | ||||
|         type: newAssertion.type, | ||||
|         registrationClientExtensions: JSON.stringify(registrationClientExtensions), | ||||
|         response: { | ||||
|             clientDataJSON: b64enc(clientDataJSON), | ||||
|             attestationObject: b64enc(attObj), | ||||
|         }, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export function transformCredentialRequestOptions( | ||||
|     credentialRequestOptions: PublicKeyCredentialRequestOptions, | ||||
| ): PublicKeyCredentialRequestOptions { | ||||
|     const challenge = u8arr(credentialRequestOptions.challenge.toString()); | ||||
|  | ||||
|     const allowCredentials = (credentialRequestOptions.allowCredentials || []).map( | ||||
|         (credentialDescriptor) => { | ||||
|             const id = u8arr(credentialDescriptor.id.toString()); | ||||
|             return Object.assign({}, credentialDescriptor, { id }); | ||||
|         }, | ||||
|     ); | ||||
|  | ||||
|     return { | ||||
|         ...credentialRequestOptions, | ||||
|         challenge, | ||||
|         allowCredentials, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export interface AuthAssertion { | ||||
|     id: string; | ||||
|     rawId: string; | ||||
|     type: string; | ||||
|     assertionClientExtensions: string; | ||||
|     response: { | ||||
|         clientDataJSON: string; | ||||
|         authenticatorData: string; | ||||
|         signature: string; | ||||
|         userHandle: string | null; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Encodes the binary data in the assertion into strings for posting to the server. | ||||
|  * @param {PublicKeyCredential} newAssertion | ||||
|  */ | ||||
| export function transformAssertionForServer(newAssertion: PublicKeyCredential): AuthAssertion { | ||||
|     const response = newAssertion.response as AuthenticatorAssertionResponse; | ||||
|     const authData = new Uint8Array(response.authenticatorData); | ||||
|     const clientDataJSON = new Uint8Array(response.clientDataJSON); | ||||
|     const rawId = new Uint8Array(newAssertion.rawId); | ||||
|     const sig = new Uint8Array(response.signature); | ||||
|     const assertionClientExtensions = newAssertion.getClientExtensionResults(); | ||||
|  | ||||
|     return { | ||||
|         id: newAssertion.id, | ||||
|         rawId: b64enc(rawId), | ||||
|         type: newAssertion.type, | ||||
|         assertionClientExtensions: JSON.stringify(assertionClientExtensions), | ||||
|  | ||||
|         response: { | ||||
|             clientDataJSON: b64RawEnc(clientDataJSON), | ||||
|             signature: b64RawEnc(sig), | ||||
|             authenticatorData: b64RawEnc(authData), | ||||
|             userHandle: null, | ||||
|         }, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { me } from "@goauthentik/common/users.js"; | ||||
| import { isUserRoute } from "@goauthentik/elements/router/utils.js"; | ||||
| import { deepmerge, deepmergeInto } from "deepmerge-ts"; | ||||
|  | ||||
| import { UiThemeEnum, UserSelf } from "@goauthentik/api"; | ||||
| import { CurrentBrand } from "@goauthentik/api"; | ||||
| @ -96,13 +97,12 @@ export class DefaultUIConfig implements UIConfig { | ||||
| let globalUiConfig: Promise<UIConfig>; | ||||
|  | ||||
| export function getConfigForUser(user: UserSelf): UIConfig { | ||||
|     const settings = user.settings; | ||||
|     let config = new DefaultUIConfig(); | ||||
|     const settings = user.settings as UIConfig; | ||||
|     const config = new DefaultUIConfig(); | ||||
|     if (!settings) { | ||||
|         return config; | ||||
|     } | ||||
|     config = Object.assign(new DefaultUIConfig(), settings); | ||||
|     return config; | ||||
|     return deepmerge({ ...config }, settings); | ||||
| } | ||||
|  | ||||
| export function uiConfig(): Promise<UIConfig> { | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import { formatSlug } from "@goauthentik/elements/router/utils.js"; | ||||
| import { bound } from "@goauthentik/elements/decorators/bound.js"; | ||||
| import { kebabCase } from "change-case"; | ||||
|  | ||||
| import { html } from "lit"; | ||||
| import { customElement, property, query } from "lit/decorators.js"; | ||||
| @ -6,59 +7,83 @@ import { ifDefined } from "lit/directives/if-defined.js"; | ||||
|  | ||||
| import { HorizontalLightComponent } from "./HorizontalLightComponent"; | ||||
|  | ||||
| const slugify = (s: string) => kebabCase(s, { suffixCharacters: "-" }); | ||||
|  | ||||
| /** | ||||
|  * @element ak-slug-input | ||||
|  * @class AkSlugInput | ||||
|  * | ||||
|  * A wrapper around `ak-form-element-horizontal` and a text input control that listens for input on | ||||
|  * a peer text input control and automatically mirrors that control's value, transforming the value | ||||
|  * into a slug and displaying it separately. | ||||
|  * | ||||
|  * If the user manually changes the slug, mirroring and transformation stop. If, after that, both | ||||
|  * fields are cleared manually, mirroring and transformation resume. | ||||
|  * | ||||
|  * ## Limitations: | ||||
|  * | ||||
|  * Both the source text field and the slug field must be rendered in the same render pass (i.e., | ||||
|  * part of the same singular call to a `render` function) so that the slug field can find its | ||||
|  * source. | ||||
|  * | ||||
|  * For the same reason, both the source text field and the slug field must share the same immediate | ||||
|  * parent DOM object. | ||||
|  * | ||||
|  * Since we expect the source text field and the slug to be part of the same form and rendered not | ||||
|  * just in the same form but in the same form group, these are not considered burdensome | ||||
|  * restrictions. | ||||
|  */ | ||||
| @customElement("ak-slug-input") | ||||
| export class AkSlugInput extends HorizontalLightComponent<string> { | ||||
|     @property({ type: String, reflect: true }) | ||||
|     value = ""; | ||||
|  | ||||
|     /** | ||||
|      * A selector indicating the source text input control. Must be unique within the whole DOM | ||||
|      * context of the slug and source controls. The most common use in authentik is the default: | ||||
|      * slugifying the "name" of something. | ||||
|      */ | ||||
|     @property({ type: String }) | ||||
|     source = ""; | ||||
|     public source = "[name='name']"; | ||||
|  | ||||
|     origin?: HTMLInputElement | null; | ||||
|     @property({ type: String, reflect: true }) | ||||
|     public value = ""; | ||||
|  | ||||
|     @query("input") | ||||
|     input!: HTMLInputElement; | ||||
|     private input!: HTMLInputElement; | ||||
|  | ||||
|     touched: boolean = false; | ||||
|     #origin?: HTMLInputElement | null; | ||||
|  | ||||
|     constructor() { | ||||
|         super(); | ||||
|         this.slugify = this.slugify.bind(this); | ||||
|         this.handleTouch = this.handleTouch.bind(this); | ||||
|     } | ||||
|  | ||||
|     firstUpdated() { | ||||
|         this.input.addEventListener("input", this.handleTouch); | ||||
|     } | ||||
|     #touched: boolean = false; | ||||
|  | ||||
|     // Do not stop propagation of this event; it must be sent up the tree so that a parent | ||||
|     // component, such as a custom forms manager, may receive it. | ||||
|     handleTouch(ev: Event) { | ||||
|         this.input.value = formatSlug(this.input.value); | ||||
|         this.value = this.input.value; | ||||
|     protected handleTouch(ev: Event) { | ||||
|         this.value = this.input.value = slugify(this.input.value); | ||||
|  | ||||
|         if (this.origin && this.origin.value === "" && this.input.value === "") { | ||||
|             this.touched = false; | ||||
|         // Reset 'touched' status if the slug & target have been reset | ||||
|         if (this.#origin && this.#origin.value === "" && this.input.value === "") { | ||||
|             this.#touched = false; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (ev && ev.target && ev.target instanceof HTMLInputElement) { | ||||
|             this.touched = true; | ||||
|             this.#touched = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     slugify(ev: Event) { | ||||
|     @bound | ||||
|     protected slugify(ev: Event) { | ||||
|         if (!(ev && ev.target && ev.target instanceof HTMLInputElement)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Reset 'touched' status if the slug & target have been reset | ||||
|         if (ev.target.value === "" && this.input.value === "") { | ||||
|             this.touched = false; | ||||
|             this.#touched = false; | ||||
|         } | ||||
|  | ||||
|         // Don't proceed if the user has hand-modified the slug | ||||
|         if (this.touched) { | ||||
|         // Don't proceed if the user has hand-modified the slug. (Note the order of statements: if | ||||
|         // the user hand modified the slug to be empty as part of resetting the slug/source | ||||
|         // relationship, that's a "not-touched" condition and falls through.) | ||||
|         if (this.#touched) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @ -67,7 +92,7 @@ export class AkSlugInput extends HorizontalLightComponent<string> { | ||||
|         // "any event which adds or removes a character but leaves the rest of the slug looking like | ||||
|         // the previous iteration, set it to the current iteration." | ||||
|  | ||||
|         const newSlug = formatSlug(ev.target.value); | ||||
|         const newSlug = slugify(ev.target.value); | ||||
|         const oldSlug = this.input.value; | ||||
|         const [shorter, longer] = | ||||
|             newSlug.length < oldSlug.length ? [newSlug, oldSlug] : [oldSlug, newSlug]; | ||||
| @ -81,7 +106,6 @@ export class AkSlugInput extends HorizontalLightComponent<string> { | ||||
|         // to listeners, both the name and value of the host must match those of the target | ||||
|         // input. The name is already handled since it's both required and automatically | ||||
|         // forwarded to our templated input, but the value must also be set. | ||||
|  | ||||
|         this.value = this.input.value = newSlug; | ||||
|         this.dispatchEvent( | ||||
|             new Event("input", { | ||||
| @ -91,38 +115,36 @@ export class AkSlugInput extends HorizontalLightComponent<string> { | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     connectedCallback() { | ||||
|         super.connectedCallback(); | ||||
|  | ||||
|         // Set up listener on source element, so we can slugify the content. | ||||
|         setTimeout(() => { | ||||
|             if (this.source) { | ||||
|                 const rootNode = this.getRootNode(); | ||||
|                 if (rootNode instanceof ShadowRoot || rootNode instanceof Document) { | ||||
|                     this.origin = rootNode.querySelector(this.source); | ||||
|                 } | ||||
|                 if (this.origin) { | ||||
|                     this.origin.addEventListener("input", this.slugify); | ||||
|                 } | ||||
|             } | ||||
|         }, 0); | ||||
|     } | ||||
|  | ||||
|     disconnectedCallback() { | ||||
|         if (this.origin) { | ||||
|             this.origin.removeEventListener("input", this.slugify); | ||||
|     public override disconnectedCallback() { | ||||
|         if (this.#origin) { | ||||
|             this.#origin.removeEventListener("input", this.slugify); | ||||
|         } | ||||
|         super.disconnectedCallback(); | ||||
|     } | ||||
|  | ||||
|     renderControl() { | ||||
|     public override renderControl() { | ||||
|         return html`<input | ||||
|             @input=${(ev: Event) => this.handleTouch(ev)} | ||||
|             type="text" | ||||
|             value=${ifDefined(this.value)} | ||||
|             class="pf-c-form-control" | ||||
|             ?required=${this.required} | ||||
|         />`; | ||||
|     } | ||||
|  | ||||
|     public override firstUpdated() { | ||||
|         if (!this.source) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         const rootNode = this.getRootNode(); | ||||
|         if (rootNode instanceof ShadowRoot || rootNode instanceof Document) { | ||||
|             this.#origin = rootNode.querySelector(this.source); | ||||
|         } | ||||
|         if (this.#origin) { | ||||
|             this.#origin.addEventListener("input", this.slugify); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default AkSlugInput; | ||||
|  | ||||
| @ -94,7 +94,8 @@ export class ObjectChangelog extends Table<Event> { | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No Events found.")}> | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No Events found.")}</span> | ||||
|                 <div slot="body">${msg("No matching events could be found.")}</div> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
| @ -66,7 +66,8 @@ export class UserEvents extends Table<Event> { | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No Events found.")}> | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No Events found.")}</span> | ||||
|                 <div slot="body">${msg("No matching events could be found.")}</div> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { AKElement } from "@goauthentik/elements/Base"; | ||||
| import "@goauthentik/elements/Spinner"; | ||||
| import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types"; | ||||
| import { spread } from "@open-wc/lit-helpers"; | ||||
| import { SlotController } from "@patternfly/pfe-core/controllers/slot-controller.js"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| import { css, html, nothing } from "lit"; | ||||
| @ -33,6 +34,8 @@ export class EmptyState extends AKElement implements IEmptyState { | ||||
|     @property() | ||||
|     header?: string; | ||||
|  | ||||
|     slots = new SlotController(this, "header", "body", "primary"); | ||||
|  | ||||
|     static get styles() { | ||||
|         return [ | ||||
|             PFBase, | ||||
| @ -48,6 +51,12 @@ export class EmptyState extends AKElement implements IEmptyState { | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         const showHeader = this.loading || this.slots.hasSlotted("header"); | ||||
|         const header = () => | ||||
|             this.slots.hasSlotted("header") | ||||
|                 ? html`<slot name="header"></slot>` | ||||
|                 : html`<span>${msg("Loading")}</span>`; | ||||
|  | ||||
|         return html`<div class="pf-c-empty-state ${this.fullHeight && "pf-m-full-height"}"> | ||||
|             <div class="pf-c-empty-state__content"> | ||||
|                 ${this.loading | ||||
| @ -59,15 +68,17 @@ export class EmptyState extends AKElement implements IEmptyState { | ||||
|                           "fa-question-circle"} pf-c-empty-state__icon" | ||||
|                           aria-hidden="true" | ||||
|                       ></i>`} | ||||
|                 <h1 class="pf-c-title pf-m-lg"> | ||||
|                     ${this.loading && this.header === undefined ? msg("Loading") : this.header} | ||||
|                 </h1> | ||||
|                 <div class="pf-c-empty-state__body"> | ||||
|                     <slot name="body"></slot> | ||||
|                 </div> | ||||
|                 <div class="pf-c-empty-state__primary"> | ||||
|                     <slot name="primary"></slot> | ||||
|                 </div> | ||||
|                 ${showHeader ? html` <h1 class="pf-c-title pf-m-lg">${header()}</h1>` : nothing} | ||||
|                 ${this.slots.hasSlotted("body") | ||||
|                     ? html` <div class="pf-c-empty-state__body"> | ||||
|                           <slot name="body"></slot> | ||||
|                       </div>` | ||||
|                     : nothing} | ||||
|                 ${this.slots.hasSlotted("primary") | ||||
|                     ? html` <div class="pf-c-empty-state__primary"> | ||||
|                           <slot name="primary"></slot> | ||||
|                       </div>` | ||||
|                     : nothing} | ||||
|             </div> | ||||
|         </div>`; | ||||
|     } | ||||
|  | ||||
| @ -200,7 +200,8 @@ export abstract class AKChart<T> extends AKElement { | ||||
|             <div class="container"> | ||||
|                 ${this.error | ||||
|                     ? html` | ||||
|                           <ak-empty-state header="${msg("Failed to fetch data.")}" icon="fa-times"> | ||||
|                           <ak-empty-state icon="fa-times" | ||||
|                               ><span slot="header">${msg("Failed to fetch data.")}</span> | ||||
|                               <p slot="body">${pluckErrorDetail(this.error)}</p> | ||||
|                           </ak-empty-state> | ||||
|                       ` | ||||
|  | ||||
| @ -40,7 +40,9 @@ export class LogViewer extends Table<LogEvent> { | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|         return super.renderEmpty( | ||||
|             html`<ak-empty-state header=${msg("No log messages.")}> </ak-empty-state>`, | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No log messages.")}</span> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -7,7 +7,6 @@ import { AKElement } from "@goauthentik/elements/Base"; | ||||
| import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| import { PreventFormSubmit } from "@goauthentik/elements/forms/helpers"; | ||||
| import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; | ||||
| import { formatSlug } from "@goauthentik/elements/router/utils.js"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| import { CSSResult, TemplateResult, css, html } from "lit"; | ||||
| @ -197,39 +196,6 @@ export abstract class Form<T> extends AKElement { | ||||
|         return this.successMessage; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * After rendering the form, if there is both a `name` and `slug` element within the form, | ||||
|      * events the `name` element so that the slug will always have a slugified version of the | ||||
|      * `name.`. This duplicates functionality within ak-form-element-horizontal. | ||||
|      */ | ||||
|     updated(): void { | ||||
|         this.shadowRoot | ||||
|             ?.querySelectorAll("ak-form-element-horizontal[name=name]") | ||||
|             .forEach((nameInput) => { | ||||
|                 const input = nameInput.firstElementChild as HTMLInputElement; | ||||
|                 const form = nameInput.closest("form"); | ||||
|                 if (form === null) { | ||||
|                     return; | ||||
|                 } | ||||
|                 const slugFieldWrapper = form.querySelector( | ||||
|                     "ak-form-element-horizontal[name=slug]", | ||||
|                 ); | ||||
|                 if (!slugFieldWrapper) { | ||||
|                     return; | ||||
|                 } | ||||
|                 const slugField = slugFieldWrapper.firstElementChild as HTMLInputElement; | ||||
|                 // Only attach handler if the slug is already equal to the name | ||||
|                 // if not, they are probably completely different and shouldn't update | ||||
|                 // each other | ||||
|                 if (formatSlug(input.value) !== slugField.value) { | ||||
|                     return; | ||||
|                 } | ||||
|                 nameInput.addEventListener("input", () => { | ||||
|                     slugField.value = formatSlug(input.value); | ||||
|                 }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     resetForm(): void { | ||||
|         const form = this.shadowRoot?.querySelector<HTMLFormElement>("form"); | ||||
|         form?.reset(); | ||||
|  | ||||
| @ -77,9 +77,6 @@ export class HorizontalFormElement extends AKElement { | ||||
|     @property({ attribute: false }) | ||||
|     errorMessages: string[] | string[][] = []; | ||||
|  | ||||
|     @property({ type: Boolean }) | ||||
|     slugMode = false; | ||||
|  | ||||
|     _invalid = false; | ||||
|  | ||||
|     /* If this property changes, we want to make sure the parent control is "opened" so | ||||
| @ -109,13 +106,6 @@ export class HorizontalFormElement extends AKElement { | ||||
|         this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => { | ||||
|             input.focus(); | ||||
|         }); | ||||
|         if (this.name === "slug" || this.slugMode) { | ||||
|             this.querySelectorAll<HTMLInputElement>("input[type='text']").forEach((input) => { | ||||
|                 input.addEventListener("keyup", () => { | ||||
|                     input.value = formatSlug(input.value); | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
|         this.querySelectorAll("*").forEach((input) => { | ||||
|             if (isAkControl(input) && !input.getAttribute("name")) { | ||||
|                 input.setAttribute("name", this.name); | ||||
|  | ||||
| @ -163,7 +163,8 @@ export class NotificationDrawer extends AKElement { | ||||
|     } | ||||
|  | ||||
|     renderEmpty() { | ||||
|         return html`<ak-empty-state header=${msg("No notifications found.")}> | ||||
|         return html`<ak-empty-state | ||||
|             ><span slot="header">${msg("No notifications found.")}</span> | ||||
|             <div slot="body">${msg("You don't have any notifications currently.")}</div> | ||||
|         </ak-empty-state>`; | ||||
|     } | ||||
|  | ||||
| @ -288,7 +288,9 @@ export abstract class Table<T> extends AKElement implements TableLike { | ||||
|         return html`<tr role="row"> | ||||
|             <td role="cell" colspan="25"> | ||||
|                 <div class="pf-l-bullseye"> | ||||
|                     <ak-empty-state loading header=${msg("Loading")}></ak-empty-state> | ||||
|                     <ak-empty-state loading | ||||
|                         ><span slot="header">${msg("Loading")}</span></ak-empty-state | ||||
|                     > | ||||
|                 </div> | ||||
|             </td> | ||||
|         </tr>`; | ||||
| @ -300,8 +302,9 @@ export abstract class Table<T> extends AKElement implements TableLike { | ||||
|                 <td role="cell" colspan="8"> | ||||
|                     <div class="pf-l-bullseye"> | ||||
|                         ${inner ?? | ||||
|                         html`<ak-empty-state header="${msg("No objects found.")}" | ||||
|                             ><div slot="primary">${this.renderObjectCreate()}</div> | ||||
|                         html`<ak-empty-state | ||||
|                             ><span slot="header">${msg("No objects found.")}</span> > | ||||
|                             <div slot="primary">${this.renderObjectCreate()}</div> | ||||
|                         </ak-empty-state>`} | ||||
|                     </div> | ||||
|                 </td> | ||||
| @ -316,7 +319,8 @@ export abstract class Table<T> extends AKElement implements TableLike { | ||||
|     renderError(): SlottedTemplateResult { | ||||
|         if (!this.error) return nothing; | ||||
|  | ||||
|         return html`<ak-empty-state header="${msg("Failed to fetch objects.")}" icon="fa-ban"> | ||||
|         return html`<ak-empty-state icon="fa-ban" | ||||
|             ><span slot="header">${msg("Failed to fetch objects.")}</span> | ||||
|             <div slot="body">${pluckErrorDetail(this.error)}</div> | ||||
|         </ak-empty-state>`; | ||||
|     } | ||||
|  | ||||
| @ -19,7 +19,11 @@ describe("ak-empty-state", () => { | ||||
|     }); | ||||
|  | ||||
|     it("should render the default loader", async () => { | ||||
|         render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`); | ||||
|         render( | ||||
|             html`<ak-empty-state loading | ||||
|                 ><span slot="header">${msg("Loading")}</span> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
|         const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon"); | ||||
|         await expect(empty).toExist(); | ||||
| @ -29,7 +33,11 @@ describe("ak-empty-state", () => { | ||||
|     }); | ||||
|  | ||||
|     it("should handle standard boolean", async () => { | ||||
|         render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`); | ||||
|         render( | ||||
|             html`<ak-empty-state loading | ||||
|                 ><span slot="header">${msg("Loading")}</span> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
|         const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon"); | ||||
|         await expect(empty).toExist(); | ||||
| @ -39,7 +47,11 @@ describe("ak-empty-state", () => { | ||||
|     }); | ||||
|  | ||||
|     it("should render a static empty state", async () => { | ||||
|         render(html`<ak-empty-state header=${msg("No messages found")}> </ak-empty-state>`); | ||||
|         render( | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No messages found")}</span> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
|         const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon"); | ||||
|         await expect(empty).toExist(); | ||||
| @ -51,7 +63,8 @@ describe("ak-empty-state", () => { | ||||
|  | ||||
|     it("should render a slotted message", async () => { | ||||
|         render( | ||||
|             html`<ak-empty-state header=${msg("No messages found")}> | ||||
|             html`<ak-empty-state | ||||
|                 ><span slot="header">${msg("No messages found")}</span> | ||||
|                 <p slot="body">Try again with a different filter</p> | ||||
|             </ak-empty-state>`, | ||||
|         ); | ||||
|  | ||||
| @ -1,8 +1,4 @@ | ||||
| import { | ||||
|     checkWebAuthnSupport, | ||||
|     transformAssertionForServer, | ||||
|     transformCredentialRequestOptions, | ||||
| } from "@goauthentik/common/helpers/webauthn"; | ||||
| import { checkWebAuthnSupport } from "@goauthentik/common/helpers/webauthn"; | ||||
| import "@goauthentik/elements/EmptyState"; | ||||
| import { BaseDeviceStage } from "@goauthentik/flow/stages/authenticator_validate/base"; | ||||
|  | ||||
| @ -38,12 +34,12 @@ export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage< | ||||
|     async authenticate(): Promise<void> { | ||||
|         // request the authenticator to create an assertion signature using the | ||||
|         // credential private key | ||||
|         let assertion; | ||||
|         let assertion: PublicKeyCredential; | ||||
|         checkWebAuthnSupport(); | ||||
|         try { | ||||
|             assertion = await navigator.credentials.get({ | ||||
|             assertion = (await navigator.credentials.get({ | ||||
|                 publicKey: this.transformedCredentialRequestOptions, | ||||
|             }); | ||||
|             })) as PublicKeyCredential; | ||||
|             if (!assertion) { | ||||
|                 throw new Error("Assertions is empty"); | ||||
|             } | ||||
| @ -51,17 +47,11 @@ export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage< | ||||
|             throw new Error(`Error when creating credential: ${err}`); | ||||
|         } | ||||
|  | ||||
|         // we now have an authentication assertion! encode the byte arrays contained | ||||
|         // in the assertion data as strings for posting to the server | ||||
|         const transformedAssertionForServer = transformAssertionForServer( | ||||
|             assertion as PublicKeyCredential, | ||||
|         ); | ||||
|  | ||||
|         // post the assertion to the server for verification. | ||||
|         try { | ||||
|             await this.host?.submit( | ||||
|                 { | ||||
|                     webauthn: transformedAssertionForServer, | ||||
|                     webauthn: assertion.toJSON(), | ||||
|                 }, | ||||
|                 { | ||||
|                     invisible: true, | ||||
| @ -74,12 +64,10 @@ export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage< | ||||
|  | ||||
|     updated(changedProperties: PropertyValues<this>) { | ||||
|         if (changedProperties.has("challenge") && this.challenge !== undefined) { | ||||
|             // convert certain members of the PublicKeyCredentialRequestOptions into | ||||
|             // byte arrays as expected by the spec. | ||||
|             const credentialRequestOptions = this.deviceChallenge | ||||
|                 ?.challenge as PublicKeyCredentialRequestOptions; | ||||
|                 ?.challenge as unknown as PublicKeyCredentialRequestOptionsJSON; | ||||
|             this.transformedCredentialRequestOptions = | ||||
|                 transformCredentialRequestOptions(credentialRequestOptions); | ||||
|                 PublicKeyCredential.parseRequestOptionsFromJSON(credentialRequestOptions); | ||||
|             this.authenticateWrapper(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -1,9 +1,4 @@ | ||||
| import { | ||||
|     Assertion, | ||||
|     checkWebAuthnSupport, | ||||
|     transformCredentialCreateOptions, | ||||
|     transformNewAssertionForServer, | ||||
| } from "@goauthentik/common/helpers/webauthn"; | ||||
| import { checkWebAuthnSupport } from "@goauthentik/common/helpers/webauthn"; | ||||
| import "@goauthentik/elements/EmptyState"; | ||||
| import { BaseStage } from "@goauthentik/flow/stages/base"; | ||||
|  | ||||
| @ -24,10 +19,6 @@ import { | ||||
|     AuthenticatorWebAuthnChallengeResponseRequest, | ||||
| } from "@goauthentik/api"; | ||||
|  | ||||
| export interface WebAuthnAuthenticatorRegisterChallengeResponse { | ||||
|     response: Assertion; | ||||
| } | ||||
|  | ||||
| @customElement("ak-stage-authenticator-webauthn") | ||||
| export class WebAuthnAuthenticatorRegisterStage extends BaseStage< | ||||
|     AuthenticatorWebAuthnChallenge, | ||||
| @ -68,7 +59,7 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage< | ||||
|         } | ||||
|         checkWebAuthnSupport(); | ||||
|         // request the authenticator(s) to create a new credential keypair. | ||||
|         let credential; | ||||
|         let credential: PublicKeyCredential; | ||||
|         try { | ||||
|             credential = (await navigator.credentials.create({ | ||||
|                 publicKey: this.publicKeyCredentialCreateOptions, | ||||
| @ -80,16 +71,12 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage< | ||||
|             throw new Error(msg(str`Error creating credential: ${err}`)); | ||||
|         } | ||||
|  | ||||
|         // we now have a new credential! We now need to encode the byte arrays | ||||
|         // in the credential into strings, for posting to our server. | ||||
|         const newAssertionForServer = transformNewAssertionForServer(credential); | ||||
|  | ||||
|         // post the transformed credential data to the server for validation | ||||
|         // and storing the public key | ||||
|         try { | ||||
|             await this.host?.submit( | ||||
|                 { | ||||
|                     response: newAssertionForServer, | ||||
|                     response: credential.toJSON(), | ||||
|                 }, | ||||
|                 { | ||||
|                     invisible: true, | ||||
| @ -118,12 +105,10 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage< | ||||
|  | ||||
|     updated(changedProperties: PropertyValues<this>) { | ||||
|         if (changedProperties.has("challenge") && this.challenge !== undefined) { | ||||
|             // convert certain members of the PublicKeyCredentialCreateOptions into | ||||
|             // byte arrays as expected by the spec. | ||||
|             this.publicKeyCredentialCreateOptions = transformCredentialCreateOptions( | ||||
|                 this.challenge?.registration as PublicKeyCredentialCreationOptions, | ||||
|                 this.challenge?.registration.user.id, | ||||
|             ); | ||||
|             this.publicKeyCredentialCreateOptions = | ||||
|                 PublicKeyCredential.parseCreationOptionsFromJSON( | ||||
|                     this.challenge?.registration as PublicKeyCredentialCreationOptionsJSON, | ||||
|                 ); | ||||
|             this.registerWrapper(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -3,6 +3,7 @@ import "construct-style-sheets-polyfill"; | ||||
| import "@webcomponents/webcomponentsjs"; | ||||
| import "lit/polyfill-support.js"; | ||||
| import "core-js/actual"; | ||||
| import "webauthn-polyfills"; | ||||
|  | ||||
| import "@formatjs/intl-listformat/polyfill"; | ||||
| import "@formatjs/intl-listformat/locale-data/en"; | ||||
|  | ||||
							
								
								
									
										2
									
								
								web/types/node.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								web/types/node.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -14,7 +14,7 @@ declare module "module" { | ||||
|          * const relativeDirname = dirname(fileURLToPath(import.meta.url)); | ||||
|          * ``` | ||||
|          */ | ||||
|         // eslint-disable-next-line no-var | ||||
|  | ||||
|         var __dirname: string; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -9245,6 +9245,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -7753,6 +7753,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9305,6 +9305,9 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9874,6 +9874,9 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9857,6 +9857,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9213,6 +9213,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9117,6 +9117,9 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9540,6 +9540,9 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9549,4 +9549,7 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
| </body></file></xliff> | ||||
|  | ||||
| @ -9632,6 +9632,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9604,6 +9604,9 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -6368,6 +6368,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
| </body> | ||||
| </file> | ||||
| </xliff> | ||||
|  | ||||
| @ -9878,12 +9878,18 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="sf9686d31d28fcf7d"> | ||||
|   <source>Show field content</source> | ||||
|   <target>显示字段内容</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="sb1b05a7573ab618c"> | ||||
|   <source>Hide field content</source> | ||||
|   <target>隐藏字段内容</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
|   <target>使用 Plex 重新验证身份</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -7453,6 +7453,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -3011,11 +3011,6 @@ doesn't pass when either or both of the selected options are equal or above the | ||||
|         <source>Load servers</source> | ||||
|         <target>加载服务器</target> | ||||
|          | ||||
|       </trans-unit> | ||||
|       <trans-unit id="s24f405197ede5ebb"> | ||||
|         <source>Re-authenticate with plex</source> | ||||
|         <target>使用 Plex 重新验证身份</target> | ||||
|          | ||||
|       </trans-unit> | ||||
|       <trans-unit id="sc297b2e13c28ecf9"> | ||||
|         <source>Allow friends to authenticate via Plex, even if you don't share any servers</source> | ||||
| @ -9880,6 +9875,18 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| <trans-unit id="sb3d5c0a0501669df"> | ||||
|   <source>Generate New Certificate-Key Pair</source> | ||||
|   <target>生成新的证书密钥对</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="sf9686d31d28fcf7d"> | ||||
|   <source>Show field content</source> | ||||
|   <target>显示字段内容</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="sb1b05a7573ab618c"> | ||||
|   <source>Hide field content</source> | ||||
|   <target>隐藏字段内容</target> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
|   <target>使用 Plex 重新验证身份</target> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -9192,6 +9192,9 @@ Bindings to groups/users are checked against the user of the event.</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s4f820625804ed29b"> | ||||
|   <source>Re-authenticate with Plex</source> | ||||
| </trans-unit> | ||||
| <trans-unit id="s0433d667ea6eec1a"> | ||||
|   <source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source> | ||||
| </trans-unit> | ||||
|     </body> | ||||
|   </file> | ||||
|  | ||||
| @ -70,9 +70,6 @@ To check if your config has been applied correctly, you can run the following co | ||||
| - `AUTHENTIK_POSTGRESQL__USER`: Database user | ||||
| - `AUTHENTIK_POSTGRESQL__PORT`: Database port, defaults to 5432 | ||||
| - `AUTHENTIK_POSTGRESQL__PASSWORD`: Database password, defaults to the environment variable `POSTGRES_PASSWORD` | ||||
|   {/* TODO: Temporarily deactivated feature, see https://github.com/goauthentik/authentik/issues/14320 */} | ||||
|   {/* - `AUTHENTIK_POSTGRESQL__USE_POOL`: Use a [connection pool](https://docs.djangoproject.com/en/stable/ref/databases/#connection-pool) for PostgreSQL connections. Defaults to `false`. :ak-version[2025.4] */} | ||||
|   {/* - `AUTHENTIK_POSTGRESQL__POOL_OPTIONS`: Extra configuration to pass to the [ConnectionPool object](https://www.psycopg.org/psycopg3/docs/api/pool.html#psycopg_pool.ConnectionPool) when it is created. Must be a base64-encoded JSON dictionary. Ignored when `USE_POOL` is set to `false`. :ak-version[2025.4] */} | ||||
| - `AUTHENTIK_POSTGRESQL__USE_PGBOUNCER`: Adjust configuration to support connection to PgBouncer. Deprecated, see below | ||||
| - `AUTHENTIK_POSTGRESQL__USE_PGPOOL`: Adjust configuration to support connection to Pgpool. Deprecated, see below | ||||
| - `AUTHENTIK_POSTGRESQL__SSLMODE`: Strictness of ssl verification. Defaults to `"verify-ca"` | ||||
| @ -85,7 +82,7 @@ To check if your config has been applied correctly, you can run the following co | ||||
|  | ||||
| The PostgreSQL settings `HOST`, `PORT`, `USER`, and `PASSWORD` support hot-reloading. Adding and removing read replicas doesn't support hot-reloading. | ||||
|  | ||||
| - `AUTHENTIK_POSTGRESQL__DEFAULT_SCHEMA`:ak-version[2024.12] | ||||
| - `AUTHENTIK_POSTGRESQL__DEFAULT_SCHEMA` :ak-version[2024.12] | ||||
|  | ||||
|     The name of the schema used by default in the database. Defaults to `public`. | ||||
|  | ||||
|  | ||||
| @ -142,6 +142,17 @@ helm upgrade authentik authentik/authentik -f values.yaml --version ^2025.6 | ||||
| - tenants: fix tenant aware celery scheduler (cherry-pick #14921) | ||||
| - web/user: fix user settings flow not loading (cherry-pick #14911) (#14930) | ||||
|  | ||||
| ## Fixed in 2025.6.2 | ||||
|  | ||||
| - brands: fix custom_css being escaped (cherry-pick #14994) (#14996) | ||||
| - core: bump django from 5.1.10 to 5.1.11 (cherry-pick #14997) (#15010) | ||||
| - core: bump django from 5.1.9 to 5.1.10 (cherry-pick #14951) (#15008) | ||||
| - internal/outpost: fix incorrect usage of golang SHA API (cherry-pick #14981) (#14982) | ||||
| - providers/rac: fixes prompt data not being merged with connection_settings (cherry-pick #15037) (#15038) | ||||
| - stages/email: Only attach logo to email if used (cherry-pick #14835) (#14969) | ||||
| - web/elements: fix dual select without sortBy (cherry-pick #14977) (#14979) | ||||
| - web/elements: fix typo in localeComparator (cherry-pick #15054) (#15055) | ||||
|  | ||||
| ## API Changes | ||||
|  | ||||
| #### What's New | ||||
|  | ||||
| @ -27,3 +27,29 @@ uv run ak create_recovery_key 10 akadmin | ||||
| ``` | ||||
|  | ||||
| This will output a link, that can be used to instantly gain access to authentik as the user specified above. The link is valid for amount of years specified above, in this case, 10 years. | ||||
|  | ||||
| ## Can't access initial setup flow during installation steps | ||||
|  | ||||
| If you're unable to access the initial setup flow (`/if/flow/initial-setup/`) immediately after installing authentik, first try restarting the containers because this often resolves temporary issues. | ||||
|  | ||||
| However, if the issue persists after restarting, you can directly set the admin password using the following commands: | ||||
|  | ||||
| Docker Compose deployments: | ||||
|  | ||||
|     ```bash | ||||
|     docker compose exec server ak changepassword akadmin | ||||
|     ``` | ||||
|  | ||||
| Kubernetes deployments: | ||||
|  | ||||
|     ```bash | ||||
|     kubectl exec -it deployment/authentik-server -c server -- ak changepassword akadmin | ||||
|     ``` | ||||
|  | ||||
| After following the prompts to set a new password, you can then login via: `https://authentik.company/if/flow/default-authentication-flow/?next=%2F` | ||||
|  | ||||
| After logging in, you can set the email address and other settings for the account by navigating to **Directory** > **Users** and editing the user account. | ||||
|  | ||||
| :::note | ||||
| This method bypasses the initial setup flow and should only be used as a last resort. The initial setup flow is the recommended method to configure the administrator user. | ||||
| ::: | ||||
|  | ||||
| @ -49,7 +49,7 @@ After you are connected, execute these commands to create a database backup: | ||||
| cd /bitnami/postgresql/ | ||||
|  | ||||
| # Set the PostgreSQL password from environment variable | ||||
| export PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD | ||||
| export PGPASSWORD=$(cat $POSTGRES_PASSWORD_FILE) | ||||
|  | ||||
| # Create a full database dump | ||||
| pg_dump -U $POSTGRES_USER $POSTGRES_DB > /bitnami/postgresql/dump.sql | ||||
| @ -117,7 +117,7 @@ cd /bitnami/postgresql/ | ||||
| ls -lh dump.sql | ||||
|  | ||||
| # Set the PostgreSQL password | ||||
| export PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD | ||||
| export PGPASSWORD=$(cat $POSTGRES_PASSWORD_FILE) | ||||
|  | ||||
| # Import the database dump | ||||
| psql -U $POSTGRES_USER $POSTGRES_DB < dump.sql | ||||
|  | ||||
| @ -81,7 +81,7 @@ If you want to control user storage and designate Nextcloud administrators, you | ||||
|     - **Create Scope Mapping**: | ||||
|  | ||||
|         - **Name**: `Nextcloud Profile` | ||||
|         - **Scope name**: `profile` | ||||
|         - **Scope name**: `nextcloud` | ||||
|         - **Expression**: | ||||
|  | ||||
|             ```python | ||||
| @ -146,7 +146,7 @@ Depending on your Nextcloud configuration, you may need to use `https://nextclou | ||||
|     - **Client ID**: Client ID from authentik | ||||
|     - **Client secret**: Client secret from authentik | ||||
|     - **Discovery endpoint**: `https://authentik.company/application/o/<application_slug>/.well-known/openid-configuration` | ||||
|     - **Scope**: `email profile openid` | ||||
|     - **Scope**: `email nextcloud openid` | ||||
|     - Under **Attribute mappings**: | ||||
|  | ||||
|         - **User ID mapping**: `sub` (or `user_id` for existing users) | ||||
| @ -161,6 +161,10 @@ Depending on your Nextcloud configuration, you may need to use `https://nextclou | ||||
|  | ||||
|     - **Use unique user ID**: If this option is disabled, Nextcloud will use the mapped user ID as the Federated Cloud ID. | ||||
|  | ||||
|     :::note | ||||
|     If authentik and Nextcloud are running on the same host, you will need to add `'allow_local_remote_servers' => true` to your nextcloud `config.php` file. This setting allows remote servers with local addresses. | ||||
|     ::: | ||||
|  | ||||
|     :::tip | ||||
|     To avoid a hashed Federated Cloud ID, deselect **Use unique user ID** and use `user_id` for the User ID mapping. | ||||
|     ::: | ||||
|  | ||||
| @ -40,6 +40,7 @@ To support the integration of Zipline with authentik, you need to create an appl | ||||
|     - Note the **Client ID** and **Client Secret** values because they will be required later. | ||||
|     - Set a `Strict` redirect URI to `https://zipline.company/api/auth/oauth/oidc`. | ||||
|     - Select any available signing key. | ||||
|     - Under **Advanced Protocol Settings** > **Scopes**, add `authentik default OAuth Mapping: OpenID 'offline_access'` to the **Selected Scopes** list. | ||||
| - **Configure Bindings** _(optional)_: Create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page. | ||||
|  | ||||
| 3. Click **Submit** to save the new application and provider. | ||||
|  | ||||
							
								
								
									
										224
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										224
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -19,11 +19,10 @@ | ||||
|                 "@goauthentik/docusaurus-config": "^1.1.0", | ||||
|                 "@goauthentik/tsconfig": "^1.0.4", | ||||
|                 "@mdx-js/react": "^3.1.0", | ||||
|                 "@swc/html-linux-x64-gnu": "1.12.1", | ||||
|                 "clsx": "^2.1.1", | ||||
|                 "docusaurus-plugin-openapi-docs": "^4.4.0", | ||||
|                 "docusaurus-theme-openapi-docs": "^4.4.0", | ||||
|                 "postcss": "^8.5.5", | ||||
|                 "postcss": "^8.5.6", | ||||
|                 "prism-react-renderer": "^2.4.1", | ||||
|                 "react": "^18.3.1", | ||||
|                 "react-before-after-slider-component": "^1.1.8", | ||||
| @ -36,26 +35,26 @@ | ||||
|                 "@docusaurus/module-type-aliases": "^3.7.0", | ||||
|                 "@docusaurus/tsconfig": "^3.7.0", | ||||
|                 "@docusaurus/types": "^3.7.0", | ||||
|                 "@eslint/js": "^9.28.0", | ||||
|                 "@eslint/js": "^9.29.0", | ||||
|                 "@goauthentik/eslint-config": "^1.0.5", | ||||
|                 "@goauthentik/prettier-config": "^1.0.5", | ||||
|                 "@goauthentik/tsconfig": "^1.0.4", | ||||
|                 "@trivago/prettier-plugin-sort-imports": "^5.2.2", | ||||
|                 "@types/lodash": "^4.17.17", | ||||
|                 "@types/node": "^24.0.1", | ||||
|                 "@types/lodash": "^4.17.18", | ||||
|                 "@types/node": "^24.0.3", | ||||
|                 "@types/postman-collection": "^3.5.11", | ||||
|                 "@types/react": "^18.3.22", | ||||
|                 "@types/semver": "^7.7.0", | ||||
|                 "@typescript-eslint/eslint-plugin": "^8.34.0", | ||||
|                 "@typescript-eslint/parser": "^8.34.0", | ||||
|                 "@typescript-eslint/eslint-plugin": "^8.34.1", | ||||
|                 "@typescript-eslint/parser": "^8.34.1", | ||||
|                 "cross-env": "^7.0.3", | ||||
|                 "eslint": "^9.28.0", | ||||
|                 "eslint": "^9.29.0", | ||||
|                 "fast-glob": "^3.3.3", | ||||
|                 "npm-run-all": "^4.1.5", | ||||
|                 "prettier": "^3.5.3", | ||||
|                 "prettier-plugin-packagejson": "^2.5.15", | ||||
|                 "typescript": "^5.8.3", | ||||
|                 "typescript-eslint": "^8.34.0" | ||||
|                 "typescript-eslint": "^8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": ">=22.14.0" | ||||
| @ -4209,9 +4208,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@eslint/config-array": { | ||||
|             "version": "0.20.0", | ||||
|             "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", | ||||
|             "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", | ||||
|             "version": "0.20.1", | ||||
|             "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", | ||||
|             "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", | ||||
|             "devOptional": true, | ||||
|             "license": "Apache-2.0", | ||||
|             "dependencies": { | ||||
| @ -4308,9 +4307,9 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/@eslint/js": { | ||||
|             "version": "9.28.0", | ||||
|             "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", | ||||
|             "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", | ||||
|             "version": "9.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", | ||||
|             "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", | ||||
|             "devOptional": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -6581,9 +6580,9 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/@types/lodash": { | ||||
|             "version": "4.17.17", | ||||
|             "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", | ||||
|             "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", | ||||
|             "version": "4.17.18", | ||||
|             "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.18.tgz", | ||||
|             "integrity": "sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g==", | ||||
|             "dev": true, | ||||
|             "license": "MIT" | ||||
|         }, | ||||
| @ -6615,9 +6614,9 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/@types/node": { | ||||
|             "version": "24.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", | ||||
|             "integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==", | ||||
|             "version": "24.0.3", | ||||
|             "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", | ||||
|             "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "undici-types": "~7.8.0" | ||||
| @ -6831,17 +6830,17 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/eslint-plugin": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", | ||||
|             "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", | ||||
|             "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/regexpp": "^4.10.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/type-utils": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/type-utils": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "graphemer": "^1.4.0", | ||||
|                 "ignore": "^7.0.0", | ||||
|                 "natural-compare": "^1.4.0", | ||||
| @ -6855,7 +6854,7 @@ | ||||
|                 "url": "https://opencollective.com/typescript-eslint" | ||||
|             }, | ||||
|             "peerDependencies": { | ||||
|                 "@typescript-eslint/parser": "^8.34.0", | ||||
|                 "@typescript-eslint/parser": "^8.34.1", | ||||
|                 "eslint": "^8.57.0 || ^9.0.0", | ||||
|                 "typescript": ">=4.8.4 <5.9.0" | ||||
|             } | ||||
| @ -6871,16 +6870,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/parser": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", | ||||
|             "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", | ||||
|             "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -6896,14 +6895,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/project-service": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", | ||||
|             "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", | ||||
|             "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/tsconfig-utils": "^8.34.0", | ||||
|                 "@typescript-eslint/types": "^8.34.0", | ||||
|                 "@typescript-eslint/tsconfig-utils": "^8.34.1", | ||||
|                 "@typescript-eslint/types": "^8.34.1", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -6918,14 +6917,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/scope-manager": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", | ||||
|             "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", | ||||
|             "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0" | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -6936,9 +6935,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/tsconfig-utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -6953,14 +6952,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/type-utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "ts-api-utils": "^2.1.0" | ||||
|             }, | ||||
| @ -6977,9 +6976,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/types": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", | ||||
|             "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", | ||||
|             "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
| @ -6991,16 +6990,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", | ||||
|             "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", | ||||
|             "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/project-service": "8.34.0", | ||||
|                 "@typescript-eslint/tsconfig-utils": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.0", | ||||
|                 "@typescript-eslint/project-service": "8.34.1", | ||||
|                 "@typescript-eslint/tsconfig-utils": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/visitor-keys": "8.34.1", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "fast-glob": "^3.3.2", | ||||
|                 "is-glob": "^4.0.3", | ||||
| @ -7020,9 +7019,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { | ||||
|             "version": "2.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", | ||||
|             "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", | ||||
|             "version": "2.0.2", | ||||
|             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", | ||||
|             "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
| @ -7046,16 +7045,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/utils": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", | ||||
|             "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", | ||||
|             "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/eslint-utils": "^4.7.0", | ||||
|                 "@typescript-eslint/scope-manager": "8.34.0", | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.0" | ||||
|                 "@typescript-eslint/scope-manager": "8.34.1", | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "@typescript-eslint/typescript-estree": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -7070,14 +7069,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/visitor-keys": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", | ||||
|             "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", | ||||
|             "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "8.34.0", | ||||
|                 "eslint-visitor-keys": "^4.2.0" | ||||
|                 "@typescript-eslint/types": "8.34.1", | ||||
|                 "eslint-visitor-keys": "^4.2.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -7291,9 +7290,10 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/acorn": { | ||||
|             "version": "8.14.0", | ||||
|             "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", | ||||
|             "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", | ||||
|             "version": "8.15.0", | ||||
|             "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", | ||||
|             "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", | ||||
|             "license": "MIT", | ||||
|             "bin": { | ||||
|                 "acorn": "bin/acorn" | ||||
|             }, | ||||
| @ -12218,19 +12218,19 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/eslint": { | ||||
|             "version": "9.28.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", | ||||
|             "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", | ||||
|             "version": "9.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", | ||||
|             "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", | ||||
|             "devOptional": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/eslint-utils": "^4.2.0", | ||||
|                 "@eslint-community/regexpp": "^4.12.1", | ||||
|                 "@eslint/config-array": "^0.20.0", | ||||
|                 "@eslint/config-array": "^0.20.1", | ||||
|                 "@eslint/config-helpers": "^0.2.1", | ||||
|                 "@eslint/core": "^0.14.0", | ||||
|                 "@eslint/eslintrc": "^3.3.1", | ||||
|                 "@eslint/js": "9.28.0", | ||||
|                 "@eslint/js": "9.29.0", | ||||
|                 "@eslint/plugin-kit": "^0.3.1", | ||||
|                 "@humanfs/node": "^0.16.6", | ||||
|                 "@humanwhocodes/module-importer": "^1.0.1", | ||||
| @ -12242,9 +12242,9 @@ | ||||
|                 "cross-spawn": "^7.0.6", | ||||
|                 "debug": "^4.3.2", | ||||
|                 "escape-string-regexp": "^4.0.0", | ||||
|                 "eslint-scope": "^8.3.0", | ||||
|                 "eslint-visitor-keys": "^4.2.0", | ||||
|                 "espree": "^10.3.0", | ||||
|                 "eslint-scope": "^8.4.0", | ||||
|                 "eslint-visitor-keys": "^4.2.1", | ||||
|                 "espree": "^10.4.0", | ||||
|                 "esquery": "^1.5.0", | ||||
|                 "esutils": "^2.0.2", | ||||
|                 "fast-deep-equal": "^3.1.3", | ||||
| @ -12558,9 +12558,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/eslint/node_modules/eslint-scope": { | ||||
|             "version": "8.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", | ||||
|             "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", | ||||
|             "version": "8.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", | ||||
|             "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", | ||||
|             "devOptional": true, | ||||
|             "license": "BSD-2-Clause", | ||||
|             "dependencies": { | ||||
| @ -12575,9 +12575,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/eslint/node_modules/eslint-visitor-keys": { | ||||
|             "version": "4.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", | ||||
|             "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", | ||||
|             "version": "4.2.1", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", | ||||
|             "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", | ||||
|             "devOptional": true, | ||||
|             "license": "Apache-2.0", | ||||
|             "engines": { | ||||
| @ -12706,15 +12706,15 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/espree": { | ||||
|             "version": "10.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", | ||||
|             "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", | ||||
|             "version": "10.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", | ||||
|             "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", | ||||
|             "devOptional": true, | ||||
|             "license": "BSD-2-Clause", | ||||
|             "dependencies": { | ||||
|                 "acorn": "^8.14.0", | ||||
|                 "acorn": "^8.15.0", | ||||
|                 "acorn-jsx": "^5.3.2", | ||||
|                 "eslint-visitor-keys": "^4.2.0" | ||||
|                 "eslint-visitor-keys": "^4.2.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
| @ -12724,9 +12724,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/espree/node_modules/eslint-visitor-keys": { | ||||
|             "version": "4.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", | ||||
|             "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", | ||||
|             "version": "4.2.1", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", | ||||
|             "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", | ||||
|             "devOptional": true, | ||||
|             "license": "Apache-2.0", | ||||
|             "engines": { | ||||
| @ -20672,9 +20672,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/postcss": { | ||||
|             "version": "8.5.5", | ||||
|             "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", | ||||
|             "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==", | ||||
|             "version": "8.5.6", | ||||
|             "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", | ||||
|             "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", | ||||
|             "funding": [ | ||||
|                 { | ||||
|                     "type": "opencollective", | ||||
| @ -26387,15 +26387,15 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/typescript-eslint": { | ||||
|             "version": "8.34.0", | ||||
|             "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", | ||||
|             "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", | ||||
|             "version": "8.34.1", | ||||
|             "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", | ||||
|             "integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/eslint-plugin": "8.34.0", | ||||
|                 "@typescript-eslint/parser": "8.34.0", | ||||
|                 "@typescript-eslint/utils": "8.34.0" | ||||
|                 "@typescript-eslint/eslint-plugin": "8.34.1", | ||||
|                 "@typescript-eslint/parser": "8.34.1", | ||||
|                 "@typescript-eslint/utils": "8.34.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
|  | ||||
| @ -37,7 +37,7 @@ | ||||
|         "clsx": "^2.1.1", | ||||
|         "docusaurus-plugin-openapi-docs": "^4.4.0", | ||||
|         "docusaurus-theme-openapi-docs": "^4.4.0", | ||||
|         "postcss": "^8.5.5", | ||||
|         "postcss": "^8.5.6", | ||||
|         "prism-react-renderer": "^2.4.1", | ||||
|         "react": "^18.3.1", | ||||
|         "react-before-after-slider-component": "^1.1.8", | ||||
| @ -50,26 +50,26 @@ | ||||
|         "@docusaurus/module-type-aliases": "^3.7.0", | ||||
|         "@docusaurus/tsconfig": "^3.7.0", | ||||
|         "@docusaurus/types": "^3.7.0", | ||||
|         "@eslint/js": "^9.28.0", | ||||
|         "@eslint/js": "^9.29.0", | ||||
|         "@goauthentik/eslint-config": "^1.0.5", | ||||
|         "@goauthentik/prettier-config": "^1.0.5", | ||||
|         "@goauthentik/tsconfig": "^1.0.4", | ||||
|         "@trivago/prettier-plugin-sort-imports": "^5.2.2", | ||||
|         "@types/lodash": "^4.17.17", | ||||
|         "@types/node": "^24.0.1", | ||||
|         "@types/lodash": "^4.17.18", | ||||
|         "@types/node": "^24.0.3", | ||||
|         "@types/postman-collection": "^3.5.11", | ||||
|         "@types/react": "^18.3.22", | ||||
|         "@types/semver": "^7.7.0", | ||||
|         "@typescript-eslint/eslint-plugin": "^8.34.0", | ||||
|         "@typescript-eslint/parser": "^8.34.0", | ||||
|         "@typescript-eslint/eslint-plugin": "^8.34.1", | ||||
|         "@typescript-eslint/parser": "^8.34.1", | ||||
|         "cross-env": "^7.0.3", | ||||
|         "eslint": "^9.28.0", | ||||
|         "eslint": "^9.29.0", | ||||
|         "fast-glob": "^3.3.3", | ||||
|         "npm-run-all": "^4.1.5", | ||||
|         "prettier": "^3.5.3", | ||||
|         "prettier-plugin-packagejson": "^2.5.15", | ||||
|         "typescript": "^5.8.3", | ||||
|         "typescript-eslint": "^8.34.0" | ||||
|         "typescript-eslint": "^8.34.1" | ||||
|     }, | ||||
|     "optionalDependencies": { | ||||
|         "@rspack/binding-darwin-arm64": "1.3.15", | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	