Compare commits
	
		
			11 Commits
		
	
	
		
			version/20
			...
			version-20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fb0a88f2cf | |||
| 4d8d405e70 | |||
| 1d5f399b61 | |||
| bb575fcc10 | |||
| 13fd1afbb9 | |||
| f059b998cc | |||
| 3f48202dfe | |||
| 2a3ebb616b | |||
| ceab1f732d | |||
| 01d2cce9ca | |||
| 72f85defb8 | 
| @ -1,5 +1,5 @@ | |||||||
| [bumpversion] | [bumpversion] | ||||||
| current_version = 2023.3.0 | current_version = 2023.3.1 | ||||||
| tag = True | tag = True | ||||||
| commit = True | commit = True | ||||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) | parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) | ||||||
|  | |||||||
| @ -12,3 +12,6 @@ indent_size = 2 | |||||||
|  |  | ||||||
| [*.{yaml,yml}] | [*.{yaml,yml}] | ||||||
| indent_size = 2 | indent_size = 2 | ||||||
|  |  | ||||||
|  | [*.go] | ||||||
|  | indent_style = tab | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| from os import environ | from os import environ | ||||||
| from typing import Optional | from typing import Optional | ||||||
|  |  | ||||||
| __version__ = "2023.3.0" | __version__ = "2023.3.1" | ||||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -19,10 +19,8 @@ class Command(BaseCommand): | |||||||
|         for blueprint_path in options.get("blueprints", []): |         for blueprint_path in options.get("blueprints", []): | ||||||
|             content = BlueprintInstance(path=blueprint_path).retrieve() |             content = BlueprintInstance(path=blueprint_path).retrieve() | ||||||
|             importer = Importer(content) |             importer = Importer(content) | ||||||
|             valid, logs = importer.validate() |             valid, _ = importer.validate() | ||||||
|             if not valid: |             if not valid: | ||||||
|                 for log in logs: |  | ||||||
|                     getattr(LOGGER, log.pop("log_level"))(**log) |  | ||||||
|                 self.stderr.write("blueprint invalid") |                 self.stderr.write("blueprint invalid") | ||||||
|                 sys_exit(1) |                 sys_exit(1) | ||||||
|             importer.apply() |             importer.apply() | ||||||
|  | |||||||
| @ -40,6 +40,10 @@ from authentik.lib.models import SerializerModel | |||||||
| from authentik.outposts.models import OutpostServiceConnection | from authentik.outposts.models import OutpostServiceConnection | ||||||
| from authentik.policies.models import Policy, PolicyBindingModel | from authentik.policies.models import Policy, PolicyBindingModel | ||||||
|  |  | ||||||
|  | # Context set when the serializer is created in a blueprint context | ||||||
|  | # Update website/developer-docs/blueprints/v1/models.md when used | ||||||
|  | SERIALIZER_CONTEXT_BLUEPRINT = "blueprint_entry" | ||||||
|  |  | ||||||
|  |  | ||||||
| def is_model_allowed(model: type[Model]) -> bool: | def is_model_allowed(model: type[Model]) -> bool: | ||||||
|     """Check if model is allowed""" |     """Check if model is allowed""" | ||||||
| @ -158,7 +162,12 @@ class Importer: | |||||||
|             raise EntryInvalidError(f"Model {model} not allowed") |             raise EntryInvalidError(f"Model {model} not allowed") | ||||||
|         if issubclass(model, BaseMetaModel): |         if issubclass(model, BaseMetaModel): | ||||||
|             serializer_class: type[Serializer] = model.serializer() |             serializer_class: type[Serializer] = model.serializer() | ||||||
|             serializer = serializer_class(data=entry.get_attrs(self.__import)) |             serializer = serializer_class( | ||||||
|  |                 data=entry.get_attrs(self.__import), | ||||||
|  |                 context={ | ||||||
|  |                     SERIALIZER_CONTEXT_BLUEPRINT: entry, | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|             try: |             try: | ||||||
|                 serializer.is_valid(raise_exception=True) |                 serializer.is_valid(raise_exception=True) | ||||||
|             except ValidationError as exc: |             except ValidationError as exc: | ||||||
| @ -217,7 +226,12 @@ class Importer: | |||||||
|         always_merger.merge(full_data, updated_identifiers) |         always_merger.merge(full_data, updated_identifiers) | ||||||
|         serializer_kwargs["data"] = full_data |         serializer_kwargs["data"] = full_data | ||||||
|  |  | ||||||
|         serializer: Serializer = model().serializer(**serializer_kwargs) |         serializer: Serializer = model().serializer( | ||||||
|  |             context={ | ||||||
|  |                 SERIALIZER_CONTEXT_BLUEPRINT: entry, | ||||||
|  |             }, | ||||||
|  |             **serializer_kwargs, | ||||||
|  |         ) | ||||||
|         try: |         try: | ||||||
|             serializer.is_valid(raise_exception=True) |             serializer.is_valid(raise_exception=True) | ||||||
|         except ValidationError as exc: |         except ValidationError as exc: | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ from rest_framework.viewsets import ModelViewSet | |||||||
| from authentik.api.authorization import OwnerSuperuserPermissions | from authentik.api.authorization import OwnerSuperuserPermissions | ||||||
| from authentik.api.decorators import permission_required | from authentik.api.decorators import permission_required | ||||||
| from authentik.blueprints.api import ManagedSerializer | from authentik.blueprints.api import ManagedSerializer | ||||||
|  | from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT | ||||||
| from authentik.core.api.used_by import UsedByMixin | from authentik.core.api.used_by import UsedByMixin | ||||||
| from authentik.core.api.users import UserSerializer | from authentik.core.api.users import UserSerializer | ||||||
| from authentik.core.api.utils import PassiveSerializer | from authentik.core.api.utils import PassiveSerializer | ||||||
| @ -29,6 +30,11 @@ class TokenSerializer(ManagedSerializer, ModelSerializer): | |||||||
|  |  | ||||||
|     user_obj = UserSerializer(required=False, source="user", read_only=True) |     user_obj = UserSerializer(required=False, source="user", read_only=True) | ||||||
|  |  | ||||||
|  |     def __init__(self, *args, **kwargs) -> None: | ||||||
|  |         super().__init__(*args, **kwargs) | ||||||
|  |         if SERIALIZER_CONTEXT_BLUEPRINT in self.context: | ||||||
|  |             self.fields["key"] = CharField() | ||||||
|  |  | ||||||
|     def validate(self, attrs: dict[Any, str]) -> dict[Any, str]: |     def validate(self, attrs: dict[Any, str]) -> dict[Any, str]: | ||||||
|         """Ensure only API or App password tokens are created.""" |         """Ensure only API or App password tokens are created.""" | ||||||
|         request: Request = self.context.get("request") |         request: Request = self.context.get("request") | ||||||
|  | |||||||
| @ -355,6 +355,62 @@ class TestAuthorize(OAuthTestCase): | |||||||
|                 delta=5, |                 delta=5, | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |     def test_full_fragment_code(self): | ||||||
|  |         """Test full authorization""" | ||||||
|  |         flow = create_test_flow() | ||||||
|  |         provider: OAuth2Provider = OAuth2Provider.objects.create( | ||||||
|  |             name=generate_id(), | ||||||
|  |             client_id="test", | ||||||
|  |             client_secret=generate_key(), | ||||||
|  |             authorization_flow=flow, | ||||||
|  |             redirect_uris="http://localhost", | ||||||
|  |             signing_key=self.keypair, | ||||||
|  |         ) | ||||||
|  |         Application.objects.create(name="app", slug="app", provider=provider) | ||||||
|  |         state = generate_id() | ||||||
|  |         user = create_test_admin_user() | ||||||
|  |         self.client.force_login(user) | ||||||
|  |         with patch( | ||||||
|  |             "authentik.providers.oauth2.id_token.get_login_event", | ||||||
|  |             MagicMock( | ||||||
|  |                 return_value=Event( | ||||||
|  |                     action=EventAction.LOGIN, | ||||||
|  |                     context={PLAN_CONTEXT_METHOD: "password"}, | ||||||
|  |                     created=now(), | ||||||
|  |                 ) | ||||||
|  |             ), | ||||||
|  |         ): | ||||||
|  |             # Step 1, initiate params and get redirect to flow | ||||||
|  |             self.client.get( | ||||||
|  |                 reverse("authentik_providers_oauth2:authorize"), | ||||||
|  |                 data={ | ||||||
|  |                     "response_type": "code", | ||||||
|  |                     "response_mode": "fragment", | ||||||
|  |                     "client_id": "test", | ||||||
|  |                     "state": state, | ||||||
|  |                     "scope": "openid", | ||||||
|  |                     "redirect_uri": "http://localhost", | ||||||
|  |                     "nonce": generate_id(), | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|  |             response = self.client.get( | ||||||
|  |                 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), | ||||||
|  |             ) | ||||||
|  |             code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first() | ||||||
|  |             self.assertJSONEqual( | ||||||
|  |                 response.content.decode(), | ||||||
|  |                 { | ||||||
|  |                     "component": "xak-flow-redirect", | ||||||
|  |                     "type": ChallengeTypes.REDIRECT.value, | ||||||
|  |                     "to": (f"http://localhost#code={code.code}" f"&state={state}"), | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|  |             self.assertAlmostEqual( | ||||||
|  |                 code.expires.timestamp() - now().timestamp(), | ||||||
|  |                 timedelta_from_string(provider.access_code_validity).total_seconds(), | ||||||
|  |                 delta=5, | ||||||
|  |             ) | ||||||
|  |  | ||||||
|     def test_full_form_post_id_token(self): |     def test_full_form_post_id_token(self): | ||||||
|         """Test full authorization (form_post response)""" |         """Test full authorization (form_post response)""" | ||||||
|         flow = create_test_flow() |         flow = create_test_flow() | ||||||
|  | |||||||
| @ -514,6 +514,11 @@ class OAuthFulfillmentStage(StageView): | |||||||
|                 return urlunsplit(uri) |                 return urlunsplit(uri) | ||||||
|  |  | ||||||
|             if self.params.response_mode == ResponseMode.FRAGMENT: |             if self.params.response_mode == ResponseMode.FRAGMENT: | ||||||
|  |                 query_fragment = {} | ||||||
|  |                 if self.params.grant_type in [GrantTypes.AUTHORIZATION_CODE]: | ||||||
|  |                     query_fragment["code"] = code.code | ||||||
|  |                     query_fragment["state"] = [str(self.params.state) if self.params.state else ""] | ||||||
|  |                 else: | ||||||
|                     query_fragment = self.create_implicit_response(code) |                     query_fragment = self.create_implicit_response(code) | ||||||
|  |  | ||||||
|                 uri = uri._replace( |                 uri = uri._replace( | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ services: | |||||||
|     volumes: |     volumes: | ||||||
|       - redis:/data |       - redis:/data | ||||||
|   server: |   server: | ||||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.3.0} |     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.3.1} | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     command: server |     command: server | ||||||
|     environment: |     environment: | ||||||
| @ -50,7 +50,7 @@ services: | |||||||
|       - "${AUTHENTIK_PORT_HTTP:-9000}:9000" |       - "${AUTHENTIK_PORT_HTTP:-9000}:9000" | ||||||
|       - "${AUTHENTIK_PORT_HTTPS:-9443}:9443" |       - "${AUTHENTIK_PORT_HTTPS:-9443}:9443" | ||||||
|   worker: |   worker: | ||||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.3.0} |     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.3.1} | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     command: worker |     command: worker | ||||||
|     environment: |     environment: | ||||||
|  | |||||||
| @ -29,4 +29,4 @@ func UserAgent() string { | |||||||
| 	return fmt.Sprintf("authentik@%s", FullVersion()) | 	return fmt.Sprintf("authentik@%s", FullVersion()) | ||||||
| } | } | ||||||
|  |  | ||||||
| const VERSION = "2023.3.0" | const VERSION = "2023.3.1" | ||||||
|  | |||||||
| @ -30,11 +30,15 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry { | |||||||
| 	// Only append attributes that don't already exist | 	// Only append attributes that don't already exist | ||||||
| 	// TODO: Remove in 2023.3 | 	// TODO: Remove in 2023.3 | ||||||
| 	for _, rawAttr := range rawAttrs { | 	for _, rawAttr := range rawAttrs { | ||||||
|  | 		exists := false | ||||||
| 		for _, attr := range attrs { | 		for _, attr := range attrs { | ||||||
| 			if !strings.EqualFold(attr.Name, rawAttr.Name) { | 			if strings.EqualFold(attr.Name, rawAttr.Name) { | ||||||
| 				attrs = append(attrs, rawAttr) | 				exists = true | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		if !exists { | ||||||
|  | 			attrs = append(attrs, rawAttr) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if u.IsActive == nil { | 	if u.IsActive == nil { | ||||||
|  | |||||||
| @ -36,11 +36,15 @@ func (lg *LDAPGroup) Entry() *ldap.Entry { | |||||||
| 	// Only append attributes that don't already exist | 	// Only append attributes that don't already exist | ||||||
| 	// TODO: Remove in 2023.3 | 	// TODO: Remove in 2023.3 | ||||||
| 	for _, rawAttr := range rawAttrs { | 	for _, rawAttr := range rawAttrs { | ||||||
|  | 		exists := false | ||||||
| 		for _, attr := range attrs { | 		for _, attr := range attrs { | ||||||
| 			if !strings.EqualFold(attr.Name, rawAttr.Name) { | 			if strings.EqualFold(attr.Name, rawAttr.Name) { | ||||||
| 				attrs = append(attrs, rawAttr) | 				exists = true | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		if !exists { | ||||||
|  | 			attrs = append(attrs, rawAttr) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	objectClass := []string{constants.OCGroup, constants.OCGroupOfUniqueNames, constants.OCGroupOfNames, constants.OCAKGroup, constants.OCPosixGroup} | 	objectClass := []string{constants.OCGroup, constants.OCGroupOfUniqueNames, constants.OCGroupOfNames, constants.OCAKGroup, constants.OCPosixGroup} | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ package application | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	log "github.com/sirupsen/logrus" | 	log "github.com/sirupsen/logrus" | ||||||
| 	"goauthentik.io/api/v3" | 	"goauthentik.io/api/v3" | ||||||
| @ -31,29 +30,29 @@ func updateURL(rawUrl string, scheme string, host string) string { | |||||||
| func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bool) OIDCEndpoint { | func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bool) OIDCEndpoint { | ||||||
| 	authUrl := p.OidcConfiguration.AuthorizationEndpoint | 	authUrl := p.OidcConfiguration.AuthorizationEndpoint | ||||||
| 	endUrl := p.OidcConfiguration.EndSessionEndpoint | 	endUrl := p.OidcConfiguration.EndSessionEndpoint | ||||||
| 	tokenUrl := p.OidcConfiguration.TokenEndpoint |  | ||||||
| 	jwksUrl := p.OidcConfiguration.JwksUri |  | ||||||
| 	issuer := p.OidcConfiguration.Issuer | 	issuer := p.OidcConfiguration.Issuer | ||||||
| 	if config.Get().AuthentikHostBrowser != "" { |  | ||||||
| 		authUrl = strings.ReplaceAll(authUrl, authentikHost, config.Get().AuthentikHostBrowser) |  | ||||||
| 		endUrl = strings.ReplaceAll(endUrl, authentikHost, config.Get().AuthentikHostBrowser) |  | ||||||
| 		jwksUrl = strings.ReplaceAll(jwksUrl, authentikHost, config.Get().AuthentikHostBrowser) |  | ||||||
| 		issuer = strings.ReplaceAll(issuer, authentikHost, config.Get().AuthentikHostBrowser) |  | ||||||
| 	} |  | ||||||
| 	ep := OIDCEndpoint{ | 	ep := OIDCEndpoint{ | ||||||
| 		Endpoint: oauth2.Endpoint{ | 		Endpoint: oauth2.Endpoint{ | ||||||
| 			AuthURL:   authUrl, | 			AuthURL:   authUrl, | ||||||
| 			TokenURL:  tokenUrl, | 			TokenURL:  p.OidcConfiguration.TokenEndpoint, | ||||||
| 			AuthStyle: oauth2.AuthStyleInParams, | 			AuthStyle: oauth2.AuthStyleInParams, | ||||||
| 		}, | 		}, | ||||||
| 		EndSessionEndpoint: endUrl, | 		EndSessionEndpoint: endUrl, | ||||||
| 		JwksUri:            jwksUrl, | 		JwksUri:            p.OidcConfiguration.JwksUri, | ||||||
| 		TokenIntrospection: p.OidcConfiguration.IntrospectionEndpoint, | 		TokenIntrospection: p.OidcConfiguration.IntrospectionEndpoint, | ||||||
| 		Issuer:             issuer, | 		Issuer:             issuer, | ||||||
| 	} | 	} | ||||||
| 	if !embedded { | 	// For the embedded outpost, we use the configure `authentik_host` for the browser URLs | ||||||
|  | 	// and localhost (which is what we've got from the API) for backchannel URLs | ||||||
|  | 	// | ||||||
|  | 	// For other outposts, when `AUTHENTIK_HOST_BROWSER` is set, we use that for the browser URLs | ||||||
|  | 	// and use what we got from the API for backchannel | ||||||
|  | 	hostBrowser := config.Get().AuthentikHostBrowser | ||||||
|  | 	if !embedded && hostBrowser == "" { | ||||||
| 		return ep | 		return ep | ||||||
| 	} | 	} | ||||||
|  | 	var newHost *url.URL | ||||||
|  | 	if embedded { | ||||||
| 		if authentikHost == "" { | 		if authentikHost == "" { | ||||||
| 			log.Warning("Outpost has localhost/blank API Connection but no authentik_host is configured.") | 			log.Warning("Outpost has localhost/blank API Connection but no authentik_host is configured.") | ||||||
| 			return ep | 			return ep | ||||||
| @ -62,9 +61,24 @@ func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bo | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return ep | 			return ep | ||||||
| 		} | 		} | ||||||
| 	ep.AuthURL = updateURL(authUrl, aku.Scheme, aku.Host) | 		newHost = aku | ||||||
| 	ep.EndSessionEndpoint = updateURL(endUrl, aku.Scheme, aku.Host) | 	} else if hostBrowser != "" { | ||||||
| 	ep.JwksUri = updateURL(jwksUrl, aku.Scheme, aku.Host) | 		aku, err := url.Parse(hostBrowser) | ||||||
| 	ep.Issuer = updateURL(ep.Issuer, aku.Scheme, aku.Host) | 		if err != nil { | ||||||
|  | 			return ep | ||||||
|  | 		} | ||||||
|  | 		newHost = aku | ||||||
|  | 	} | ||||||
|  | 	// Update all browser-accessed URLs to use the new host and scheme | ||||||
|  | 	ep.AuthURL = updateURL(authUrl, newHost.Scheme, newHost.Host) | ||||||
|  | 	ep.EndSessionEndpoint = updateURL(endUrl, newHost.Scheme, newHost.Host) | ||||||
|  | 	// Update issuer to use the same host and scheme, which would normally break as we don't | ||||||
|  | 	// change the token URL here, but the token HTTP transport overwrites the Host header | ||||||
|  | 	// | ||||||
|  | 	// This is only used in embedded outposts as there we can guarantee that the request | ||||||
|  | 	// is routed correctly | ||||||
|  | 	if embedded { | ||||||
|  | 		ep.Issuer = updateURL(ep.Issuer, newHost.Scheme, newHost.Host) | ||||||
|  | 	} | ||||||
| 	return ep | 	return ep | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										88
									
								
								internal/outpost/proxyv2/application/endpoint_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								internal/outpost/proxyv2/application/endpoint_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"goauthentik.io/api/v3" | ||||||
|  | 	"goauthentik.io/internal/config" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestEndpointDefault(t *testing.T) { | ||||||
|  | 	pc := api.ProxyOutpostConfig{ | ||||||
|  | 		OidcConfiguration: api.ProxyOutpostConfigOidcConfiguration{ | ||||||
|  | 			AuthorizationEndpoint: "https://test.goauthentik.io/application/o/authorize/", | ||||||
|  | 			EndSessionEndpoint:    "https://test.goauthentik.io/application/o/test-app/end-session/", | ||||||
|  | 			IntrospectionEndpoint: "https://test.goauthentik.io/application/o/introspect/", | ||||||
|  | 			Issuer:                "https://test.goauthentik.io/application/o/test-app/", | ||||||
|  | 			JwksUri:               "https://test.goauthentik.io/application/o/test-app/jwks/", | ||||||
|  | 			TokenEndpoint:         "https://test.goauthentik.io/application/o/token/", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ep := GetOIDCEndpoint(pc, "https://authentik-host.test.goauthentik.io", false) | ||||||
|  | 	// Standard outpost, non embedded | ||||||
|  | 	// All URLs should use the host that they get from the config | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/authorize/", ep.AuthURL) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/", ep.Issuer) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestEndpointAuthentikHostBrowser(t *testing.T) { | ||||||
|  | 	c := config.Get() | ||||||
|  | 	c.AuthentikHostBrowser = "https://browser.test.goauthentik.io" | ||||||
|  | 	defer func() { | ||||||
|  | 		c.AuthentikHostBrowser = "" | ||||||
|  | 	}() | ||||||
|  | 	pc := api.ProxyOutpostConfig{ | ||||||
|  | 		OidcConfiguration: api.ProxyOutpostConfigOidcConfiguration{ | ||||||
|  | 			AuthorizationEndpoint: "https://test.goauthentik.io/application/o/authorize/", | ||||||
|  | 			EndSessionEndpoint:    "https://test.goauthentik.io/application/o/test-app/end-session/", | ||||||
|  | 			IntrospectionEndpoint: "https://test.goauthentik.io/application/o/introspect/", | ||||||
|  | 			Issuer:                "https://test.goauthentik.io/application/o/test-app/", | ||||||
|  | 			JwksUri:               "https://test.goauthentik.io/application/o/test-app/jwks/", | ||||||
|  | 			TokenEndpoint:         "https://test.goauthentik.io/application/o/token/", | ||||||
|  | 			UserinfoEndpoint:      "https://test.goauthentik.io/application/o/userinfo/", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ep := GetOIDCEndpoint(pc, "https://authentik-host.test.goauthentik.io", false) | ||||||
|  | 	// Standard outpost, with AUTHENTIK_HOST_BROWSER set | ||||||
|  | 	// Only the authorize/end session URLs should be changed | ||||||
|  | 	assert.Equal(t, "https://browser.test.goauthentik.io/application/o/authorize/", ep.AuthURL) | ||||||
|  | 	assert.Equal(t, "https://browser.test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/", ep.Issuer) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestEndpointEmbedded(t *testing.T) { | ||||||
|  | 	pc := api.ProxyOutpostConfig{ | ||||||
|  | 		OidcConfiguration: api.ProxyOutpostConfigOidcConfiguration{ | ||||||
|  | 			AuthorizationEndpoint: "https://test.goauthentik.io/application/o/authorize/", | ||||||
|  | 			EndSessionEndpoint:    "https://test.goauthentik.io/application/o/test-app/end-session/", | ||||||
|  | 			IntrospectionEndpoint: "https://test.goauthentik.io/application/o/introspect/", | ||||||
|  | 			Issuer:                "https://test.goauthentik.io/application/o/test-app/", | ||||||
|  | 			JwksUri:               "https://test.goauthentik.io/application/o/test-app/jwks/", | ||||||
|  | 			TokenEndpoint:         "https://test.goauthentik.io/application/o/token/", | ||||||
|  | 			UserinfoEndpoint:      "https://test.goauthentik.io/application/o/userinfo/", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ep := GetOIDCEndpoint(pc, "https://authentik-host.test.goauthentik.io", true) | ||||||
|  | 	// Embedded outpost | ||||||
|  | 	// Browser URLs should use the config of "authentik_host", everything else can use what's | ||||||
|  | 	// received from the API endpoint | ||||||
|  | 	// Token URL is an exception since it's sent via a special HTTP transport that overrides the | ||||||
|  | 	// HTTP Host header, to make sure it's the same value as the issuer | ||||||
|  | 	assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/authorize/", ep.AuthURL) | ||||||
|  | 	assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/test-app/", ep.Issuer) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri) | ||||||
|  | 	assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection) | ||||||
|  | } | ||||||
| @ -105,7 +105,7 @@ filterwarnings = [ | |||||||
|  |  | ||||||
| [tool.poetry] | [tool.poetry] | ||||||
| name = "authentik" | name = "authentik" | ||||||
| version = "2023.3.0" | version = "2023.3.1" | ||||||
| description = "" | description = "" | ||||||
| authors = ["authentik Team <hello@goauthentik.io>"] | authors = ["authentik Team <hello@goauthentik.io>"] | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| openapi: 3.0.3 | openapi: 3.0.3 | ||||||
| info: | info: | ||||||
|   title: authentik |   title: authentik | ||||||
|   version: 2023.3.0 |   version: 2023.3.1 | ||||||
|   description: Making authentication simple. |   description: Making authentication simple. | ||||||
|   contact: |   contact: | ||||||
|     email: hello@goauthentik.io |     email: hello@goauthentik.io | ||||||
|  | |||||||
							
								
								
									
										7668
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7668
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -74,8 +74,6 @@ | |||||||
|         "@lingui/detect-locale": "^3.17.2", |         "@lingui/detect-locale": "^3.17.2", | ||||||
|         "@lingui/macro": "^3.17.2", |         "@lingui/macro": "^3.17.2", | ||||||
|         "@patternfly/patternfly": "^4.224.2", |         "@patternfly/patternfly": "^4.224.2", | ||||||
|         "@polymer/iron-form": "^3.0.1", |  | ||||||
|         "@polymer/paper-input": "^3.2.1", |  | ||||||
|         "@rollup/plugin-babel": "^6.0.3", |         "@rollup/plugin-babel": "^6.0.3", | ||||||
|         "@rollup/plugin-commonjs": "^24.0.1", |         "@rollup/plugin-commonjs": "^24.0.1", | ||||||
|         "@rollup/plugin-node-resolve": "^15.0.1", |         "@rollup/plugin-node-resolve": "^15.0.1", | ||||||
|  | |||||||
| @ -61,6 +61,9 @@ export class LDAPSyncStatusChart extends AKChart<SyncStatus[]> { | |||||||
|                             metrics.healthy += 1; |                             metrics.healthy += 1; | ||||||
|                         } |                         } | ||||||
|                     }); |                     }); | ||||||
|  |                     if (health.length < 1) { | ||||||
|  |                         metrics.unsynced += 1; | ||||||
|  |                     } | ||||||
|                 } catch { |                 } catch { | ||||||
|                     metrics.unsynced += 1; |                     metrics.unsynced += 1; | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { KeyUnknown } from "@goauthentik/elements/forms/Form"; | import { KeyUnknown } from "@goauthentik/elements/forms/Form"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| @ -60,7 +61,7 @@ export class TypeOAuthCodeApplicationWizardPage extends WizardFormPage { | |||||||
|                         return flows.results; |                         return flows.results; | ||||||
|                     }} |                     }} | ||||||
|                     .renderElement=${(flow: Flow): string => { |                     .renderElement=${(flow: Flow): string => { | ||||||
|                         return flow.slug; |                         return RenderFlowOption(flow); | ||||||
|                     }} |                     }} | ||||||
|                     .renderDescription=${(flow: Flow): TemplateResult => { |                     .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                         return html`${flow.name}`; |                         return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,6 +1,10 @@ | |||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
|  |  | ||||||
| import { FlowDesignationEnum, LayoutEnum } from "@goauthentik/api"; | import { Flow, FlowDesignationEnum, LayoutEnum } from "@goauthentik/api"; | ||||||
|  |  | ||||||
|  | export function RenderFlowOption(flow: Flow): string { | ||||||
|  |     return `${flow.slug} (${flow.name})`; | ||||||
|  | } | ||||||
|  |  | ||||||
| export function DesignationToLabel(designation: FlowDesignationEnum): string { | export function DesignationToLabel(designation: FlowDesignationEnum): string { | ||||||
|     switch (designation) { |     switch (designation) { | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config"; | ||||||
| import { first } from "@goauthentik/common/utils"; | import { first } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| @ -9,9 +10,8 @@ import "@goauthentik/elements/forms/SearchSelect"; | |||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
|  |  | ||||||
| import { TemplateResult, html } from "lit"; | import { TemplateResult, html } from "lit"; | ||||||
| import { customElement } from "lit/decorators.js"; | import { customElement, state } from "lit/decorators.js"; | ||||||
| import { ifDefined } from "lit/directives/if-defined.js"; | import { ifDefined } from "lit/directives/if-defined.js"; | ||||||
| import { until } from "lit/directives/until.js"; |  | ||||||
|  |  | ||||||
| import { | import { | ||||||
|     CertificateKeyPair, |     CertificateKeyPair, | ||||||
| @ -19,6 +19,7 @@ import { | |||||||
|     CoreGroupsListRequest, |     CoreGroupsListRequest, | ||||||
|     CryptoApi, |     CryptoApi, | ||||||
|     CryptoCertificatekeypairsListRequest, |     CryptoCertificatekeypairsListRequest, | ||||||
|  |     CurrentTenant, | ||||||
|     Flow, |     Flow, | ||||||
|     FlowsApi, |     FlowsApi, | ||||||
|     FlowsInstancesListDesignationEnum, |     FlowsInstancesListDesignationEnum, | ||||||
| @ -31,10 +32,14 @@ import { | |||||||
|  |  | ||||||
| @customElement("ak-provider-ldap-form") | @customElement("ak-provider-ldap-form") | ||||||
| export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> { | export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> { | ||||||
|     loadInstance(pk: number): Promise<LDAPProvider> { |     @state() | ||||||
|         return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({ |     tenant?: CurrentTenant; | ||||||
|  |     async loadInstance(pk: number): Promise<LDAPProvider> { | ||||||
|  |         const provider = await new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({ | ||||||
|             id: pk, |             id: pk, | ||||||
|         }); |         }); | ||||||
|  |         this.tenant = await tenant(); | ||||||
|  |         return provider; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getSuccessMessage(): string { |     getSuccessMessage(): string { | ||||||
| @ -74,26 +79,20 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> { | |||||||
|                 ?required=${true} |                 ?required=${true} | ||||||
|                 name="authorizationFlow" |                 name="authorizationFlow" | ||||||
|             > |             > | ||||||
|                 ${until( |  | ||||||
|                     tenant().then((t) => { |  | ||||||
|                         return html` |  | ||||||
|                 <ak-search-select |                 <ak-search-select | ||||||
|                     .fetchObjects=${async (query?: string): Promise<Flow[]> => { |                     .fetchObjects=${async (query?: string): Promise<Flow[]> => { | ||||||
|                         const args: FlowsInstancesListRequest = { |                         const args: FlowsInstancesListRequest = { | ||||||
|                             ordering: "slug", |                             ordering: "slug", | ||||||
|                                         designation: |                             designation: FlowsInstancesListDesignationEnum.Authentication, | ||||||
|                                             FlowsInstancesListDesignationEnum.Authentication, |  | ||||||
|                         }; |                         }; | ||||||
|                         if (query !== undefined) { |                         if (query !== undefined) { | ||||||
|                             args.search = query; |                             args.search = query; | ||||||
|                         } |                         } | ||||||
|                                     const flows = await new FlowsApi( |                         const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args); | ||||||
|                                         DEFAULT_CONFIG, |  | ||||||
|                                     ).flowsInstancesList(args); |  | ||||||
|                         return flows.results; |                         return flows.results; | ||||||
|                     }} |                     }} | ||||||
|                     .renderElement=${(flow: Flow): string => { |                     .renderElement=${(flow: Flow): string => { | ||||||
|                                     return flow.name; |                         return RenderFlowOption(flow); | ||||||
|                     }} |                     }} | ||||||
|                     .renderDescription=${(flow: Flow): TemplateResult => { |                     .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                         return html`${flow.slug}`; |                         return html`${flow.slug}`; | ||||||
| @ -102,7 +101,7 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> { | |||||||
|                         return flow?.pk; |                         return flow?.pk; | ||||||
|                     }} |                     }} | ||||||
|                     .selected=${(flow: Flow): boolean => { |                     .selected=${(flow: Flow): boolean => { | ||||||
|                                     let selected = flow.pk === t.flowAuthentication; |                         let selected = flow.pk === this.tenant?.flowAuthentication; | ||||||
|                         if (this.instance?.authorizationFlow === flow.pk) { |                         if (this.instance?.authorizationFlow === flow.pk) { | ||||||
|                             selected = true; |                             selected = true; | ||||||
|                         } |                         } | ||||||
| @ -110,10 +109,6 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> { | |||||||
|                     }} |                     }} | ||||||
|                 > |                 > | ||||||
|                 </ak-search-select> |                 </ak-search-select> | ||||||
|                         `; |  | ||||||
|                     }), |  | ||||||
|                     html`<option>${t`Loading...`}</option>`, |  | ||||||
|                 )} |  | ||||||
|                 <p class="pf-c-form__helper-text"> |                 <p class="pf-c-form__helper-text"> | ||||||
|                     ${t`Flow used for users to authenticate. Currently only identification and password stages are supported.`} |                     ${t`Flow used for users to authenticate. Currently only identification and password stages are supported.`} | ||||||
|                 </p> |                 </p> | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { first, randomString } from "@goauthentik/common/utils"; | import { first, randomString } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| @ -96,7 +97,7 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> { | |||||||
|                         return flows.results; |                         return flows.results; | ||||||
|                     }} |                     }} | ||||||
|                     .renderElement=${(flow: Flow): string => { |                     .renderElement=${(flow: Flow): string => { | ||||||
|                         return flow.slug; |                         return RenderFlowOption(flow); | ||||||
|                     }} |                     }} | ||||||
|                     .renderDescription=${(flow: Flow): TemplateResult => { |                     .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                         return html`${flow.name}`; |                         return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { first } from "@goauthentik/common/utils"; | import { first } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| @ -318,7 +319,7 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> { | |||||||
|                         return flows.results; |                         return flows.results; | ||||||
|                     }} |                     }} | ||||||
|                     .renderElement=${(flow: Flow): string => { |                     .renderElement=${(flow: Flow): string => { | ||||||
|                         return flow.slug; |                         return RenderFlowOption(flow); | ||||||
|                     }} |                     }} | ||||||
|                     .renderDescription=${(flow: Flow): TemplateResult => { |                     .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                         return html`${flow.name}`; |                         return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| @ -88,7 +89,7 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> { | |||||||
|                         return flows.results; |                         return flows.results; | ||||||
|                     }} |                     }} | ||||||
|                     .renderElement=${(flow: Flow): string => { |                     .renderElement=${(flow: Flow): string => { | ||||||
|                         return flow.slug; |                         return RenderFlowOption(flow); | ||||||
|                     }} |                     }} | ||||||
|                     .renderDescription=${(flow: Flow): TemplateResult => { |                     .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                         return html`${flow.name}`; |                         return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { SentryIgnoredError } from "@goauthentik/common/errors"; | import { SentryIgnoredError } from "@goauthentik/common/errors"; | ||||||
| import { Form } from "@goauthentik/elements/forms/Form"; | import { Form } from "@goauthentik/elements/forms/Form"; | ||||||
| @ -59,7 +60,7 @@ export class SAMLProviderImportForm extends Form<SAMLProvider> { | |||||||
|                         return flows.results; |                         return flows.results; | ||||||
|                     }} |                     }} | ||||||
|                     .renderElement=${(flow: Flow): string => { |                     .renderElement=${(flow: Flow): string => { | ||||||
|                         return flow.slug; |                         return RenderFlowOption(flow); | ||||||
|                     }} |                     }} | ||||||
|                     .renderDescription=${(flow: Flow): TemplateResult => { |                     .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                         return html`${flow.name}`; |                         return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; | import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; | ||||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||||
| import { first } from "@goauthentik/common/utils"; | import { first } from "@goauthentik/common/utils"; | ||||||
| @ -431,7 +432,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
| @ -477,7 +478,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; | import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; | ||||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||||
| import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex"; | import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex"; | ||||||
| @ -364,7 +365,7 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
| @ -410,7 +411,7 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; | import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; | ||||||
| import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; | ||||||
| import { first } from "@goauthentik/common/utils"; | import { first } from "@goauthentik/common/utils"; | ||||||
| @ -496,7 +497,7 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
| @ -540,7 +541,7 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
| @ -586,7 +587,7 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { first } from "@goauthentik/common/utils"; | import { first } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| @ -146,7 +147,7 @@ export class AuthenticatorDuoStageForm extends ModelForm<AuthenticatorDuoStage, | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { first } from "@goauthentik/common/utils"; | import { first } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| @ -292,7 +293,7 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage, | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { first } from "@goauthentik/common/utils"; | import { first } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| @ -93,7 +94,7 @@ export class AuthenticatorStaticStageForm extends ModelForm<AuthenticatorStaticS | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| @ -98,7 +99,7 @@ export class AuthenticatorTOTPStageForm extends ModelForm<AuthenticatorTOTPStage | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; | import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; | ||||||
| @ -162,7 +163,7 @@ export class AuthenticateWebAuthnStageForm extends ModelForm<AuthenticateWebAuth | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { first, groupBy } from "@goauthentik/common/utils"; | import { first, groupBy } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| @ -265,7 +266,7 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { dateTimeLocal, first } from "@goauthentik/common/utils"; | import { dateTimeLocal, first } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/CodeMirror"; | import "@goauthentik/elements/CodeMirror"; | ||||||
| @ -88,7 +89,7 @@ export class InvitationForm extends ModelForm<Invitation, string> { | |||||||
|                         return flows.results; |                         return flows.results; | ||||||
|                     }} |                     }} | ||||||
|                     .renderElement=${(flow: Flow): string => { |                     .renderElement=${(flow: Flow): string => { | ||||||
|                         return flow.slug; |                         return RenderFlowOption(flow); | ||||||
|                     }} |                     }} | ||||||
|                     .renderDescription=${(flow: Flow): TemplateResult => { |                     .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                         return html`${flow.name}`; |                         return html`${flow.name}`; | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { first } from "@goauthentik/common/utils"; | import { first } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| @ -136,7 +137,7 @@ export class PasswordStageForm extends ModelForm<PasswordStage, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -77,7 +77,7 @@ export class PromptStageForm extends ModelForm<PromptStage, string> { | |||||||
|                                                 value=${ifDefined(prompt.pk)} |                                                 value=${ifDefined(prompt.pk)} | ||||||
|                                                 ?selected=${selected} |                                                 ?selected=${selected} | ||||||
|                                             > |                                             > | ||||||
|                                                 ${t`${prompt.fieldKey} ("${prompt.label}", of type ${prompt.type})`} |                                                 ${t`${prompt.name} ("${prompt.fieldKey}", of type ${prompt.type})`} | ||||||
|                                             </option>`; |                                             </option>`; | ||||||
|                                         }); |                                         }); | ||||||
|                                     }), |                                     }), | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
| import { first } from "@goauthentik/common/utils"; | import { first } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/CodeMirror"; | import "@goauthentik/elements/CodeMirror"; | ||||||
| @ -165,7 +166,7 @@ export class TenantForm extends ModelForm<Tenant, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
| @ -202,7 +203,7 @@ export class TenantForm extends ModelForm<Tenant, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
| @ -237,7 +238,7 @@ export class TenantForm extends ModelForm<Tenant, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
| @ -274,7 +275,7 @@ export class TenantForm extends ModelForm<Tenant, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
| @ -312,7 +313,7 @@ export class TenantForm extends ModelForm<Tenant, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
| @ -347,7 +348,7 @@ export class TenantForm extends ModelForm<Tenant, string> { | |||||||
|                                 return flows.results; |                                 return flows.results; | ||||||
|                             }} |                             }} | ||||||
|                             .renderElement=${(flow: Flow): string => { |                             .renderElement=${(flow: Flow): string => { | ||||||
|                                 return flow.slug; |                                 return RenderFlowOption(flow); | ||||||
|                             }} |                             }} | ||||||
|                             .renderDescription=${(flow: Flow): TemplateResult => { |                             .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                 return html`${flow.name}`; |                                 return html`${flow.name}`; | ||||||
|  | |||||||
| @ -7,19 +7,9 @@ import { EVENT_REFRESH, VERSION } from "@goauthentik/common/constants"; | |||||||
| import { globalAK } from "@goauthentik/common/global"; | import { globalAK } from "@goauthentik/common/global"; | ||||||
| import { activateLocale } from "@goauthentik/common/ui/locale"; | import { activateLocale } from "@goauthentik/common/ui/locale"; | ||||||
|  |  | ||||||
| import { | import { Config, Configuration, CoreApi, CurrentTenant, RootApi } from "@goauthentik/api"; | ||||||
|     Config, |  | ||||||
|     ConfigFromJSON, |  | ||||||
|     Configuration, |  | ||||||
|     CoreApi, |  | ||||||
|     CurrentTenant, |  | ||||||
|     CurrentTenantFromJSON, |  | ||||||
|     RootApi, |  | ||||||
| } from "@goauthentik/api"; |  | ||||||
|  |  | ||||||
| let globalConfigPromise: Promise<Config> | undefined = Promise.resolve( | let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config); | ||||||
|     ConfigFromJSON(globalAK()?.config), |  | ||||||
| ); |  | ||||||
| export function config(): Promise<Config> { | export function config(): Promise<Config> { | ||||||
|     if (!globalConfigPromise) { |     if (!globalConfigPromise) { | ||||||
|         globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve(); |         globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve(); | ||||||
| @ -52,9 +42,7 @@ export function tenantSetLocale(tenant: CurrentTenant) { | |||||||
|     activateLocale(tenant.defaultLocale); |     activateLocale(tenant.defaultLocale); | ||||||
| } | } | ||||||
|  |  | ||||||
| let globalTenantPromise: Promise<CurrentTenant> | undefined = Promise.resolve( | let globalTenantPromise: Promise<CurrentTenant> | undefined = Promise.resolve(globalAK().tenant); | ||||||
|     CurrentTenantFromJSON(globalAK()?.tenant), |  | ||||||
| ); |  | ||||||
| export function tenant(): Promise<CurrentTenant> { | export function tenant(): Promise<CurrentTenant> { | ||||||
|     if (!globalTenantPromise) { |     if (!globalTenantPromise) { | ||||||
|         globalTenantPromise = new CoreApi(DEFAULT_CONFIG) |         globalTenantPromise = new CoreApi(DEFAULT_CONFIG) | ||||||
| @ -82,7 +70,7 @@ export const DEFAULT_CONFIG = new Configuration({ | |||||||
|     middleware: [ |     middleware: [ | ||||||
|         new CSRFMiddleware(), |         new CSRFMiddleware(), | ||||||
|         new EventMiddleware(), |         new EventMiddleware(), | ||||||
|         new LoggingMiddleware(CurrentTenantFromJSON(globalAK()?.tenant)), |         new LoggingMiddleware(globalAK().tenant), | ||||||
|     ], |     ], | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success"; | |||||||
| export const ERROR_CLASS = "pf-m-danger"; | export const ERROR_CLASS = "pf-m-danger"; | ||||||
| export const PROGRESS_CLASS = "pf-m-in-progress"; | export const PROGRESS_CLASS = "pf-m-in-progress"; | ||||||
| export const CURRENT_CLASS = "pf-m-current"; | export const CURRENT_CLASS = "pf-m-current"; | ||||||
| export const VERSION = "2023.3.0"; | export const VERSION = "2023.3.1"; | ||||||
| export const TITLE_DEFAULT = "authentik"; | export const TITLE_DEFAULT = "authentik"; | ||||||
| export const ROUTE_SEPARATOR = ";"; | export const ROUTE_SEPARATOR = ";"; | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import { Config, CurrentTenant } from "@goauthentik/api"; | import { Config, ConfigFromJSON, CurrentTenant, CurrentTenantFromJSON } from "@goauthentik/api"; | ||||||
|  |  | ||||||
| export interface GlobalAuthentik { | export interface GlobalAuthentik { | ||||||
|  |     _converted?: boolean; | ||||||
|     locale?: string; |     locale?: string; | ||||||
|     flow?: { |     flow?: { | ||||||
|         layout: string; |         layout: string; | ||||||
| @ -13,11 +14,17 @@ export interface GlobalAuthentik { | |||||||
| } | } | ||||||
|  |  | ||||||
| export interface AuthentikWindow { | export interface AuthentikWindow { | ||||||
|     authentik?: GlobalAuthentik; |     authentik: GlobalAuthentik; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function globalAK(): GlobalAuthentik | undefined { | export function globalAK(): GlobalAuthentik { | ||||||
|     return (window as unknown as AuthentikWindow).authentik; |     const ak = (window as unknown as AuthentikWindow).authentik; | ||||||
|  |     if (ak && !ak._converted) { | ||||||
|  |         ak._converted = true; | ||||||
|  |         ak.tenant = CurrentTenantFromJSON(ak.tenant); | ||||||
|  |         ak.config = ConfigFromJSON(ak.config); | ||||||
|  |     } | ||||||
|  |     return ak; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function docLink(path: string): string { | export function docLink(path: string): string { | ||||||
|  | |||||||
| @ -172,6 +172,6 @@ export class Interface extends AKElement { | |||||||
|  |  | ||||||
|     async getTheme(): Promise<UiThemeEnum> { |     async getTheme(): Promise<UiThemeEnum> { | ||||||
|         const config = await uiConfig(); |         const config = await uiConfig(); | ||||||
|         return config.theme.base; |         return config.theme?.base || UiThemeEnum.Automatic; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,9 +5,6 @@ import { AKElement } from "@goauthentik/elements/Base"; | |||||||
| import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; | import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect"; | import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect"; | ||||||
| import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; | import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; | ||||||
| import "@polymer/iron-form/iron-form"; |  | ||||||
| import { IronFormElement } from "@polymer/iron-form/iron-form"; |  | ||||||
| import "@polymer/paper-input/paper-input"; |  | ||||||
|  |  | ||||||
| import { CSSResult, TemplateResult, css, html } from "lit"; | import { CSSResult, TemplateResult, css, html } from "lit"; | ||||||
| import { customElement, property } from "lit/decorators.js"; | import { customElement, property } from "lit/decorators.js"; | ||||||
| @ -110,63 +107,76 @@ export class Form<T> extends AKElement { | |||||||
|      * Reset the inner iron-form |      * Reset the inner iron-form | ||||||
|      */ |      */ | ||||||
|     resetForm(): void { |     resetForm(): void { | ||||||
|         const ironForm = this.shadowRoot?.querySelector("iron-form"); |         const form = this.shadowRoot?.querySelector<HTMLFormElement>("form"); | ||||||
|         ironForm?.reset(); |         form?.reset(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getFormFiles(): { [key: string]: File } { |     getFormFiles(): { [key: string]: File } { | ||||||
|         const ironForm = this.shadowRoot?.querySelector("iron-form"); |  | ||||||
|         const files: { [key: string]: File } = {}; |         const files: { [key: string]: File } = {}; | ||||||
|         if (!ironForm) { |         const elements = | ||||||
|             return files; |             this.shadowRoot?.querySelectorAll<HorizontalFormElement>( | ||||||
|         } |                 "ak-form-element-horizontal", | ||||||
|         const elements = ironForm._getSubmittableElements(); |             ) || []; | ||||||
|         for (let i = 0; i < elements.length; i++) { |         for (let i = 0; i < elements.length; i++) { | ||||||
|             const element = elements[i] as HTMLInputElement; |             const element = elements[i]; | ||||||
|             if (element.tagName.toLowerCase() === "input" && element.type === "file") { |             element.requestUpdate(); | ||||||
|                 if ((element.files || []).length < 1) { |             const inputElement = element.querySelector<HTMLInputElement>("[name]"); | ||||||
|  |             if (!inputElement) { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|                 files[element.name] = (element.files || [])[0]; |             if (inputElement.tagName.toLowerCase() === "input" && inputElement.type === "file") { | ||||||
|  |                 if ((inputElement.files || []).length < 1) { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 files[element.name] = (inputElement.files || [])[0]; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return files; |         return files; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     serializeForm(): T | undefined { |     serializeForm(): T | undefined { | ||||||
|         const form = this.shadowRoot?.querySelector<IronFormElement>("iron-form"); |         const elements = | ||||||
|         if (!form) { |             this.shadowRoot?.querySelectorAll<HorizontalFormElement>( | ||||||
|             console.warn("authentik/forms: failed to find iron-form"); |                 "ak-form-element-horizontal", | ||||||
|             return; |             ) || []; | ||||||
|         } |  | ||||||
|         const elements: HTMLInputElement[] = form._getSubmittableElements(); |  | ||||||
|         const json: { [key: string]: unknown } = {}; |         const json: { [key: string]: unknown } = {}; | ||||||
|         elements.forEach((element) => { |         elements.forEach((element) => { | ||||||
|             const values = form._serializeElementValues(element); |             element.requestUpdate(); | ||||||
|             if (element.hidden) { |             const inputElement = element.querySelector<HTMLInputElement>("[name]"); | ||||||
|  |             if (element.hidden || !inputElement) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             if (element.tagName.toLowerCase() === "select" && "multiple" in element.attributes) { |             if ( | ||||||
|                 json[element.name] = values; |                 inputElement.tagName.toLowerCase() === "select" && | ||||||
|             } else if (element.tagName.toLowerCase() === "input" && element.type === "date") { |                 "multiple" in inputElement.attributes | ||||||
|                 json[element.name] = element.valueAsDate; |  | ||||||
|             } else if ( |  | ||||||
|                 element.tagName.toLowerCase() === "input" && |  | ||||||
|                 element.type === "datetime-local" |  | ||||||
|             ) { |             ) { | ||||||
|                 json[element.name] = new Date(element.valueAsNumber); |                 const selectElement = inputElement as unknown as HTMLSelectElement; | ||||||
|  |                 json[element.name] = Array.from(selectElement.selectedOptions).map((v) => v.value); | ||||||
|             } else if ( |             } else if ( | ||||||
|                 element.tagName.toLowerCase() === "input" && |                 inputElement.tagName.toLowerCase() === "input" && | ||||||
|                 "type" in element.dataset && |                 inputElement.type === "date" | ||||||
|                 element.dataset["type"] === "datetime-local" |             ) { | ||||||
|  |                 json[element.name] = inputElement.valueAsDate; | ||||||
|  |             } else if ( | ||||||
|  |                 inputElement.tagName.toLowerCase() === "input" && | ||||||
|  |                 inputElement.type === "datetime-local" | ||||||
|  |             ) { | ||||||
|  |                 json[element.name] = new Date(inputElement.valueAsNumber); | ||||||
|  |             } else if ( | ||||||
|  |                 inputElement.tagName.toLowerCase() === "input" && | ||||||
|  |                 "type" in inputElement.dataset && | ||||||
|  |                 inputElement.dataset["type"] === "datetime-local" | ||||||
|             ) { |             ) { | ||||||
|                 // Workaround for Firefox <93, since 92 and older don't support |                 // Workaround for Firefox <93, since 92 and older don't support | ||||||
|                 // datetime-local fields |                 // datetime-local fields | ||||||
|                 json[element.name] = new Date(element.value); |                 json[element.name] = new Date(inputElement.value); | ||||||
|             } else if (element.tagName.toLowerCase() === "input" && element.type === "checkbox") { |             } else if ( | ||||||
|                 json[element.name] = element.checked; |                 inputElement.tagName.toLowerCase() === "input" && | ||||||
|             } else if (element.tagName.toLowerCase() === "ak-search-select") { |                 inputElement.type === "checkbox" | ||||||
|                 const select = element as unknown as SearchSelect<unknown>; |             ) { | ||||||
|  |                 json[element.name] = inputElement.checked; | ||||||
|  |             } else if (inputElement.tagName.toLowerCase() === "ak-search-select") { | ||||||
|  |                 const select = inputElement as unknown as SearchSelect<unknown>; | ||||||
|                 let value: unknown; |                 let value: unknown; | ||||||
|                 try { |                 try { | ||||||
|                     value = select.toForm(); |                     value = select.toForm(); | ||||||
| @ -179,9 +189,7 @@ export class Form<T> extends AKElement { | |||||||
|                 } |                 } | ||||||
|                 json[element.name] = value; |                 json[element.name] = value; | ||||||
|             } else { |             } else { | ||||||
|                 for (let v = 0; v < values.length; v++) { |                 this.serializeFieldRecursive(inputElement, inputElement.value, json); | ||||||
|                     this.serializeFieldRecursive(element, values[v], json); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         return json as unknown as T; |         return json as unknown as T; | ||||||
| @ -213,11 +221,6 @@ export class Form<T> extends AKElement { | |||||||
|         if (!data) { |         if (!data) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         const form = this.shadowRoot?.querySelector<IronFormElement>("iron-form"); |  | ||||||
|         if (!form) { |  | ||||||
|             console.warn("authentik/forms: failed to find iron-form"); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         return this.send(data) |         return this.send(data) | ||||||
|             .then((r) => { |             .then((r) => { | ||||||
|                 showMessage({ |                 showMessage({ | ||||||
| @ -244,8 +247,12 @@ export class Form<T> extends AKElement { | |||||||
|                         throw errorMessage; |                         throw errorMessage; | ||||||
|                     } |                     } | ||||||
|                     // assign all input-related errors to their elements |                     // assign all input-related errors to their elements | ||||||
|                     const elements: HorizontalFormElement[] = form._getSubmittableElements(); |                     const elements = | ||||||
|  |                         this.shadowRoot?.querySelectorAll<HorizontalFormElement>( | ||||||
|  |                             "ak-form-element-horizontal", | ||||||
|  |                         ) || []; | ||||||
|                     elements.forEach((element) => { |                     elements.forEach((element) => { | ||||||
|  |                         element.requestUpdate(); | ||||||
|                         const elementName = element.name; |                         const elementName = element.name; | ||||||
|                         if (!elementName) return; |                         if (!elementName) return; | ||||||
|                         if (camelToSnake(elementName) in errorMessage) { |                         if (camelToSnake(elementName) in errorMessage) { | ||||||
| @ -296,13 +303,7 @@ export class Form<T> extends AKElement { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     renderVisible(): TemplateResult { |     renderVisible(): TemplateResult { | ||||||
|         return html`<iron-form |         return html` ${this.renderNonFieldErrors()} ${this.renderForm()}`; | ||||||
|             @iron-form-presubmit=${(ev: Event) => { |  | ||||||
|                 this.submit(ev); |  | ||||||
|             }} |  | ||||||
|         > |  | ||||||
|             ${this.renderNonFieldErrors()} ${this.renderForm()} |  | ||||||
|         </iron-form>`; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     render(): TemplateResult { |     render(): TemplateResult { | ||||||
|  | |||||||
| @ -69,6 +69,10 @@ export class HorizontalFormElement extends AKElement { | |||||||
|     @property() |     @property() | ||||||
|     name = ""; |     name = ""; | ||||||
|  |  | ||||||
|  |     firstUpdated(): void { | ||||||
|  |         this.updated(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     updated(): void { |     updated(): void { | ||||||
|         this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => { |         this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => { | ||||||
|             input.focus(); |             input.focus(); | ||||||
| @ -89,7 +93,7 @@ export class HorizontalFormElement extends AKElement { | |||||||
|                 case "ak-chip-group": |                 case "ak-chip-group": | ||||||
|                 case "ak-search-select": |                 case "ak-search-select": | ||||||
|                 case "ak-radio": |                 case "ak-radio": | ||||||
|                     (input as HTMLInputElement).name = this.name; |                     input.setAttribute("name", this.name); | ||||||
|                     break; |                     break; | ||||||
|                 default: |                 default: | ||||||
|                     return; |                     return; | ||||||
| @ -108,6 +112,7 @@ export class HorizontalFormElement extends AKElement { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     render(): TemplateResult { |     render(): TemplateResult { | ||||||
|  |         this.updated(); | ||||||
|         return html`<div class="pf-c-form__group"> |         return html`<div class="pf-c-form__group"> | ||||||
|             <div class="pf-c-form__group-label"> |             <div class="pf-c-form__group-label"> | ||||||
|                 <label class="pf-c-form__label"> |                 <label class="pf-c-form__label"> | ||||||
|  | |||||||
| @ -70,6 +70,7 @@ export class SearchSelect<T> extends AKElement { | |||||||
|     observer: IntersectionObserver; |     observer: IntersectionObserver; | ||||||
|     dropdownUID: string; |     dropdownUID: string; | ||||||
|     dropdownContainer: HTMLDivElement; |     dropdownContainer: HTMLDivElement; | ||||||
|  |     isFetchingData = false; | ||||||
|  |  | ||||||
|     constructor() { |     constructor() { | ||||||
|         super(); |         super(); | ||||||
| @ -103,13 +104,18 @@ export class SearchSelect<T> extends AKElement { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     updateData(): void { |     updateData(): void { | ||||||
|  |         if (this.isFetchingData) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         this.isFetchingData = true; | ||||||
|         this.fetchObjects(this.query).then((objects) => { |         this.fetchObjects(this.query).then((objects) => { | ||||||
|             this.objects = objects; |             objects.forEach((obj) => { | ||||||
|             this.objects.forEach((obj) => { |  | ||||||
|                 if (this.selected && this.selected(obj, this.objects || [])) { |                 if (this.selected && this.selected(obj, this.objects || [])) { | ||||||
|                     this.selectedObject = obj; |                     this.selectedObject = obj; | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|  |             this.objects = objects; | ||||||
|  |             this.isFetchingData = false; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -200,9 +206,10 @@ export class SearchSelect<T> extends AKElement { | |||||||
|         render( |         render( | ||||||
|             html`<div |             html`<div | ||||||
|                 class="pf-c-dropdown pf-m-expanded" |                 class="pf-c-dropdown pf-m-expanded" | ||||||
|                 ?hidden=${!this.open} |  | ||||||
|                 style="position: fixed; inset: 0px auto auto 0px; z-index: 9999; transform: translate(${pos.x}px, ${pos.y + |                 style="position: fixed; inset: 0px auto auto 0px; z-index: 9999; transform: translate(${pos.x}px, ${pos.y + | ||||||
|                 this.offsetHeight}px); width: ${pos.width}px;" |                 this.offsetHeight}px); width: ${pos.width}px; ${this.open | ||||||
|  |                     ? "" | ||||||
|  |                     : "visibility: hidden;"}" | ||||||
|             > |             > | ||||||
|                 <ul |                 <ul | ||||||
|                     class="pf-c-dropdown__menu pf-m-static" |                     class="pf-c-dropdown__menu pf-m-static" | ||||||
| @ -249,6 +256,14 @@ export class SearchSelect<T> extends AKElement { | |||||||
|  |  | ||||||
|     render(): TemplateResult { |     render(): TemplateResult { | ||||||
|         this.renderMenu(); |         this.renderMenu(); | ||||||
|  |         let value = ""; | ||||||
|  |         if (!this.objects) { | ||||||
|  |             value = t`Loading...`; | ||||||
|  |         } else if (this.selectedObject) { | ||||||
|  |             value = this.renderElement(this.selectedObject); | ||||||
|  |         } else if (this.blankable) { | ||||||
|  |             value = this.emptyOption; | ||||||
|  |         } | ||||||
|         return html`<div class="pf-c-select"> |         return html`<div class="pf-c-select"> | ||||||
|             <div class="pf-c-select__toggle pf-m-typeahead"> |             <div class="pf-c-select__toggle pf-m-typeahead"> | ||||||
|                 <div class="pf-c-select__toggle-wrapper"> |                 <div class="pf-c-select__toggle-wrapper"> | ||||||
| @ -256,6 +271,7 @@ export class SearchSelect<T> extends AKElement { | |||||||
|                         class="pf-c-form-control pf-c-select__toggle-typeahead" |                         class="pf-c-form-control pf-c-select__toggle-typeahead" | ||||||
|                         type="text" |                         type="text" | ||||||
|                         placeholder=${this.placeholder} |                         placeholder=${this.placeholder} | ||||||
|  |                         spellcheck="false" | ||||||
|                         @input=${(ev: InputEvent) => { |                         @input=${(ev: InputEvent) => { | ||||||
|                             this.query = (ev.target as HTMLInputElement).value; |                             this.query = (ev.target as HTMLInputElement).value; | ||||||
|                             this.updateData(); |                             this.updateData(); | ||||||
| @ -285,11 +301,7 @@ export class SearchSelect<T> extends AKElement { | |||||||
|                             this.open = false; |                             this.open = false; | ||||||
|                             this.renderMenu(); |                             this.renderMenu(); | ||||||
|                         }} |                         }} | ||||||
|                         .value=${this.selectedObject |                         .value=${value} | ||||||
|                             ? this.renderElement(this.selectedObject) |  | ||||||
|                             : this.blankable |  | ||||||
|                             ? this.emptyOption |  | ||||||
|                             : ""} |  | ||||||
|                     /> |                     /> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
| @ -68,6 +68,7 @@ export class PasswordStage extends BaseStage<PasswordChallenge, PasswordChalleng | |||||||
|         if (this.timer) { |         if (this.timer) { | ||||||
|             console.debug("authentik/stages/password: cleared focus timer"); |             console.debug("authentik/stages/password: cleared focus timer"); | ||||||
|             window.clearInterval(this.timer); |             window.clearInterval(this.timer); | ||||||
|  |             this.timer = undefined; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,5 +1,3 @@ | |||||||
| // @ts-ignore |  | ||||||
| window["polymerSkipLoadingFontRoboto"] = true; |  | ||||||
| import "construct-style-sheets-polyfill"; | import "construct-style-sheets-polyfill"; | ||||||
| import "@webcomponents/webcomponentsjs"; | import "@webcomponents/webcomponentsjs"; | ||||||
| import "lit/polyfill-support.js"; | import "lit/polyfill-support.js"; | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ When authenticating with a flow, you'll get an authenticated Session cookie, tha | |||||||
|  |  | ||||||
| ### API Token | ### API Token | ||||||
|  |  | ||||||
| Superusers can create tokens to authenticate as any user with a static key, which can optionally be expiring and auto-rotate. | Users can create tokens to authenticate as any user with a static key, which can optionally be expiring and auto-rotate. | ||||||
|  |  | ||||||
| ### JWT Token | ### JWT Token | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										27
									
								
								website/developer-docs/blueprints/v1/models.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								website/developer-docs/blueprints/v1/models.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | # Models | ||||||
|  |  | ||||||
|  | Some models behave differently and allow for access to different API fields when created via blueprint. | ||||||
|  |  | ||||||
|  | ### `authentik_core.token` | ||||||
|  |  | ||||||
|  | :::info | ||||||
|  | Requires authentik 2023.4 | ||||||
|  | ::: | ||||||
|  |  | ||||||
|  | Via the standard API, a token's key cannot be changed, it can only be rotated. This is to ensure a high entropy in it's key, and to prevent insecure data from being used. However, when provisioning tokens via a blueprint, it may be required to set a token to an existing value. | ||||||
|  |  | ||||||
|  | With blueprints, the field `key` can be set, to set the token's key to any value. | ||||||
|  |  | ||||||
|  | For example: | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | # [...] | ||||||
|  | - model: authentik_core.token | ||||||
|  |   state: present | ||||||
|  |   identifiers: | ||||||
|  |       identifier: my-token | ||||||
|  |   attrs: | ||||||
|  |       key: this-should-be-a-long-value | ||||||
|  |       user: !KeyOf my-user | ||||||
|  |       intent: api | ||||||
|  | ``` | ||||||
| @ -16,6 +16,7 @@ module.exports = { | |||||||
|                 "blueprints/v1/structure", |                 "blueprints/v1/structure", | ||||||
|                 "blueprints/v1/tags", |                 "blueprints/v1/tags", | ||||||
|                 "blueprints/v1/example", |                 "blueprints/v1/example", | ||||||
|  |                 "blueprints/v1/models", | ||||||
|                 "blueprints/v1/meta", |                 "blueprints/v1/meta", | ||||||
|             ], |             ], | ||||||
|         }, |         }, | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	