Compare commits
13 Commits
webdriver-
...
web/add-co
Author | SHA1 | Date | |
---|---|---|---|
00c1e17b52 | |||
cba8e84bbe | |||
d313fd7fb4 | |||
102811508f | |||
16b3ca3715 | |||
8b4e0361c4 | |||
22cb5b7379 | |||
2d0117d096 | |||
035bda4eac | |||
50906214e5 | |||
e505f274b6 | |||
fe52f44dca | |||
3146e5a50f |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2025.6.2
|
||||
current_version = 2025.6.1
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||
|
2
.github/workflows/ci-main.yml
vendored
2
.github/workflows/ci-main.yml
vendored
@ -202,7 +202,7 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: web/dist
|
||||
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
|
||||
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
|
||||
- name: prepare web ui
|
||||
if: steps.cache-web.outputs.cache-hit != 'true'
|
||||
working-directory: web
|
||||
|
@ -77,7 +77,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
# Stage 4: Download uv
|
||||
FROM ghcr.io/astral-sh/uv:0.7.13 AS uv
|
||||
# Stage 5: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base
|
||||
FROM ghcr.io/goauthentik/fips-python:3.13.4-slim-bookworm-fips AS python-base
|
||||
|
||||
ENV VENV_PATH="/ak-root/.venv" \
|
||||
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
|
||||
|
2
Makefile
2
Makefile
@ -94,7 +94,7 @@ gen-build: ## Extract the schema from the database
|
||||
AUTHENTIK_DEBUG=true \
|
||||
AUTHENTIK_TENANTS__ENABLED=true \
|
||||
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
|
||||
uv run ak make_blueprint_schema --file blueprints/schema.json
|
||||
uv run ak make_blueprint_schema > blueprints/schema.json
|
||||
AUTHENTIK_DEBUG=true \
|
||||
AUTHENTIK_TENANTS__ENABLED=true \
|
||||
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from os import environ
|
||||
|
||||
__version__ = "2025.6.2"
|
||||
__version__ = "2025.6.1"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -72,33 +72,20 @@ class Command(BaseCommand):
|
||||
"additionalProperties": True,
|
||||
},
|
||||
"entries": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/blueprint_entry"},
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/blueprint_entry"},
|
||||
},
|
||||
},
|
||||
],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"$defs": {"blueprint_entry": {"oneOf": []}},
|
||||
"$defs": {},
|
||||
}
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--file", type=str)
|
||||
|
||||
@no_translations
|
||||
def handle(self, *args, file: str, **options):
|
||||
def handle(self, *args, **options):
|
||||
"""Generate JSON Schema for blueprints"""
|
||||
self.build()
|
||||
with open(file, "w") as _schema:
|
||||
_schema.write(dumps(self.schema, indent=4, default=Command.json_default))
|
||||
self.stdout.write(dumps(self.schema, indent=4, default=Command.json_default))
|
||||
|
||||
@staticmethod
|
||||
def json_default(value: Any) -> Any:
|
||||
@ -125,7 +112,7 @@ class Command(BaseCommand):
|
||||
}
|
||||
)
|
||||
model_path = f"{model._meta.app_label}.{model._meta.model_name}"
|
||||
self.schema["$defs"]["blueprint_entry"]["oneOf"].append(
|
||||
self.schema["properties"]["entries"]["items"]["oneOf"].append(
|
||||
self.template_entry(model_path, model, serializer)
|
||||
)
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
version: 1
|
||||
entries:
|
||||
foo:
|
||||
- identifiers:
|
||||
name: "%(id)s"
|
||||
slug: "%(id)s"
|
||||
model: authentik_flows.flow
|
||||
state: present
|
||||
attrs:
|
||||
designation: stage_configuration
|
||||
title: foo
|
||||
- identifiers:
|
||||
name: "%(id)s"
|
||||
slug: "%(id)s"
|
||||
model: authentik_flows.flow
|
||||
state: present
|
||||
attrs:
|
||||
designation: stage_configuration
|
||||
title: foo
|
||||
|
@ -191,18 +191,11 @@ class Blueprint:
|
||||
"""Dataclass used for a full export"""
|
||||
|
||||
version: int = field(default=1)
|
||||
entries: list[BlueprintEntry] | dict[str, list[BlueprintEntry]] = field(default_factory=list)
|
||||
entries: list[BlueprintEntry] = field(default_factory=list)
|
||||
context: dict = field(default_factory=dict)
|
||||
|
||||
metadata: BlueprintMetadata | None = field(default=None)
|
||||
|
||||
def iter_entries(self) -> Iterable[BlueprintEntry]:
|
||||
if isinstance(self.entries, dict):
|
||||
for _section, entries in self.entries.items():
|
||||
yield from entries
|
||||
else:
|
||||
yield from self.entries
|
||||
|
||||
|
||||
class YAMLTag:
|
||||
"""Base class for all YAML Tags"""
|
||||
@ -233,7 +226,7 @@ class KeyOf(YAMLTag):
|
||||
self.id_from = node.value
|
||||
|
||||
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
||||
for _entry in blueprint.iter_entries():
|
||||
for _entry in blueprint.entries:
|
||||
if _entry.id == self.id_from and _entry._state.instance:
|
||||
# Special handling for PolicyBindingModels, as they'll have a different PK
|
||||
# which is used when creating policy bindings
|
||||
|
@ -384,7 +384,7 @@ class Importer:
|
||||
def _apply_models(self, raise_errors=False) -> bool:
|
||||
"""Apply (create/update) models yaml"""
|
||||
self.__pk_map = {}
|
||||
for entry in self._import.iter_entries():
|
||||
for entry in self._import.entries:
|
||||
model_app_label, model_name = entry.get_model(self._import).split(".")
|
||||
try:
|
||||
model: type[SerializerModel] = registry.get_model(model_app_label, model_name)
|
||||
|
@ -387,7 +387,8 @@ class TestAuthorize(OAuthTestCase):
|
||||
self.assertEqual(
|
||||
response.url,
|
||||
(
|
||||
f"http://localhost#id_token={provider.encode(token.id_token.to_dict())}"
|
||||
f"http://localhost#access_token={token.token}"
|
||||
f"&id_token={provider.encode(token.id_token.to_dict())}"
|
||||
f"&token_type={TOKEN_TYPE}"
|
||||
f"&expires_in={int(expires)}&state={state}"
|
||||
),
|
||||
@ -562,6 +563,7 @@ class TestAuthorize(OAuthTestCase):
|
||||
"url": "http://localhost",
|
||||
"title": f"Redirecting to {app.name}...",
|
||||
"attrs": {
|
||||
"access_token": token.token,
|
||||
"id_token": provider.encode(token.id_token.to_dict()),
|
||||
"token_type": TOKEN_TYPE,
|
||||
"expires_in": "3600",
|
||||
|
@ -150,12 +150,12 @@ class OAuthAuthorizationParams:
|
||||
self.check_redirect_uri()
|
||||
self.check_grant()
|
||||
self.check_scope(github_compat)
|
||||
self.check_nonce()
|
||||
self.check_code_challenge()
|
||||
if self.request:
|
||||
raise AuthorizeError(
|
||||
self.redirect_uri, "request_not_supported", self.grant_type, self.state
|
||||
)
|
||||
self.check_nonce()
|
||||
self.check_code_challenge()
|
||||
|
||||
def check_grant(self):
|
||||
"""Check grant"""
|
||||
@ -630,6 +630,7 @@ class OAuthFulfillmentStage(StageView):
|
||||
if self.params.response_type in [
|
||||
ResponseTypes.ID_TOKEN_TOKEN,
|
||||
ResponseTypes.CODE_ID_TOKEN_TOKEN,
|
||||
ResponseTypes.ID_TOKEN,
|
||||
ResponseTypes.CODE_TOKEN,
|
||||
]:
|
||||
query_fragment["access_token"] = token.token
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -31,7 +31,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.1}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -55,7 +55,7 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.1}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
2
go.mod
2
go.mod
@ -29,7 +29,7 @@ require (
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2025062.1
|
||||
goauthentik.io/api/v3 v3.2025061.2
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.15.0
|
||||
|
4
go.sum
4
go.sum
@ -298,8 +298,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
goauthentik.io/api/v3 v3.2025062.1 h1:spvILDpDDWJNO3pM6QGqmryx6NvSchr1E8H60J/XUCA=
|
||||
goauthentik.io/api/v3 v3.2025062.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2025061.2 h1:bKmrl82Gz6J8lz3f+QIH9g+MEkl3MvkMXF34GktesA0=
|
||||
goauthentik.io/api/v3 v3.2025061.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -33,4 +33,4 @@ func UserAgent() string {
|
||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2025.6.2"
|
||||
const VERSION = "2025.6.1"
|
||||
|
@ -26,7 +26,7 @@ Parameters:
|
||||
Description: authentik Docker image
|
||||
AuthentikVersion:
|
||||
Type: String
|
||||
Default: 2025.6.2
|
||||
Default: 2025.6.1
|
||||
Description: authentik Docker image tag
|
||||
AuthentikServerCPU:
|
||||
Type: Number
|
||||
|
@ -6,18 +6,18 @@
|
||||
# Translators:
|
||||
# jcamat, 2022
|
||||
# Angel, 2024
|
||||
# Iamanaws, 2024
|
||||
# Marcelo Elizeche Landó, 2025
|
||||
# Jens L. <jens@goauthentik.io>, 2025
|
||||
# Iamanaws, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-04 00:12+0000\n"
|
||||
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Iamanaws, 2025\n"
|
||||
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
|
||||
"Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -111,7 +111,7 @@ msgstr "Certificado Web usado por el servidor web Core de authentik"
|
||||
|
||||
#: authentik/brands/models.py
|
||||
msgid "Certificates used for client authentication."
|
||||
msgstr "Certificados utilizados para la autenticación del cliente."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/brands/models.py
|
||||
msgid "Brand"
|
||||
@ -131,7 +131,7 @@ msgstr "Descripción adicional no disponible."
|
||||
|
||||
#: authentik/core/api/groups.py
|
||||
msgid "Cannot set group as parent of itself."
|
||||
msgstr "No se puede establecer un grupo como su propio padre."
|
||||
msgstr "No se puede establecer el grupo como padre de sí mismo."
|
||||
|
||||
#: authentik/core/api/providers.py
|
||||
msgid ""
|
||||
@ -183,11 +183,11 @@ msgstr "Remueve usuario del grupo"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Enable superuser status"
|
||||
msgstr "Habilitar el estado de superusuario"
|
||||
msgstr "Habiliar estado de \"superusuario\""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Disable superuser status"
|
||||
msgstr "Deshabilitar el estado de superusuario"
|
||||
msgstr "Deshabiliar estado de \"superusuario\""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "User's display name."
|
||||
@ -241,7 +241,7 @@ msgstr "Flujo utilizado al autorizar a este proveedor."
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Flow used ending the session from a provider."
|
||||
msgstr "Flujo utilizado para finalizar la sesión desde un proveedor."
|
||||
msgstr "Flujo usado para terminar la sesión de un proveedor."
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid ""
|
||||
@ -273,11 +273,11 @@ msgstr "Aplicaciones"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlement"
|
||||
msgstr "Derecho de Aplicación"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlements"
|
||||
msgstr "Derechos de Aplicación"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Use the source-specific identifier"
|
||||
@ -288,9 +288,9 @@ msgid ""
|
||||
"Link to a user with identical email address. Can have security implications "
|
||||
"when a source doesn't validate email addresses."
|
||||
msgstr ""
|
||||
"Enlace a un usuario con la misma dirección de correo electrónico. Puede "
|
||||
"tener implicaciones de seguridad cuando una fuente no valida las direcciones"
|
||||
" de correo electrónico."
|
||||
"Apunta a un usuario con una dirección de correo electrónico idéntica. Puede "
|
||||
"tener implicaciones de seguridad cuando una fuente no valida la dirección de"
|
||||
" correo electrónico."
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid ""
|
||||
@ -305,8 +305,8 @@ msgid ""
|
||||
"Link to a user with identical username. Can have security implications when "
|
||||
"a username is used with another source."
|
||||
msgstr ""
|
||||
"Enlace a un usuario con el mismo nombre de usuario. Puede tener "
|
||||
"implicaciones de seguridad cuando un nombre de usuario se utiliza con otra "
|
||||
"Enlace a un usuario con un nombre de usuario idéntico. Puede tener "
|
||||
"implicaciones de seguridad cuando se usa un nombre de usuario con otra "
|
||||
"fuente."
|
||||
|
||||
#: authentik/core/models.py
|
||||
@ -322,8 +322,8 @@ msgid ""
|
||||
"Link to a group with identical name. Can have security implications when a "
|
||||
"group name is used with another source."
|
||||
msgstr ""
|
||||
"Enlace a un grupo con el mismo nombre. Puede tener implicaciones de "
|
||||
"seguridad cuando un nombre de grupo se utiliza con otra fuente."
|
||||
"Enlace a un grupo con un nombre idéntico. Puede tener implicaciones de "
|
||||
"seguridad cuando se utiliza un nombre de grupo con otra fuente."
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Use the group name, but deny enrollment when the name already exists."
|
||||
@ -385,7 +385,7 @@ msgstr "Asignaciones de Propiedades"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "session data"
|
||||
msgstr "datos de sesión"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Session"
|
||||
@ -424,7 +424,7 @@ msgstr "¡Autenticado exitosamente con {source}!"
|
||||
#: authentik/core/sources/flow_manager.py
|
||||
#, python-brace-format
|
||||
msgid "Successfully linked {source}!"
|
||||
msgstr "¡{source} enlazado correctamente!"
|
||||
msgstr "¡{source} vinculado exitosamente!"
|
||||
|
||||
#: authentik/core/sources/flow_manager.py
|
||||
msgid "Source is not configured for enrollment."
|
||||
@ -476,11 +476,11 @@ msgstr ""
|
||||
|
||||
#: authentik/crypto/models.py
|
||||
msgid "Certificate-Key Pair"
|
||||
msgstr "Par Certificado-Clave"
|
||||
msgstr "Par de claves de certificado"
|
||||
|
||||
#: authentik/crypto/models.py
|
||||
msgid "Certificate-Key Pairs"
|
||||
msgstr "Pares Certificado-Clave"
|
||||
msgstr "Pares de claves de certificado"
|
||||
|
||||
#: authentik/enterprise/api.py
|
||||
msgid "Enterprise is required to create/update this object."
|
||||
@ -511,7 +511,7 @@ msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Number of passwords to check against."
|
||||
msgstr "Número de contraseñas contra las que verificar."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
#: authentik/policies/password/models.py
|
||||
@ -521,20 +521,18 @@ msgstr "La contraseña no se ha establecido en contexto"
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "This password has been used previously. Please choose a different one."
|
||||
msgstr ""
|
||||
"Esta contraseña se ha utilizado anteriormente. Por favor, elija una "
|
||||
"diferente."
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policy"
|
||||
msgstr "Política de Unicidad de Contraseñas"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "Password Uniqueness Policies"
|
||||
msgstr "Políticas de Unicidad de Contraseñas"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policies/unique_password/models.py
|
||||
msgid "User Password History"
|
||||
msgstr "Historial de Contraseñas del Usuario"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/policy.py
|
||||
msgid "Enterprise required to access this feature."
|
||||
@ -619,39 +617,39 @@ msgstr "Clave de firma"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "Key used to sign the SSF Events."
|
||||
msgstr "Clave utilizada para firmar los eventos SSF."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "Shared Signals Framework Provider"
|
||||
msgstr "Proveedor del Marco de Señales Compartidas"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "Shared Signals Framework Providers"
|
||||
msgstr "Proveedores del Marco de Señales Compartidas"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "Add stream to SSF provider"
|
||||
msgstr "Agregar flujo de datos al proveedor SSF"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "SSF Stream"
|
||||
msgstr "Flujo de Datos SSF"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "SSF Streams"
|
||||
msgstr "Flujos de Datos SSF"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "SSF Stream Event"
|
||||
msgstr "Evento de Flujo de Datos SSF"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "SSF Stream Events"
|
||||
msgstr "Eventos de Flujos de Datos SSF"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/providers/ssf/tasks.py
|
||||
msgid "Failed to send request"
|
||||
msgstr "Error al enviar la solicitud"
|
||||
msgstr "Falló envio de petición"
|
||||
|
||||
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||
@ -683,29 +681,26 @@ msgid ""
|
||||
"option has a higher priority than the `client_certificate` option on "
|
||||
"`Brand`."
|
||||
msgstr ""
|
||||
"Configura las autoridades certificadoras para validar el certificado. Esta "
|
||||
"opción tiene una prioridad mayor que la opción `client_certificate` en "
|
||||
"`Brand`."
|
||||
|
||||
#: authentik/enterprise/stages/mtls/models.py
|
||||
msgid "Mutual TLS Stage"
|
||||
msgstr "Etapa de TLS mutuo"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/stages/mtls/models.py
|
||||
msgid "Mutual TLS Stages"
|
||||
msgstr "Etapas de TLS mutuo"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/stages/mtls/models.py
|
||||
msgid "Permissions to pass Certificates for outposts."
|
||||
msgstr "Permisos para pasar Certificados a los puestos avanzados."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/stages/mtls/stage.py
|
||||
msgid "Certificate required but no certificate was given."
|
||||
msgstr "Se requiere certificado, pero no se proporcionó ninguno."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/stages/mtls/stage.py
|
||||
msgid "No user found for certificate."
|
||||
msgstr "No se encontró usuario para el certificado."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/enterprise/stages/source/models.py
|
||||
msgid ""
|
||||
@ -758,16 +753,12 @@ msgid ""
|
||||
"Customize the body of the request. Mapping should return data that is JSON-"
|
||||
"serializable."
|
||||
msgstr ""
|
||||
"Personaliza el cuerpo de la solicitud. El mapeo debe devolver datos que sean"
|
||||
" serializables en JSON."
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
"Configure additional headers to be sent. Mapping should return a dictionary "
|
||||
"of key-value pairs"
|
||||
msgstr ""
|
||||
"Configura encabezados adicionales para enviar. El mapeo debe devolver un "
|
||||
"diccionario de pares clave-valor"
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
@ -795,7 +786,7 @@ msgstr "Transporte de notificaciones"
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid "Notification Transports"
|
||||
msgstr "Medios de Notificación"
|
||||
msgstr "Transportes de notificación"
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid "Notice"
|
||||
@ -822,9 +813,9 @@ msgid ""
|
||||
"Select which transports should be used to notify the user. If none are "
|
||||
"selected, the notification will only be shown in the authentik UI."
|
||||
msgstr ""
|
||||
"Selecciona qué medios se deben usar para notificar al usuario. Si no se "
|
||||
"selecciona ninguno, la notificación solo se mostrará en la interfaz de "
|
||||
"authentik."
|
||||
"Seleccione qué transportes se deben usar para notificar al usuario. Si no se"
|
||||
" selecciona ninguno, la notificación solo se mostrará en la interfaz de "
|
||||
"usuario de authentik."
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid "Controls which severity level the created notifications will have."
|
||||
@ -996,7 +987,7 @@ msgstr "Evalúa políticas durante el proceso de planeación del Flujo."
|
||||
|
||||
#: authentik/flows/models.py
|
||||
msgid "Evaluate policies when the Stage is presented to the user."
|
||||
msgstr "Evaluar las políticas cuando la Etapa se presenta al usuario."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py
|
||||
msgid ""
|
||||
@ -1043,8 +1034,6 @@ msgid ""
|
||||
"When enabled, provider will not modify or create objects in the remote "
|
||||
"system."
|
||||
msgstr ""
|
||||
"Cuando está habilitado, el proveedor no modificará ni creará objetos en el "
|
||||
"sistema remoto."
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
msgid "Starting full provider sync"
|
||||
@ -1052,21 +1041,20 @@ msgstr "Iniciando sincronización completa de proveedor"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
msgid "Syncing users"
|
||||
msgstr "Sincronizando usuarios"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
msgid "Syncing groups"
|
||||
msgstr "Sincronizando grupos"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Syncing page {page} of {object_type}"
|
||||
msgstr "Sincronizando página {page} de {object_type}"
|
||||
msgid "Syncing page {page} of groups"
|
||||
msgstr "Sincronizando página {page} de grupos"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
msgid "Dropping mutating request due to dry run"
|
||||
msgstr ""
|
||||
"Descartando solicitud de mutación debido a ejecución en modo de simulación"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
@ -1245,7 +1233,7 @@ msgstr ""
|
||||
|
||||
#: authentik/policies/expiry/models.py
|
||||
msgid "Password has expired."
|
||||
msgstr "La contraseña ha expirado."
|
||||
msgstr "La contraseña ha caducado."
|
||||
|
||||
#: authentik/policies/expiry/models.py
|
||||
msgid "Password Expiry Policy"
|
||||
@ -1283,7 +1271,7 @@ msgstr "La IP del cliente no está en un país permitido."
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance from previous authentication is larger than threshold."
|
||||
msgstr "La distancia desde la autenticación anterior es mayor que el umbral."
|
||||
msgstr "La distancia desde la autenticación previa es mayor que el límite."
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance is further than possible."
|
||||
@ -1332,7 +1320,7 @@ msgstr "Vinculación de Políticas"
|
||||
|
||||
#: authentik/policies/models.py
|
||||
msgid "Policy Bindings"
|
||||
msgstr "Vinculaciones de Políticas"
|
||||
msgstr "Vinculaciones de políticas"
|
||||
|
||||
#: authentik/policies/models.py
|
||||
msgid ""
|
||||
@ -1606,11 +1594,11 @@ msgstr "ES256 (Encriptación Asimétrica)"
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "ES384 (Asymmetric Encryption)"
|
||||
msgstr "ES384 (Encriptación Asimétrica)"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "ES512 (Asymmetric Encryption)"
|
||||
msgstr "ES512 (Encriptación Asimétrica)"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Scope used by the client"
|
||||
@ -1825,7 +1813,7 @@ msgstr "Valida Certificados SSL de servidores de origen"
|
||||
|
||||
#: authentik/providers/proxy/models.py
|
||||
msgid "Internal host SSL Validation"
|
||||
msgstr "Validación SSL del host interno"
|
||||
msgstr "Validación SSL de host interno"
|
||||
|
||||
#: authentik/providers/proxy/models.py
|
||||
msgid ""
|
||||
@ -2039,7 +2027,7 @@ msgstr ""
|
||||
|
||||
#: authentik/providers/saml/models.py
|
||||
msgid "AuthnContextClassRef Property Mapping"
|
||||
msgstr "Asignación de Propiedades de AuthnContextClassRef"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/saml/models.py
|
||||
msgid ""
|
||||
@ -2047,9 +2035,6 @@ msgid ""
|
||||
"empty, the AuthnContextClassRef will be set based on which authentication "
|
||||
"methods the user used to authenticate."
|
||||
msgstr ""
|
||||
"Configura cómo se creará el valor de AuthnContextClassRef. Si se deja vacío,"
|
||||
" el AuthnContextClassRef se establecerá según los métodos de autenticación "
|
||||
"que el usuario haya utilizado para autenticarse."
|
||||
|
||||
#: authentik/providers/saml/models.py
|
||||
msgid ""
|
||||
@ -2199,11 +2184,11 @@ msgstr "Predeterminado"
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "AWS"
|
||||
msgstr "AWS"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "Slack"
|
||||
msgstr "Slack"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "Base URL to SCIM requests, usually ends in /v2"
|
||||
@ -2215,13 +2200,11 @@ msgstr "Token de Autenticación"
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "SCIM Compatibility Mode"
|
||||
msgstr "Modo de Compatibilidad SCIM"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "Alter authentik behavior for vendor-specific SCIM implementations."
|
||||
msgstr ""
|
||||
"Modificar el comportamiento de authentik para implementaciones SCIM "
|
||||
"específicas de proveedores."
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "SCIM Provider"
|
||||
@ -2249,7 +2232,7 @@ msgstr "Roles"
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "Initial Permissions"
|
||||
msgstr "Permisos Iniciales"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/rbac/models.py
|
||||
msgid "System permission"
|
||||
@ -2287,7 +2270,7 @@ msgstr ""
|
||||
|
||||
#: authentik/recovery/views.py
|
||||
msgid "Used recovery-link to authenticate."
|
||||
msgstr "Se utilizó un enlace de recuperación para autenticarse."
|
||||
msgstr "Se usó el enlace de recuperación para autenticarse."
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid "Kerberos realm"
|
||||
@ -2299,7 +2282,7 @@ msgstr "krb5.conf personalizado a usar. Usa el del sistema por defecto."
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid "KAdmin server type"
|
||||
msgstr "Tipo de servidor KAdmin"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid "Sync users from Kerberos into authentik"
|
||||
@ -2307,24 +2290,23 @@ msgstr "Sincronizar usuarios desde Kerberos hacia Authentik"
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid "When a user changes their password, sync it back to Kerberos"
|
||||
msgstr ""
|
||||
"Cuando un usuario cambie su contraseña, sincronizarla de vuelta a Kerberos."
|
||||
msgstr "Cuando un usuario cambia su contraseña, sincronizarlo hacia Kerberos"
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid "Principal to authenticate to kadmin for sync."
|
||||
msgstr "Principal para autenticarse en kadmin para la sincronización."
|
||||
msgstr "Principal para autenticarse como kadmin para la sincronización."
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid "Password to authenticate to kadmin for sync"
|
||||
msgstr "Contraseña para autenticarse en kadmin para la sincronización"
|
||||
msgstr "Contraseña para autenticarse como kadmin para la sincronización"
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid ""
|
||||
"Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the "
|
||||
"form TYPE:residual"
|
||||
msgstr ""
|
||||
"Keytab para autenticarse en kadmin para la sincronización. Debe estar "
|
||||
"codificado en base64 o en el formato TIPO:residuo"
|
||||
"Keytab para autenticarse como kadmin para la sincronización. Debe estar "
|
||||
"codificado en base64 o en el formato TIPO:residual"
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid ""
|
||||
@ -2340,7 +2322,7 @@ msgid ""
|
||||
"HTTP@hostname"
|
||||
msgstr ""
|
||||
"Forzar el uso de un nombre de servidor específico para SPNEGO. Debe estar en"
|
||||
" el formato HTTP@nombre_de_host"
|
||||
" el formato HTTP@nombredelservidor"
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path"
|
||||
@ -2357,8 +2339,8 @@ msgid ""
|
||||
"If enabled, the authentik-stored password will be updated upon login with "
|
||||
"the Kerberos password backend"
|
||||
msgstr ""
|
||||
"Si está habilitado, la contraseña almacenada en authentik se actualizará al "
|
||||
"iniciar sesión con el backend de contraseñas de Kerberos."
|
||||
"Si está habilitado, la contraseña almacenada por authentik será actualizada "
|
||||
"al iniciar sesión con el backend de contraseñas Kerberos"
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid "Kerberos Source"
|
||||
@ -2406,7 +2388,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Asegúrate de que tienes entradas válidas\n"
|
||||
" (obtenibles mediante kinit) \n"
|
||||
" (se obtienen a través de kinit) \n"
|
||||
" y de haber configurado correctamente el navegador.\n"
|
||||
" Por favor, contacta a tu administrador.\n"
|
||||
" "
|
||||
@ -2471,10 +2453,6 @@ msgstr "DN de grupo de adición"
|
||||
msgid "Consider Objects matching this filter to be Users."
|
||||
msgstr "Considere que los objetos que coinciden con este filtro son usuarios."
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Attribute which matches the value of `group_membership_field`."
|
||||
msgstr "Atributo que coincide con el valor de `group_membership_field`."
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Field which contains members of a group."
|
||||
msgstr "Campo que contiene los miembros de un grupo."
|
||||
@ -2507,17 +2485,12 @@ msgid ""
|
||||
"attribute. This allows nested group resolution on systems like FreeIPA and "
|
||||
"Active Directory"
|
||||
msgstr ""
|
||||
"Buscar la pertenencia a grupos basándose en un atributo del usuario en lugar"
|
||||
" de un atributo del grupo. Esto permite la resolución de grupos anidados en "
|
||||
"sistemas como FreeIPA y Active Directory"
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid ""
|
||||
"Delete authentik users and groups which were previously supplied by this "
|
||||
"source, but are now missing from it."
|
||||
msgstr ""
|
||||
"Eliminar usuarios y grupos de authentik que fueron proporcionados "
|
||||
"previamente por esta fuente, pero que ahora están ausentes."
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "LDAP Source"
|
||||
@ -2539,24 +2512,22 @@ msgstr "Asignaciones de Propiedades de Fuente de LDAP"
|
||||
msgid ""
|
||||
"Unique ID used while checking if this object still exists in the directory."
|
||||
msgstr ""
|
||||
"ID único utilizado para verificar si este objeto aún existe en el "
|
||||
"directorio."
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connection"
|
||||
msgstr "Conexión de Fuente LDAP de Usuario"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "User LDAP Source Connections"
|
||||
msgstr "Conexiones de Fuente LDAP de Usuario"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connection"
|
||||
msgstr "Conexión de Fuente LDAP de Grupo"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/models.py
|
||||
msgid "Group LDAP Source Connections"
|
||||
msgstr "Conexiones de Fuente LDAP de Grupo"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/ldap/signals.py
|
||||
msgid "Password does not match Active Directory Complexity."
|
||||
@ -2568,11 +2539,11 @@ msgstr "No se recibió ningún token."
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "HTTP Basic Authentication"
|
||||
msgstr "Autenticación Básica HTTP"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Include the client ID and secret as request parameters"
|
||||
msgstr "Incluir el ID de cliente y el secreto como parámetros de la solicitud"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Request Token URL"
|
||||
@ -2619,8 +2590,6 @@ msgid ""
|
||||
"How to perform authentication during an authorization_code token request "
|
||||
"flow"
|
||||
msgstr ""
|
||||
"Cómo realizar la autenticación durante un flujo de solicitud de token con "
|
||||
"authorization_code"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OAuth Source"
|
||||
@ -2938,7 +2907,7 @@ msgstr "Conexiones de Fuente de SAML de Grupo"
|
||||
#: authentik/sources/saml/views.py
|
||||
#, python-brace-format
|
||||
msgid "Continue to {source_name}"
|
||||
msgstr "Continuar a {source_name}"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/sources/scim/models.py
|
||||
msgid "SCIM Source"
|
||||
@ -2974,7 +2943,7 @@ msgstr "Dispositivos Duo"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email OTP"
|
||||
msgstr "OTP por Correo Electrónico"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/email/models.py
|
||||
@ -2995,11 +2964,11 @@ msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stage"
|
||||
msgstr "Etapa de Configuración del Autenticador de Correo Electrónico"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stages"
|
||||
msgstr "Etapas de Configuración del Autenticador de Correo Electrónico"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
@ -3010,11 +2979,11 @@ msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Device"
|
||||
msgstr "Dispositivo de correo electrónico"
|
||||
msgstr "Dispositivo de Email"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Devices"
|
||||
msgstr "Dispositivos de correo electrónico"
|
||||
msgstr "Dispositivos de Email"
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
@ -3024,7 +2993,7 @@ msgstr "El código no coincide"
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
msgid "Invalid email"
|
||||
msgstr "Correo electrónico inválido"
|
||||
msgstr "Email Inválido"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
@ -3044,9 +3013,6 @@ msgid ""
|
||||
" Email MFA code.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Código MFA por correo electrónico.\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#, python-format
|
||||
@ -3056,8 +3022,7 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Si no solicitaste este código, por favor ignora este correo. El código anterior es válido por %(expires)s.\n"
|
||||
" "
|
||||
"Si no solicitaste este código, por favor ignora este correo. El código anterior es válido por %(expires)s."
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
@ -3070,8 +3035,6 @@ msgid ""
|
||||
"\n"
|
||||
"Email MFA code\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Código MFA por correo electrónico\n"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#, python-format
|
||||
@ -3313,8 +3276,8 @@ msgstr "No se pudo validar el token"
|
||||
msgid ""
|
||||
"Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
|
||||
msgstr ""
|
||||
"Desfase después del cual expira el consentimiento. (Formato: "
|
||||
"hours=1;minutes=2;seconds=3)."
|
||||
"Compensación después de la cual caduca el consentimiento. (Formato: horas = "
|
||||
"1; minutos = 2; segundos = 3)."
|
||||
|
||||
#: authentik/stages/consent/models.py
|
||||
msgid "Consent Stage"
|
||||
@ -3334,7 +3297,7 @@ msgstr "Consentimientos del usuario"
|
||||
|
||||
#: authentik/stages/consent/stage.py
|
||||
msgid "Invalid consent token, re-showing prompt"
|
||||
msgstr "Token de consentimiento inválido, mostrando el aviso nuevamente"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/deny/models.py
|
||||
msgid "Deny Stage"
|
||||
@ -3354,11 +3317,11 @@ msgstr "Etapas ficticias"
|
||||
|
||||
#: authentik/stages/email/flow.py
|
||||
msgid "Continue to confirm this email address."
|
||||
msgstr "Continúa para confirmar esta dirección de correo electrónico."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/flow.py
|
||||
msgid "Link was already used, please request a new link."
|
||||
msgstr "El enlace ya fue utilizado, por favor, solícita uno nuevo."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid "Password Reset"
|
||||
@ -3482,8 +3445,7 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Si no solicitaste un cambio de contraseña, por favor ignora este correo. El enlace anterior es válido por %(expires)s.\n"
|
||||
" "
|
||||
"Si no solicitaste un cambio de contraseña, por favor ignora este correo. El enlace anterior es válido por %(expires)s."
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
msgid ""
|
||||
@ -3567,26 +3529,24 @@ msgid ""
|
||||
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||
" to skip straight to entering their password."
|
||||
msgstr ""
|
||||
"Mostrar al usuario la opción \"Recordarme en este dispositivo\", permitiendo"
|
||||
" que los usuarios recurrentes pasen directamente a ingresar su contraseña."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||
msgstr ""
|
||||
"Flujo de inscripción opcional, que se enlaza en la parte inferior de la "
|
||||
"página."
|
||||
"Flujo de inscripción opcional, que está vinculado en la parte inferior de la"
|
||||
" página."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional recovery flow, which is linked at the bottom of the page."
|
||||
msgstr ""
|
||||
"Flujo de recuperación opcional, que se enlaza en la parte inferior de la "
|
||||
"página."
|
||||
"Flujo de recuperación opcional, que está vinculado en la parte inferior de "
|
||||
"la página."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Optional passwordless flow, which is linked at the bottom of the page."
|
||||
msgstr ""
|
||||
"Flujo opcional sin contraseña, que se enlaza en la parte inferior de la "
|
||||
"página."
|
||||
"Flujo sin contraseña opcional, el cual está vinculado en la parte inferior "
|
||||
"de la página."
|
||||
|
||||
#: authentik/stages/identification/models.py
|
||||
msgid "Specify which sources should be shown."
|
||||
@ -3820,11 +3780,11 @@ msgstr "Las contraseñas no coinciden."
|
||||
|
||||
#: authentik/stages/redirect/api.py
|
||||
msgid "Target URL should be present when mode is Static."
|
||||
msgstr "La URL de destino debe estar presente cuando el modo es Estático."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/redirect/api.py
|
||||
msgid "Target Flow should be present when mode is Flow."
|
||||
msgstr "El Flujo de Destino debe estar presente cuando el modo es Flujo."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/redirect/models.py
|
||||
msgid "Redirect Stage"
|
||||
@ -3881,6 +3841,10 @@ msgstr "Etapas de inicio de"
|
||||
msgid "No Pending user to login."
|
||||
msgstr "Ningún usuario pendiente para iniciar sesión."
|
||||
|
||||
#: authentik/stages/user_login/stage.py
|
||||
msgid "Successfully logged in!"
|
||||
msgstr "¡Se ha iniciado sesión correctamente!"
|
||||
|
||||
#: authentik/stages/user_logout/models.py
|
||||
msgid "User Logout Stage"
|
||||
msgstr "Etapa de cierre de sesión del usuario"
|
||||
@ -3956,12 +3920,10 @@ msgstr ""
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||
msgstr ""
|
||||
"La reputación no puede disminuir por debajo de este valor. Cero o negativo."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||
msgstr ""
|
||||
"La reputación no puede aumentar por encima de este valor. Cero o positivo."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "The option configures the footer links on the flow executor pages."
|
||||
@ -3984,8 +3946,8 @@ msgstr "Personificación habilitada/deshabilitada globalmente."
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Require administrators to provide a reason for impersonating a user."
|
||||
msgstr ""
|
||||
"Requerir que los administradores proporcionen una razón para personificar a "
|
||||
"un usuario."
|
||||
"Requerir a los administradores proporcionar una razón para suplantar un "
|
||||
"usuario."
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Default token duration"
|
||||
@ -3997,7 +3959,7 @@ msgstr "Longitud predeterminada del token"
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Tenant"
|
||||
msgstr "Inquilino"
|
||||
msgstr "inquilino"
|
||||
|
||||
#: authentik/tenants/models.py
|
||||
msgid "Tenants"
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.6.2",
|
||||
"version": "2025.6.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.6.2",
|
||||
"version": "2025.6.1",
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"prettier": "^3.3.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.6.2",
|
||||
"version": "2025.6.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
|
182
packages/eslint-config/package-lock.json
generated
182
packages/eslint-config/package-lock.json
generated
@ -216,9 +216,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array": {
|
||||
"version": "0.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz",
|
||||
"integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==",
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
|
||||
"integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/object-schema": "^2.1.6",
|
||||
@ -274,9 +274,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
|
||||
"integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==",
|
||||
"version": "9.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz",
|
||||
"integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -576,17 +576,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
||||
"integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz",
|
||||
"integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/type-utils": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"@typescript-eslint/scope-manager": "8.34.0",
|
||||
"@typescript-eslint/type-utils": "8.34.0",
|
||||
"@typescript-eslint/utils": "8.34.0",
|
||||
"@typescript-eslint/visitor-keys": "8.34.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@ -600,7 +600,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.34.1",
|
||||
"@typescript-eslint/parser": "^8.34.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
@ -616,16 +616,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz",
|
||||
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz",
|
||||
"integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"@typescript-eslint/scope-manager": "8.34.0",
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
"@typescript-eslint/typescript-estree": "8.34.0",
|
||||
"@typescript-eslint/visitor-keys": "8.34.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -641,14 +641,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz",
|
||||
"integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz",
|
||||
"integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.34.1",
|
||||
"@typescript-eslint/types": "^8.34.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.34.0",
|
||||
"@typescript-eslint/types": "^8.34.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -663,14 +663,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz",
|
||||
"integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz",
|
||||
"integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1"
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
"@typescript-eslint/visitor-keys": "8.34.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -681,9 +681,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz",
|
||||
"integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz",
|
||||
"integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -698,14 +698,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz",
|
||||
"integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz",
|
||||
"integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.0",
|
||||
"@typescript-eslint/utils": "8.34.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@ -722,9 +722,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz",
|
||||
"integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz",
|
||||
"integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -736,16 +736,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz",
|
||||
"integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz",
|
||||
"integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.34.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"@typescript-eslint/project-service": "8.34.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.34.0",
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
"@typescript-eslint/visitor-keys": "8.34.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@ -765,9 +765,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -804,16 +804,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz",
|
||||
"integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz",
|
||||
"integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1"
|
||||
"@typescript-eslint/scope-manager": "8.34.0",
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
"@typescript-eslint/typescript-estree": "8.34.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -828,14 +828,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz",
|
||||
"integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz",
|
||||
"integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -846,9 +846,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@ -1554,18 +1554,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
|
||||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||
"version": "9.28.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz",
|
||||
"integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.20.1",
|
||||
"@eslint/config-array": "^0.20.0",
|
||||
"@eslint/config-helpers": "^0.2.1",
|
||||
"@eslint/core": "^0.14.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.29.0",
|
||||
"@eslint/js": "9.28.0",
|
||||
"@eslint/plugin-kit": "^0.3.1",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
@ -1577,9 +1577,9 @@
|
||||
"cross-spawn": "^7.0.6",
|
||||
"debug": "^4.3.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"eslint-scope": "^8.4.0",
|
||||
"eslint-visitor-keys": "^4.2.1",
|
||||
"espree": "^10.4.0",
|
||||
"eslint-scope": "^8.3.0",
|
||||
"eslint-visitor-keys": "^4.2.0",
|
||||
"espree": "^10.3.0",
|
||||
"esquery": "^1.5.0",
|
||||
"esutils": "^2.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@ -1792,9 +1792,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
|
||||
"integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
@ -1808,9 +1808,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-visitor-keys": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
||||
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
|
||||
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -1820,14 +1820,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
||||
"integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"acorn": "^8.15.0",
|
||||
"acorn": "^8.14.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -4035,15 +4035,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz",
|
||||
"integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==",
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz",
|
||||
"integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.34.1",
|
||||
"@typescript-eslint/parser": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.34.0",
|
||||
"@typescript-eslint/parser": "8.34.0",
|
||||
"@typescript-eslint/utils": "8.34.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "authentik"
|
||||
version = "2025.6.2"
|
||||
version = "2025.6.1"
|
||||
description = ""
|
||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||
requires-python = "==3.13.*"
|
||||
@ -48,7 +48,7 @@ dependencies = [
|
||||
"packaging==25.0",
|
||||
"paramiko==3.5.1",
|
||||
"psycopg[c,pool]==3.2.9",
|
||||
"pydantic==2.11.7",
|
||||
"pydantic==2.11.5",
|
||||
"pydantic-scim==0.0.8",
|
||||
"pyjwt==2.10.1",
|
||||
"pyrad==2.4",
|
||||
@ -68,7 +68,7 @@ dependencies = [
|
||||
"urllib3<3",
|
||||
"uvicorn[standard]==0.34.3",
|
||||
"watchdog==6.0.0",
|
||||
"webauthn==2.6.0",
|
||||
"webauthn==2.5.2",
|
||||
"wsproto==1.2.0",
|
||||
"xmlsec==1.3.15",
|
||||
"zxcvbn==4.5.0",
|
||||
@ -141,7 +141,6 @@ skip = [
|
||||
"**/web/src/locales",
|
||||
"**/web/xliff",
|
||||
"**/web/out",
|
||||
"**/web/playwright-report",
|
||||
"./web/storybook-static",
|
||||
"./web/custom-elements.json",
|
||||
"./website/build",
|
||||
|
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2025.6.2
|
||||
version: 2025.6.1
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
|
@ -7,7 +7,7 @@ services:
|
||||
network_mode: host
|
||||
restart: always
|
||||
mailpit:
|
||||
image: docker.io/axllent/mailpit:v1.26.1
|
||||
image: docker.io/axllent/mailpit:v1.26.0
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
|
18
uv.lock
generated
18
uv.lock
generated
@ -165,7 +165,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "authentik"
|
||||
version = "2025.6.2"
|
||||
version = "2025.6.1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "argon2-cffi" },
|
||||
@ -309,7 +309,7 @@ requires-dist = [
|
||||
{ name = "packaging", specifier = "==25.0" },
|
||||
{ name = "paramiko", specifier = "==3.5.1" },
|
||||
{ name = "psycopg", extras = ["c", "pool"], specifier = "==3.2.9" },
|
||||
{ name = "pydantic", specifier = "==2.11.7" },
|
||||
{ name = "pydantic", specifier = "==2.11.5" },
|
||||
{ name = "pydantic-scim", specifier = "==0.0.8" },
|
||||
{ name = "pyjwt", specifier = "==2.10.1" },
|
||||
{ name = "pyrad", specifier = "==2.4" },
|
||||
@ -329,7 +329,7 @@ requires-dist = [
|
||||
{ name = "urllib3", specifier = "<3" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = "==0.34.3" },
|
||||
{ name = "watchdog", specifier = "==6.0.0" },
|
||||
{ name = "webauthn", specifier = "==2.6.0" },
|
||||
{ name = "webauthn", specifier = "==2.5.2" },
|
||||
{ name = "wsproto", specifier = "==1.2.0" },
|
||||
{ name = "xmlsec", specifier = "==1.3.15" },
|
||||
{ name = "zxcvbn", specifier = "==4.5.0" },
|
||||
@ -2463,7 +2463,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.7"
|
||||
version = "2.11.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
@ -2471,9 +2471,9 @@ dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@ -3391,7 +3391,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "webauthn"
|
||||
version = "2.6.0"
|
||||
version = "2.5.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asn1crypto" },
|
||||
@ -3399,9 +3399,9 @@ dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "pyopenssl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/38/5792cb2034673c162a721df0ad65825699516ee0c938a65670ad3cdabf6c/webauthn-2.6.0.tar.gz", hash = "sha256:13cf5b009a64cef569599ffecf24550df1d7c0cd4fbaea870f937148484a80b4", size = 123608, upload-time = "2025-06-16T22:25:26.76Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8d/92/8d2a4eec83d8e7feacdaad37c6eb6eb922100cecce5c14a41d8069a59a03/webauthn-2.5.2.tar.gz", hash = "sha256:09c13dfc1c68c810f32fa4d89b1d37acb9f9ae9091c9d7019e313be4525a95ef", size = 124114, upload-time = "2025-03-07T19:44:05.243Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/56/c5/b1bba7f6a50caca77f37003e098f48f8dc68d990aba8a03ac8376016430b/webauthn-2.6.0-py3-none-any.whl", hash = "sha256:459973eb5780c1f41bec42b682acf587456b185733398a0b99a0714705b79447", size = 71189, upload-time = "2025-06-16T22:25:25.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/fe/f6ae41de9f383439e30b303a67f6f45d2fceabedaedc34c62f74d58c5c73/webauthn-2.5.2-py3-none-any.whl", hash = "sha256:44246e496e617eb5e2f51165046b9f0197fcdf470f69cd6734061a27ba365f8e", size = 71624, upload-time = "2025-03-07T19:44:03.728Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
2
web/.gitignore
vendored
2
web/.gitignore
vendored
@ -25,8 +25,6 @@ lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
playwright-report
|
||||
test-results
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
@ -27,6 +27,11 @@ const inlineCSSPlugin = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @satisfies {InlineConfig}
|
||||
*/
|
||||
// const viteFinal = ;
|
||||
|
||||
/**
|
||||
* @satisfies {StorybookConfig}
|
||||
*/
|
||||
|
@ -1,4 +1,5 @@
|
||||
/// <reference types="../types/webdriver.js" />
|
||||
/// <reference types="@wdio/globals/types" />
|
||||
/// <reference types="./types/webdriver.js" />
|
||||
|
||||
/**
|
||||
*
|
@ -1,89 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { LocatorContext } from "#e2e/selectors/types";
|
||||
import { ConsoleLogger } from "#logger/node";
|
||||
import { Locator, Page, expect } from "@playwright/test";
|
||||
import { kebabCase } from "change-case";
|
||||
|
||||
export type LocatorMatchers = ReturnType<typeof expect<Locator>>;
|
||||
|
||||
export interface LocatorProxy extends Pick<Locator, keyof Locator> {
|
||||
$: Locator;
|
||||
expect: LocatorMatchers;
|
||||
}
|
||||
|
||||
// Type helpers to extract the shape of the proxy
|
||||
export type DeepLocatorProxy<T> =
|
||||
Disposable & T extends Record<string, any>
|
||||
? T extends HTMLElement
|
||||
? LocatorProxy
|
||||
: {
|
||||
[K in keyof T]: DeepLocatorProxy<T[K]>;
|
||||
}
|
||||
: LocatorProxy;
|
||||
|
||||
export function createLocatorProxy<T extends Record<string, any>>(
|
||||
ctx: LocatorContext,
|
||||
initialPathPrefix: string[] = [],
|
||||
dataAttribute: string = "test-id",
|
||||
): DeepLocatorProxy<T> {
|
||||
dataAttribute = kebabCase(dataAttribute);
|
||||
|
||||
function createProxy(path: string[] = initialPathPrefix): any {
|
||||
const proxyCache = new Map<string, LocatorProxy>();
|
||||
|
||||
return new Proxy({} as any, {
|
||||
get(_, property: string) {
|
||||
// Build the current path
|
||||
const currentPath = [...path, property];
|
||||
|
||||
// Convert the path to kebab-case and join with hyphens
|
||||
const selectorValue = currentPath.map((segment) => kebabCase(segment)).join("-");
|
||||
const selector = `[data-${dataAttribute}="${selectorValue}"]`;
|
||||
|
||||
// Create a locator for the current selector
|
||||
const locator = ctx.locator(selector);
|
||||
|
||||
if (proxyCache.has(selector)) {
|
||||
ConsoleLogger.debug(`Using cached locator for ${selector}`);
|
||||
return proxyCache.get(selector)!;
|
||||
}
|
||||
|
||||
// Return a new proxy that also behaves like a Locator
|
||||
// This allows us to either continue chaining or use Locator methods
|
||||
const nextProxy = new Proxy(locator, {
|
||||
get(target, prop) {
|
||||
if (typeof prop === "string") {
|
||||
// The user is likely trying to access a property on the page.
|
||||
if (prop === "$") {
|
||||
return target as any;
|
||||
}
|
||||
|
||||
if (prop === "expect") {
|
||||
return expect(target);
|
||||
}
|
||||
}
|
||||
|
||||
// If the property exists on the Locator, use it
|
||||
if (prop in target) {
|
||||
const value = (target as any)[prop];
|
||||
// Bind methods to the locator instance
|
||||
if (typeof value === "function") {
|
||||
return value.bind(target);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
// Otherwise, continue building the path
|
||||
|
||||
return createProxy(currentPath)[prop];
|
||||
},
|
||||
});
|
||||
|
||||
proxyCache.set(selector, nextProxy as LocatorProxy);
|
||||
|
||||
return nextProxy;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return createProxy() as DeepLocatorProxy<T>;
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
import { PageFixture } from "#e2e/fixtures/PageFixture";
|
||||
import type { LocatorContext } from "#e2e/selectors/types";
|
||||
import { Page, expect } from "@playwright/test";
|
||||
|
||||
export class FormFixture extends PageFixture {
|
||||
static fixtureName = "Form";
|
||||
|
||||
//#region Selector Methods
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Field Methods
|
||||
|
||||
/**
|
||||
* Set the value of a text input.
|
||||
*
|
||||
* @param fieldName The name of the form element.
|
||||
* @param value the value to set.
|
||||
*/
|
||||
public fill = async (
|
||||
fieldName: string,
|
||||
value: string,
|
||||
parent: LocatorContext = this.page,
|
||||
): Promise<void> => {
|
||||
const control = parent
|
||||
.getByRole("textbox", {
|
||||
name: fieldName,
|
||||
})
|
||||
.or(
|
||||
parent.getByRole("spinbutton", {
|
||||
name: fieldName,
|
||||
}),
|
||||
)
|
||||
.first();
|
||||
|
||||
await expect(control, `Field (${fieldName}) should be visible`).toBeVisible();
|
||||
|
||||
await control.fill(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the value of a radio or checkbox input.
|
||||
*
|
||||
* @param fieldName The name of the form element.
|
||||
* @param value the value to set.
|
||||
*/
|
||||
public setInputCheck = async (
|
||||
fieldName: string,
|
||||
value: boolean = true,
|
||||
parent: LocatorContext = this.page,
|
||||
): Promise<void> => {
|
||||
const control = parent.locator("ak-switch-input", {
|
||||
hasText: fieldName,
|
||||
});
|
||||
|
||||
await control.scrollIntoViewIfNeeded();
|
||||
|
||||
await expect(control, `Field (${fieldName}) should be visible`).toBeVisible();
|
||||
|
||||
const currentChecked = await control
|
||||
.getAttribute("checked")
|
||||
.then((value) => value !== null);
|
||||
|
||||
if (currentChecked === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await control.click();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the value of a radio or checkbox input.
|
||||
*
|
||||
* @param fieldName The name of the form element.
|
||||
* @param pattern the value to set.
|
||||
*/
|
||||
public setRadio = async (
|
||||
groupName: string,
|
||||
fieldName: string,
|
||||
parent: LocatorContext = this.page,
|
||||
): Promise<void> => {
|
||||
const group = parent.getByRole("group", { name: groupName });
|
||||
|
||||
await expect(group, `Field "${groupName}" should be visible`).toBeVisible();
|
||||
const control = parent.getByRole("radio", { name: fieldName });
|
||||
|
||||
await control.setChecked(true, {
|
||||
force: true,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the value of a search select input.
|
||||
*
|
||||
* @param fieldLabel The name of the search select element.
|
||||
* @param pattern The text to match against the search select entry.
|
||||
*/
|
||||
public selectSearchValue = async (
|
||||
fieldLabel: string,
|
||||
pattern: string | RegExp,
|
||||
parent: LocatorContext = this.page,
|
||||
): Promise<void> => {
|
||||
const control = parent.getByRole("textbox", { name: fieldLabel });
|
||||
|
||||
await expect(
|
||||
control,
|
||||
`Search select control (${fieldLabel}) should be visible`,
|
||||
).toBeVisible();
|
||||
|
||||
const fieldName = await control.getAttribute("name");
|
||||
|
||||
if (!fieldName) {
|
||||
throw new Error(`Unable to find name attribute on search select (${fieldLabel})`);
|
||||
}
|
||||
|
||||
// Find the search select input control and activate it.
|
||||
await control.click();
|
||||
|
||||
const button = this.page
|
||||
// ---
|
||||
.locator(`div[data-managed-for*="${fieldName}"] button`, {
|
||||
hasText: pattern,
|
||||
});
|
||||
|
||||
if (!button) {
|
||||
throw new Error(
|
||||
`Unable to find an ak-search-select entry matching ${fieldLabel}:${pattern.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
await button.click();
|
||||
await this.page.keyboard.press("Tab");
|
||||
await control.blur();
|
||||
};
|
||||
|
||||
public setFormGroup = async (
|
||||
pattern: string | RegExp,
|
||||
value: boolean = true,
|
||||
parent: LocatorContext = this.page,
|
||||
) => {
|
||||
const control = parent
|
||||
.locator("ak-form-group", {
|
||||
hasText: pattern,
|
||||
})
|
||||
.first();
|
||||
|
||||
const currentOpen = await control.getAttribute("open").then((value) => value !== null);
|
||||
|
||||
if (currentOpen === value) {
|
||||
this.logger.debug(`Form group ${pattern} is already ${value ? "open" : "closed"}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(`Toggling form group ${pattern} to ${value ? "open" : "closed"}`);
|
||||
|
||||
await control.click();
|
||||
|
||||
if (value) {
|
||||
await expect(control).toHaveAttribute("open");
|
||||
} else {
|
||||
await expect(control).not.toHaveAttribute("open");
|
||||
}
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
constructor(page: Page, testName: string) {
|
||||
super({ page, testName });
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import { ConsoleLogger, FixtureLogger } from "#logger/node";
|
||||
import { Page } from "@playwright/test";
|
||||
|
||||
export interface PageFixtureOptions {
|
||||
page: Page;
|
||||
testName: string;
|
||||
}
|
||||
|
||||
export abstract class PageFixture {
|
||||
/**
|
||||
* The name of the fixture.
|
||||
*
|
||||
* Used for logging.
|
||||
*/
|
||||
static fixtureName: string;
|
||||
|
||||
protected readonly logger: FixtureLogger;
|
||||
protected readonly page: Page;
|
||||
protected readonly testName: string;
|
||||
|
||||
constructor({ page, testName }: PageFixtureOptions) {
|
||||
this.page = page;
|
||||
this.testName = testName;
|
||||
|
||||
const Constructor = this.constructor as typeof PageFixture;
|
||||
|
||||
this.logger = ConsoleLogger.fixture(Constructor.fixtureName, this.testName);
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { PageFixture } from "#e2e/fixtures/PageFixture";
|
||||
import type { LocatorContext } from "#e2e/selectors/types";
|
||||
import { Page } from "@playwright/test";
|
||||
|
||||
export type GetByRoleParameters = Parameters<Page["getByRole"]>;
|
||||
export type ARIARole = GetByRoleParameters[0];
|
||||
export type ARIAOptions = GetByRoleParameters[1];
|
||||
|
||||
export type ClickByName = (name: string) => Promise<void>;
|
||||
export type ClickByRole = (
|
||||
role: ARIARole,
|
||||
options?: ARIAOptions,
|
||||
context?: LocatorContext,
|
||||
) => Promise<void>;
|
||||
|
||||
export class PointerFixture extends PageFixture {
|
||||
public static fixtureName = "Pointer";
|
||||
|
||||
public click = (
|
||||
name: string,
|
||||
optionsOrRole?: ARIAOptions | ARIARole,
|
||||
context: LocatorContext = this.page,
|
||||
): Promise<void> => {
|
||||
if (typeof optionsOrRole === "string") {
|
||||
return context.getByRole(optionsOrRole, { name }).click();
|
||||
}
|
||||
|
||||
const options = {
|
||||
...optionsOrRole,
|
||||
name,
|
||||
};
|
||||
|
||||
return (
|
||||
context
|
||||
// ---
|
||||
.getByRole("button", options)
|
||||
.or(context.getByRole("link", options))
|
||||
.click()
|
||||
);
|
||||
};
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
import { PageFixture } from "#e2e/fixtures/PageFixture";
|
||||
import { Page, expect } from "@playwright/test";
|
||||
|
||||
export const GOOD_USERNAME = "test-admin@goauthentik.io";
|
||||
export const GOOD_PASSWORD = "test-runner";
|
||||
|
||||
export const BAD_USERNAME = "bad-username@bad-login.io";
|
||||
export const BAD_PASSWORD = "-this-is-a-bad-password-";
|
||||
|
||||
export interface LoginInit {
|
||||
username?: string;
|
||||
password?: string;
|
||||
to?: URL | string;
|
||||
}
|
||||
|
||||
export class SessionFixture extends PageFixture {
|
||||
static fixtureName = "Session";
|
||||
|
||||
public static readonly pathname = "/if/flow/default-authentication-flow/";
|
||||
|
||||
//#region Selectors
|
||||
|
||||
public $identificationStage = this.page.locator("ak-stage-identification");
|
||||
|
||||
/**
|
||||
* The username field on the login page.
|
||||
*/
|
||||
public $usernameField = this.$identificationStage.locator('input[name="uidField"]');
|
||||
|
||||
/**
|
||||
* The button to continue with the login process,
|
||||
* typically to the password flow stage.
|
||||
*/
|
||||
public $submitUsernameStageButton = this.$identificationStage.locator('button[type="submit"]');
|
||||
|
||||
public $passwordStage = this.page.locator("ak-stage-password");
|
||||
public $passwordField = this.$passwordStage.locator('input[name="password"]');
|
||||
/**
|
||||
* The button to submit the the login flow,
|
||||
* typically redirecting to the authenticated interface.
|
||||
*/
|
||||
public $submitPasswordStageButton = this.$passwordStage.locator('button[type="submit"]');
|
||||
|
||||
/**
|
||||
* A possible authentication failure message.
|
||||
*/
|
||||
public $authFailureMessage = this.page.locator(".pf-m-error");
|
||||
|
||||
//#endregion
|
||||
|
||||
constructor(page: Page, testName: string) {
|
||||
super({ page, testName });
|
||||
}
|
||||
|
||||
//#region Specific interactions
|
||||
|
||||
public async submitUsernameStage(username: string) {
|
||||
this.logger.info("Submitting username stage", username);
|
||||
|
||||
await this.$usernameField.fill(username);
|
||||
|
||||
await expect(this.$submitUsernameStageButton).toBeEnabled();
|
||||
|
||||
await this.$submitUsernameStageButton.click();
|
||||
}
|
||||
|
||||
public async submitPasswordStage(password: string) {
|
||||
this.logger.info("Submitting password stage");
|
||||
|
||||
await this.$passwordField.fill(password);
|
||||
|
||||
await expect(this.$submitPasswordStageButton).toBeEnabled();
|
||||
|
||||
await this.$submitPasswordStageButton.click();
|
||||
}
|
||||
|
||||
public checkAuthenticated = async (): Promise<boolean> => {
|
||||
// TODO: Check if the user is authenticated via API
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Log into the application.
|
||||
*/
|
||||
public async login({
|
||||
username = GOOD_USERNAME,
|
||||
password = GOOD_PASSWORD,
|
||||
to = SessionFixture.pathname,
|
||||
}: LoginInit = {}) {
|
||||
this.logger.info("Logging in...");
|
||||
|
||||
const initialURL = new URL(this.page.url());
|
||||
|
||||
if (initialURL.pathname === SessionFixture.pathname) {
|
||||
this.logger.info("Skipping navigation because we're already in a authentication flow");
|
||||
} else {
|
||||
await this.page.goto(to.toString());
|
||||
}
|
||||
|
||||
await this.submitUsernameStage(username);
|
||||
|
||||
await this.$passwordField.waitFor({ state: "visible" });
|
||||
|
||||
await this.submitPasswordStage(password);
|
||||
|
||||
const expectedPathname = typeof to === "string" ? to : to.pathname;
|
||||
|
||||
await this.page.waitForURL(`**${expectedPathname}`);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Navigation
|
||||
|
||||
public async toLoginPage() {
|
||||
await this.page.goto(SessionFixture.pathname);
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { DeepLocatorProxy, createLocatorProxy } from "#e2e/elements/proxy";
|
||||
import { FormFixture } from "#e2e/fixtures/FormFixture";
|
||||
import { PointerFixture } from "#e2e/fixtures/PointerFixture";
|
||||
import { SessionFixture } from "#e2e/fixtures/SessionFixture";
|
||||
import { createOUIDNameEngine } from "#e2e/selectors/ouid";
|
||||
import { type Page, test as base } from "@playwright/test";
|
||||
|
||||
export { expect } from "@playwright/test";
|
||||
|
||||
type TestIDLocatorProxy = DeepLocatorProxy<TestIDSelectorMap>;
|
||||
|
||||
interface E2EFixturesTestScope {
|
||||
/**
|
||||
* A proxy to retrieve elements by test ID.
|
||||
*
|
||||
* ```ts
|
||||
* const $button = $.button;
|
||||
* ```
|
||||
*/
|
||||
$: TestIDLocatorProxy;
|
||||
session: SessionFixture;
|
||||
pointer: PointerFixture;
|
||||
form: FormFixture;
|
||||
}
|
||||
|
||||
interface E2EWorkerScope {
|
||||
selectorRegistration: void;
|
||||
}
|
||||
|
||||
export const test = base.extend<E2EFixturesTestScope, E2EWorkerScope>({
|
||||
selectorRegistration: [
|
||||
async ({ playwright }, use) => {
|
||||
await playwright.selectors.register("ouid", createOUIDNameEngine);
|
||||
await use();
|
||||
},
|
||||
{ auto: true, scope: "worker" },
|
||||
],
|
||||
|
||||
$: async ({ page }, use) => {
|
||||
await use(createLocatorProxy<TestIDSelectorMap>(page));
|
||||
},
|
||||
|
||||
session: async ({ page }, use, { title }) => {
|
||||
await use(new SessionFixture(page, title));
|
||||
},
|
||||
|
||||
form: async ({ page }, use, { title }) => {
|
||||
await use(new FormFixture(page, title));
|
||||
},
|
||||
|
||||
pointer: async ({ page }, use, { title }) => {
|
||||
await use(new PointerFixture({ page, testName: title }));
|
||||
},
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
type SelectorRoot = Document | ShadowRoot;
|
||||
|
||||
export function createOUIDNameEngine() {
|
||||
const attributeName = "data-ouid-component-name";
|
||||
|
||||
console.log("Creating OUID selector engine!!");
|
||||
return {
|
||||
// Returns all elements matching given selector in the root's subtree.
|
||||
queryAll(scope: SelectorRoot, componentName: string) {
|
||||
const result: Element[] = [];
|
||||
|
||||
const match = (element: Element) => {
|
||||
const name = element.getAttribute(attributeName);
|
||||
|
||||
if (name === componentName) {
|
||||
result.push(element);
|
||||
}
|
||||
};
|
||||
|
||||
const query = (root: Element | ShadowRoot | Document) => {
|
||||
const shadows: ShadowRoot[] = [];
|
||||
|
||||
if ((root as Element).shadowRoot) {
|
||||
shadows.push((root as Element).shadowRoot!);
|
||||
}
|
||||
|
||||
for (const element of root.querySelectorAll("*")) {
|
||||
match(element);
|
||||
|
||||
if (element.shadowRoot) {
|
||||
shadows.push(element.shadowRoot);
|
||||
}
|
||||
}
|
||||
|
||||
shadows.forEach(query);
|
||||
};
|
||||
|
||||
query(scope);
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import type { Locator } from "@playwright/test";
|
||||
|
||||
export type LocatorContext = Pick<
|
||||
Locator,
|
||||
| "locator"
|
||||
| "getByRole"
|
||||
| "getByTestId"
|
||||
| "getByText"
|
||||
| "getByLabel"
|
||||
| "getByAltText"
|
||||
| "getByTitle"
|
||||
| "getByPlaceholder"
|
||||
>;
|
@ -1,59 +0,0 @@
|
||||
import { IDGenerator } from "@goauthentik/core/id";
|
||||
import {
|
||||
Config as NameConfig,
|
||||
adjectives,
|
||||
colors,
|
||||
uniqueNamesGenerator,
|
||||
} from "unique-names-generator";
|
||||
|
||||
/**
|
||||
* Given a dictionary of words, slice the dictionary to only include words that start with the given letter.
|
||||
*/
|
||||
export function alliterate(dictionary: string[], letter: string): string[] {
|
||||
let firstIndex = 0;
|
||||
|
||||
for (let i = 0; i < dictionary.length; i++) {
|
||||
if (dictionary[i][0] === letter) {
|
||||
firstIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let lastIndex = firstIndex;
|
||||
|
||||
for (let i = firstIndex; i < dictionary.length; i++) {
|
||||
if (dictionary[i][0] !== letter) {
|
||||
lastIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return dictionary.slice(firstIndex, lastIndex);
|
||||
}
|
||||
|
||||
export function createRandomName({
|
||||
seed = IDGenerator.randomID(),
|
||||
...config
|
||||
}: Partial<NameConfig> = {}) {
|
||||
const randomLetterIndex =
|
||||
typeof seed === "number"
|
||||
? seed
|
||||
: Array.from(seed).reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||
|
||||
const letter = adjectives[randomLetterIndex % adjectives.length][0];
|
||||
|
||||
const availableAdjectives = alliterate(adjectives, letter);
|
||||
|
||||
const availableColors = alliterate(colors, letter);
|
||||
|
||||
const name = uniqueNamesGenerator({
|
||||
dictionaries: [availableAdjectives, availableAdjectives, availableColors],
|
||||
style: "capital",
|
||||
separator: " ",
|
||||
length: 3,
|
||||
seed,
|
||||
...config,
|
||||
});
|
||||
|
||||
return name;
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
/**
|
||||
* Application logger.
|
||||
*
|
||||
* @import { LoggerOptions, Logger, Level, ChildLoggerOptions } from "pino"
|
||||
* @import { PrettyOptions } from "pino-pretty"
|
||||
*/
|
||||
import { pino } from "pino";
|
||||
|
||||
//#region Constants
|
||||
|
||||
/**
|
||||
* Default options for creating a Pino logger.
|
||||
*
|
||||
* @category Logger
|
||||
* @satisfies {LoggerOptions<never, false>}
|
||||
*/
|
||||
export const DEFAULT_PINO_LOGGER_OPTIONS = {
|
||||
enabled: true,
|
||||
level: "info",
|
||||
transport: {
|
||||
target: "./transport.js",
|
||||
options: /** @satisfies {PrettyOptions} */ ({
|
||||
colorize: true,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Functions
|
||||
|
||||
/**
|
||||
* Read the log level from the environment.
|
||||
* @return {Level}
|
||||
*/
|
||||
export function readLogLevel() {
|
||||
return process.env.AK_LOG_LEVEL || DEFAULT_PINO_LOGGER_OPTIONS.level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Logger} FixtureLogger
|
||||
*/
|
||||
|
||||
/**
|
||||
* @this {Logger}
|
||||
* @param {string} fixtureName
|
||||
* @param {string} [testName]
|
||||
* @param {ChildLoggerOptions} [options]
|
||||
* @returns {FixtureLogger}
|
||||
*/
|
||||
function createFixtureLogger(fixtureName, testName, options) {
|
||||
return this.child(
|
||||
{ name: fixtureName },
|
||||
{
|
||||
msgPrefix: `[${testName}] `,
|
||||
...options,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} CustomLoggerMethods
|
||||
* @property {typeof createFixtureLogger} fixture
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Logger & CustomLoggerMethods} ConsoleLogger
|
||||
*/
|
||||
|
||||
/**
|
||||
* A singleton logger instance for Node.js.
|
||||
*
|
||||
* ```js
|
||||
* import { ConsoleLogger } from "#logger/node";
|
||||
*
|
||||
* ConsoleLogger.info("Hello, world!");
|
||||
* ```
|
||||
*
|
||||
* @runtime node
|
||||
* @type {ConsoleLogger}
|
||||
*/
|
||||
export const ConsoleLogger = Object.assign(
|
||||
pino({
|
||||
...DEFAULT_PINO_LOGGER_OPTIONS,
|
||||
level: readLogLevel(),
|
||||
}),
|
||||
{ fixture: createFixtureLogger },
|
||||
);
|
||||
|
||||
/**
|
||||
* @typedef {ReturnType<ConsoleLogger['child']>} ChildConsoleLogger
|
||||
*/
|
||||
|
||||
//#region Aliases
|
||||
|
||||
export const info = ConsoleLogger.info.bind(ConsoleLogger);
|
||||
export const debug = ConsoleLogger.debug.bind(ConsoleLogger);
|
||||
export const warn = ConsoleLogger.warn.bind(ConsoleLogger);
|
||||
export const error = ConsoleLogger.error.bind(ConsoleLogger);
|
||||
|
||||
//#endregion
|
@ -1,21 +0,0 @@
|
||||
/**
|
||||
* @file Pretty transport for Pino
|
||||
*
|
||||
* @import { PrettyOptions } from "pino-pretty"
|
||||
*/
|
||||
import PinoPretty from "pino-pretty";
|
||||
|
||||
/**
|
||||
* @param {PrettyOptions} options
|
||||
*/
|
||||
function prettyTransporter(options) {
|
||||
const pretty = PinoPretty({
|
||||
...options,
|
||||
ignore: "pid,hostname",
|
||||
translateTime: "SYS:HH:MM:ss",
|
||||
});
|
||||
|
||||
return pretty;
|
||||
}
|
||||
|
||||
export default prettyTransporter;
|
9151
web/package-lock.json
generated
9151
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -24,8 +24,8 @@
|
||||
"pseudolocalize": "node ./scripts/pseudolocalize.mjs",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:build": "wireit",
|
||||
"test": "vitest",
|
||||
"test:e2e": "playwright test",
|
||||
"test": "wireit",
|
||||
"test:e2e": "wireit",
|
||||
"test:e2e:watch": "wireit",
|
||||
"test:watch": "wireit",
|
||||
"tsc": "wireit",
|
||||
@ -69,9 +69,6 @@
|
||||
"#flow/*": "./src/flow/*.js",
|
||||
"#locales/*": "./src/locales/*.js",
|
||||
"#stories/*": "./src/stories/*.js",
|
||||
"#tests/*": "./tests/*.js",
|
||||
"#e2e": "./e2e/index.ts",
|
||||
"#e2e/*": "./e2e/*.ts",
|
||||
"#*/browser": {
|
||||
"types": "./out/*/browser.d.ts",
|
||||
"import": "./*/browser.js"
|
||||
@ -105,8 +102,8 @@
|
||||
"@open-wc/lit-helpers": "^0.7.0",
|
||||
"@patternfly/elements": "^4.1.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^9.28.0",
|
||||
"@spotlightjs/spotlight": "^2.13.3",
|
||||
"@sentry/browser": "^9.28.1",
|
||||
"@spotlightjs/spotlight": "^3.0.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"change-case": "^5.4.4",
|
||||
@ -124,9 +121,8 @@
|
||||
"hastscript": "^9.0.1",
|
||||
"lit": "^3.2.0",
|
||||
"md-front-matter": "^1.0.4",
|
||||
"mermaid": "^11.4.1",
|
||||
"pino": "^9.7.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"mermaid": "^11.6.0",
|
||||
"ninja-keys": "^1.2.2",
|
||||
"rapidoc": "^9.3.8",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
@ -154,7 +150,6 @@
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@hcaptcha/types": "^1.0.4",
|
||||
"@lit/localize-tools": "^0.8.0",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@storybook/addon-essentials": "^8.6.14",
|
||||
"@storybook/addon-links": "^8.6.14",
|
||||
"@storybook/blocks": "^8.6.12",
|
||||
@ -164,7 +159,6 @@
|
||||
"@storybook/test": "^8.6.14",
|
||||
"@storybook/web-components": "^8.6.14",
|
||||
"@storybook/web-components-vite": "^8.6.14",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@types/chart.js": "^2.9.41",
|
||||
"@types/codemirror": "^5.60.15",
|
||||
@ -177,10 +171,12 @@
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@typescript-eslint/eslint-plugin": "^8.8.0",
|
||||
"@typescript-eslint/parser": "^8.8.0",
|
||||
"@vitest/browser": "^3.2.0",
|
||||
"@wdio/cli": "^9.15.0",
|
||||
"@wdio/spec-reporter": "^9.15.0",
|
||||
"esbuild": "^0.25.4",
|
||||
"@wdio/browser-runner": "9.4",
|
||||
"@wdio/cli": "9.4",
|
||||
"@wdio/spec-reporter": "^9.1.2",
|
||||
"@web/test-runner": "^0.20.2",
|
||||
"chromedriver": "^136.0.3",
|
||||
"esbuild": "^0.25.5",
|
||||
"esbuild-plugin-copy": "^2.1.1",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"esbuild-plugins-node-modules-polyfill": "^1.7.0",
|
||||
@ -192,22 +188,16 @@
|
||||
"knip": "^5.58.0",
|
||||
"lit-analyzer": "^2.0.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"p-iteration": "^1.1.8",
|
||||
"playwright": "^1.52.0",
|
||||
"prettier": "^3.3.3",
|
||||
"pseudolocale": "^2.1.0",
|
||||
"rollup-plugin-postcss-lit": "^2.2.0",
|
||||
"storybook": "^8.6.14",
|
||||
"storybook-addon-mock": "^5.0.0",
|
||||
"turnstile-types": "^1.2.3",
|
||||
"type-fest": "^4.41.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.34.0",
|
||||
"unique-names-generator": "^4.7.1",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-lit-css": "^2.1.0",
|
||||
"vite-plugin-lit-css": "^2.0.0",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"vitest": "^3.2.0",
|
||||
"wireit": "^0.14.12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
@ -289,7 +279,7 @@
|
||||
"command": "lit-analyzer src"
|
||||
},
|
||||
"lint:types:tests": {
|
||||
"command": "tsc --noEmit -p tsconfig.test.json"
|
||||
"command": "tsc --noEmit -p ./tests"
|
||||
},
|
||||
"lint:types": {
|
||||
"command": "tsc -p .",
|
||||
@ -325,33 +315,33 @@
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"command": "wdio ./wdio.conf.js --logLevel=warn",
|
||||
"command": "wdio ./wdio.conf.ts --logLevel=warn",
|
||||
"env": {
|
||||
"CI": "true",
|
||||
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"test:e2e": {
|
||||
"command": "wdio run ./tests/wdio.conf.js",
|
||||
"command": "wdio run ./tests/wdio.conf.ts",
|
||||
"dependencies": [
|
||||
"build"
|
||||
],
|
||||
"env": {
|
||||
"CI": "true",
|
||||
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"test:e2e:watch": {
|
||||
"command": "wdio run ./tests/wdio.conf.js",
|
||||
"command": "wdio run ./tests/wdio.conf.ts",
|
||||
"dependencies": [
|
||||
"build"
|
||||
],
|
||||
"env": {
|
||||
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"test:watch": {
|
||||
"command": "wdio run ./wdio.conf.js",
|
||||
"command": "wdio run ./wdio.conf.ts",
|
||||
"dependencies": [
|
||||
"build"
|
||||
],
|
||||
|
@ -1,50 +0,0 @@
|
||||
/**
|
||||
* @file Unique ID utilities.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A global ID generator.
|
||||
*
|
||||
* @singleton
|
||||
* @runtime common
|
||||
*
|
||||
* @category IDs
|
||||
*/
|
||||
export class IDGenerator {
|
||||
static #sequenceIndex = 0;
|
||||
static #elementIndex = 0;
|
||||
|
||||
/**
|
||||
* Create a new ID for an HTML element.
|
||||
*
|
||||
* This ID will be unique for the lifetime of the page and will not be
|
||||
* exposed on the `window` object.
|
||||
*
|
||||
* @param {string | number} [name] An optional name to use for the element.
|
||||
*/
|
||||
static elementID(name) {
|
||||
name = name || ++this.#elementIndex;
|
||||
|
||||
return "«ak-" + name + "»";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ID.
|
||||
*/
|
||||
static next() {
|
||||
this.#sequenceIndex += 1;
|
||||
|
||||
return this.#sequenceIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random ID in hexadecimal format.
|
||||
*
|
||||
* @param {number} [characterLength]
|
||||
*/
|
||||
static randomID(characterLength = 6) {
|
||||
const bytes = crypto.getRandomValues(new Uint8Array(characterLength / 2));
|
||||
|
||||
return Array.from(bytes, (a) => a.toString(16)).join("");
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @file Helpers for running tests.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A function that returns a promise.
|
||||
* @template {never[]} [A=never[]]
|
||||
* @typedef {(...args: A) => Promise<unknown>} Thenable
|
||||
*/
|
||||
|
||||
/**
|
||||
* A tuple of a function and its arguments.
|
||||
* @template {Thenable} [T=Thenable]
|
||||
* @typedef {[T, Parameters<T>]} SerializedThenable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes a sequence of promise-returning functions in series
|
||||
* @template {Thenable[]} T
|
||||
* @param {{ [K in keyof T]: [T[K], ...Parameters<T[K]>] }} sequence
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function series(...sequence) {
|
||||
for (const [thenable, ...args] of sequence) {
|
||||
await thenable(...args);
|
||||
}
|
||||
}
|
@ -11,11 +11,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@goauthentik/api": "^2024.6.0-1719577139",
|
||||
"base64-js": "^1.5.1",
|
||||
"bootstrap": "^4.6.1",
|
||||
"formdata-polyfill": "^4.0.10",
|
||||
"jquery": "^3.7.1",
|
||||
"weakmap-polyfill": "^2.0.4",
|
||||
"webauthn-polyfills": "^0.1.7"
|
||||
"weakmap-polyfill": "^2.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@goauthentik/core": "^1.0.0",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { fromByteArray } from "base64-js";
|
||||
import "formdata-polyfill";
|
||||
import $ from "jquery";
|
||||
import "weakmap-polyfill";
|
||||
import "webauthn-polyfills";
|
||||
|
||||
import {
|
||||
type AuthenticatorValidationChallenge,
|
||||
@ -257,9 +257,47 @@ class AutosubmitStage extends Stage<AutosubmitChallenge> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface Assertion {
|
||||
id: string;
|
||||
rawId: string;
|
||||
type: string;
|
||||
registrationClientExtensions: string;
|
||||
response: {
|
||||
clientDataJSON: string;
|
||||
attestationObject: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AuthAssertion {
|
||||
id: string;
|
||||
rawId: string;
|
||||
type: string;
|
||||
assertionClientExtensions: string;
|
||||
response: {
|
||||
clientDataJSON: string;
|
||||
authenticatorData: string;
|
||||
signature: string;
|
||||
userHandle: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge> {
|
||||
deviceChallenge?: DeviceChallenge;
|
||||
|
||||
b64enc(buf: Uint8Array): string {
|
||||
return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
||||
}
|
||||
|
||||
b64RawEnc(buf: Uint8Array): string {
|
||||
return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_");
|
||||
}
|
||||
|
||||
u8arr(input: string): Uint8Array {
|
||||
return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) =>
|
||||
c.charCodeAt(0),
|
||||
);
|
||||
}
|
||||
|
||||
checkWebAuthnSupport(): boolean {
|
||||
if ("credentials" in navigator) {
|
||||
return true;
|
||||
@ -272,6 +310,98 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms items in the credentialCreateOptions generated on the server
|
||||
* into byte arrays expected by the navigator.credentials.create() call
|
||||
*/
|
||||
transformCredentialCreateOptions(
|
||||
credentialCreateOptions: PublicKeyCredentialCreationOptions,
|
||||
userId: string,
|
||||
): PublicKeyCredentialCreationOptions {
|
||||
const user = credentialCreateOptions.user;
|
||||
// Because json can't contain raw bytes, the server base64-encodes the User ID
|
||||
// So to get the base64 encoded byte array, we first need to convert it to a regular
|
||||
// string, then a byte array, re-encode it and wrap that in an array.
|
||||
const stringId = decodeURIComponent(window.atob(userId));
|
||||
user.id = this.u8arr(this.b64enc(this.u8arr(stringId)));
|
||||
const challenge = this.u8arr(credentialCreateOptions.challenge.toString());
|
||||
|
||||
return Object.assign({}, credentialCreateOptions, {
|
||||
challenge,
|
||||
user,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the binary data in the credential into base64 strings
|
||||
* for posting to the server.
|
||||
* @param {PublicKeyCredential} newAssertion
|
||||
*/
|
||||
transformNewAssertionForServer(newAssertion: PublicKeyCredential): Assertion {
|
||||
const attObj = new Uint8Array(
|
||||
(newAssertion.response as AuthenticatorAttestationResponse).attestationObject,
|
||||
);
|
||||
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON);
|
||||
const rawId = new Uint8Array(newAssertion.rawId);
|
||||
|
||||
const registrationClientExtensions = newAssertion.getClientExtensionResults();
|
||||
return {
|
||||
id: newAssertion.id,
|
||||
rawId: this.b64enc(rawId),
|
||||
type: newAssertion.type,
|
||||
registrationClientExtensions: JSON.stringify(registrationClientExtensions),
|
||||
response: {
|
||||
clientDataJSON: this.b64enc(clientDataJSON),
|
||||
attestationObject: this.b64enc(attObj),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
transformCredentialRequestOptions(
|
||||
credentialRequestOptions: PublicKeyCredentialRequestOptions,
|
||||
): PublicKeyCredentialRequestOptions {
|
||||
const challenge = this.u8arr(credentialRequestOptions.challenge.toString());
|
||||
|
||||
const allowCredentials = (credentialRequestOptions.allowCredentials || []).map(
|
||||
(credentialDescriptor) => {
|
||||
const id = this.u8arr(credentialDescriptor.id.toString());
|
||||
return Object.assign({}, credentialDescriptor, { id });
|
||||
},
|
||||
);
|
||||
|
||||
return Object.assign({}, credentialRequestOptions, {
|
||||
challenge,
|
||||
allowCredentials,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the binary data in the assertion into strings for posting to the server.
|
||||
* @param {PublicKeyCredential} newAssertion
|
||||
*/
|
||||
transformAssertionForServer(newAssertion: PublicKeyCredential): AuthAssertion {
|
||||
const response = newAssertion.response as AuthenticatorAssertionResponse;
|
||||
const authData = new Uint8Array(response.authenticatorData);
|
||||
const clientDataJSON = new Uint8Array(response.clientDataJSON);
|
||||
const rawId = new Uint8Array(newAssertion.rawId);
|
||||
const sig = new Uint8Array(response.signature);
|
||||
const assertionClientExtensions = newAssertion.getClientExtensionResults();
|
||||
|
||||
return {
|
||||
id: newAssertion.id,
|
||||
rawId: this.b64enc(rawId),
|
||||
type: newAssertion.type,
|
||||
assertionClientExtensions: JSON.stringify(assertionClientExtensions),
|
||||
|
||||
response: {
|
||||
clientDataJSON: this.b64RawEnc(clientDataJSON),
|
||||
signature: this.b64RawEnc(sig),
|
||||
authenticatorData: this.b64RawEnc(authData),
|
||||
userHandle: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.challenge.deviceChallenges.length === 1) {
|
||||
this.deviceChallenge = this.challenge.deviceChallenges[0];
|
||||
@ -375,8 +505,8 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
|
||||
`);
|
||||
navigator.credentials
|
||||
.get({
|
||||
publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(
|
||||
this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptionsJSON,
|
||||
publicKey: this.transformCredentialRequestOptions(
|
||||
this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptions,
|
||||
),
|
||||
})
|
||||
.then((assertion) => {
|
||||
@ -384,9 +514,15 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
|
||||
throw new Error("No assertion");
|
||||
}
|
||||
try {
|
||||
// we now have an authentication assertion! encode the byte arrays contained
|
||||
// in the assertion data as strings for posting to the server
|
||||
const transformedAssertionForServer = this.transformAssertionForServer(
|
||||
assertion as PublicKeyCredential,
|
||||
);
|
||||
|
||||
// post the assertion to the server for verification.
|
||||
this.executor.submit({
|
||||
webauthn: (assertion as PublicKeyCredential).toJSON(),
|
||||
webauthn: transformedAssertionForServer,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Error when validating assertion on server: ${err}`);
|
||||
|
@ -1,92 +0,0 @@
|
||||
/**
|
||||
* @file Playwright configuration.
|
||||
*
|
||||
* @see https://playwright.dev/docs/test-configuration
|
||||
*
|
||||
* @import { LogFn, Logger } from "pino"
|
||||
*/
|
||||
import { ConsoleLogger } from "#logger/node";
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
const CI = !!process.env.CI;
|
||||
|
||||
/**
|
||||
* @type {Map<string, Logger>}
|
||||
*/
|
||||
const LoggerCache = new Map();
|
||||
|
||||
const baseURL = process.env.AK_TEST_RUNNER_PAGE_URL ?? "http://localhost:9000";
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./test/browser",
|
||||
fullyParallel: true,
|
||||
forbidOnly: CI,
|
||||
retries: CI ? 2 : 0,
|
||||
workers: CI ? 1 : undefined,
|
||||
reporter: CI
|
||||
? "github"
|
||||
: [
|
||||
// ---
|
||||
["list", { printSteps: true }],
|
||||
["html", { open: "never" }],
|
||||
],
|
||||
use: {
|
||||
testIdAttribute: "data-test-id",
|
||||
baseURL,
|
||||
trace: "on-first-retry",
|
||||
launchOptions: {
|
||||
logger: {
|
||||
isEnabled() {
|
||||
return true;
|
||||
},
|
||||
log: (name, severity, message, args) => {
|
||||
let logger = LoggerCache.get(name);
|
||||
|
||||
if (!logger) {
|
||||
logger = ConsoleLogger.child({
|
||||
name: `Playwright ${name.toUpperCase()}`,
|
||||
});
|
||||
LoggerCache.set(name, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {LogFn}
|
||||
*/
|
||||
let log;
|
||||
|
||||
switch (severity) {
|
||||
case "verbose":
|
||||
log = logger.debug;
|
||||
break;
|
||||
case "warning":
|
||||
log = logger.warn;
|
||||
break;
|
||||
case "error":
|
||||
log = logger.error;
|
||||
break;
|
||||
default:
|
||||
log = logger.info;
|
||||
break;
|
||||
}
|
||||
|
||||
if (name === "api") {
|
||||
log = logger.debug;
|
||||
}
|
||||
|
||||
log.call(logger, message.toString(), args);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -6,9 +6,8 @@ import "@goauthentik/elements/EmptyState";
|
||||
import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { TemplateResult, css, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import PFAbout from "@patternfly/patternfly/components/AboutModalBox/about-modal-box.css";
|
||||
@ -17,15 +16,16 @@ import { AdminApi, CapabilitiesEnum, LicenseSummaryStatusEnum } from "@goauthent
|
||||
|
||||
@customElement("ak-about-modal")
|
||||
export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton)) {
|
||||
static styles: CSSResult[] = [
|
||||
...ModalButton.styles,
|
||||
PFAbout,
|
||||
css`
|
||||
.pf-c-about-modal-box__hero {
|
||||
background-image: url("/static/dist/assets/images/flow_background.jpg");
|
||||
}
|
||||
`,
|
||||
];
|
||||
static get styles() {
|
||||
return ModalButton.styles.concat(
|
||||
PFAbout,
|
||||
css`
|
||||
.pf-c-about-modal-box__hero {
|
||||
background-image: url("/static/dist/assets/images/flow_background.jpg");
|
||||
}
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
async getAboutEntries(): Promise<[string, string | TemplateResult][]> {
|
||||
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
||||
@ -55,32 +55,21 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
||||
];
|
||||
}
|
||||
|
||||
#contentRef = createRef<HTMLDivElement>();
|
||||
|
||||
#backdropListener = (event: PointerEvent) => {
|
||||
// We only want to close the modal when the backdrop is clicked, not when it's children are clicked.
|
||||
|
||||
if (this.#contentRef.value?.contains(event.target as Node)) {
|
||||
return;
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
|
||||
protected override renderModal() {
|
||||
renderModal() {
|
||||
let product = this.brandingTitle;
|
||||
|
||||
if (this.licenseSummary.status !== LicenseSummaryStatusEnum.Unlicensed) {
|
||||
product += ` ${msg("Enterprise")}`;
|
||||
}
|
||||
return html`<div class="pf-c-backdrop" @click=${this.#backdropListener}>
|
||||
return html`<div
|
||||
class="pf-c-backdrop"
|
||||
@click=${(e: PointerEvent) => {
|
||||
e.stopPropagation();
|
||||
this.closeModal();
|
||||
}}
|
||||
>
|
||||
<div class="pf-l-bullseye">
|
||||
<div
|
||||
${ref(this.#contentRef)}
|
||||
class="pf-c-about-modal-box"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-title"
|
||||
>
|
||||
<div class="pf-c-about-modal-box" role="dialog" aria-modal="true">
|
||||
<div class="pf-c-about-modal-box__brand">
|
||||
<img
|
||||
class="pf-c-about-modal-box__brand-image"
|
||||
@ -89,12 +78,18 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__close">
|
||||
<button class="pf-c-button pf-m-plain" type="button" @click=${this.close}>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.open = false;
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__header">
|
||||
<h1 class="pf-c-title pf-m-4xl" id="modal-title">${product}</h1>
|
||||
<h1 class="pf-c-title pf-m-4xl">${product}</h1>
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__hero"></div>
|
||||
<div class="pf-c-about-modal-box__content">
|
||||
|
172
web/src/admin/AdminInterface/AdminCommands.ts
Normal file
172
web/src/admin/AdminInterface/AdminCommands.ts
Normal file
@ -0,0 +1,172 @@
|
||||
import { navigate } from "@goauthentik/elements/router/RouterOutlet";
|
||||
import { INinjaAction } from "ninja-keys/dist/interfaces/ininja-action.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
|
||||
export const adminCommands: INinjaAction[] = [
|
||||
{
|
||||
id: msg("Overview"),
|
||||
title: msg("Dashboard"),
|
||||
handler: () => navigate("/administration/overview"),
|
||||
section: msg("Dashboards"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/administration/dashboard/users"),
|
||||
id: msg("User Statistics"),
|
||||
title: msg("User Statistics"),
|
||||
icon: '<i class="pf-icon pf-icon-user"></i>',
|
||||
section: msg("Dashboards"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/administration/system-tasks"),
|
||||
id: msg("System Tasks"),
|
||||
title: msg("System Tasks"),
|
||||
section: msg("Dashboards"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/core/applications"),
|
||||
id: msg("Applications"),
|
||||
title: msg("Applications"),
|
||||
section: msg("Applications"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/core/providers"),
|
||||
id: msg("Providers"),
|
||||
title: msg("Providers"),
|
||||
section: msg("Applications"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/outpost/outposts"),
|
||||
id: msg("Outposts"),
|
||||
title: msg("Outposts"),
|
||||
section: msg("Applications"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/events/log"),
|
||||
id: msg("Logs"),
|
||||
title: msg("Logs"),
|
||||
section: msg("Events"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/events/rules"),
|
||||
id: msg("Notification Rules"),
|
||||
title: msg("Notification Rules"),
|
||||
section: msg("Events"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/events/transports"),
|
||||
id: msg("Notification Transports"),
|
||||
title: msg("Notification Transports"),
|
||||
section: msg("Events"),
|
||||
},
|
||||
|
||||
{
|
||||
handler: () => navigate("/policy/policies"),
|
||||
id: msg("Policies"),
|
||||
title: msg("Policies"),
|
||||
section: msg("Customization"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/core/property-mappings"),
|
||||
id: msg("Property Mappings"),
|
||||
title: msg("Property Mappings"),
|
||||
section: msg("Customization"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/blueprints/instances"),
|
||||
id: msg("Blueprints"),
|
||||
title: msg("Blueprints"),
|
||||
section: msg("Customization"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/policy/reputation"),
|
||||
id: msg("Reputation scores"),
|
||||
title: msg("Reputation scores"),
|
||||
section: msg("Customization"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/flow/flows"),
|
||||
id: msg("Flows"),
|
||||
title: msg("Flows"),
|
||||
section: msg("Flows"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/flow/stages"),
|
||||
id: msg("Stages"),
|
||||
title: msg("Stages"),
|
||||
section: msg("Flows"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/flow/stages/prompts"),
|
||||
id: msg("Prompts"),
|
||||
title: msg("Prompts"),
|
||||
section: msg("Flows"),
|
||||
},
|
||||
|
||||
{
|
||||
handler: () => navigate("/identity/users"),
|
||||
id: msg("Users"),
|
||||
title: msg("Users"),
|
||||
section: msg("Directory"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/identity/groups"),
|
||||
id: msg("Groups"),
|
||||
title: msg("Groups"),
|
||||
section: msg("Directory"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/identity/roles"),
|
||||
id: msg("Roles"),
|
||||
title: msg("Roles"),
|
||||
section: msg("Directory"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/core/sources"),
|
||||
id: msg("Federation and Social login"),
|
||||
title: msg("Federation and Social login"),
|
||||
section: msg("Directory"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/core/tokens"),
|
||||
id: msg("Tokens and App passwords"),
|
||||
title: msg("Tokens and App passwords"),
|
||||
section: msg("Directory"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/flow/stages/invitations"),
|
||||
id: msg("Invitations"),
|
||||
title: msg("Invitations"),
|
||||
section: msg("Directory"),
|
||||
},
|
||||
|
||||
{
|
||||
handler: () => navigate("/core/brands"),
|
||||
id: msg("Brands"),
|
||||
title: msg("Brands"),
|
||||
section: msg("System"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/crypto/certificates"),
|
||||
id: msg("Certificates"),
|
||||
title: msg("Certificates"),
|
||||
section: msg("System"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/outpost/integrations"),
|
||||
id: msg("Outpost Integrations"),
|
||||
title: msg("Outpost Integrations"),
|
||||
section: msg("System"),
|
||||
},
|
||||
{
|
||||
handler: () => navigate("/admin/settings"),
|
||||
id: msg("Settings"),
|
||||
title: msg("Settings"),
|
||||
section: msg("System"),
|
||||
},
|
||||
{
|
||||
handler: () => window.location.assign("/if/user/"),
|
||||
id: msg("User interface"),
|
||||
title: msg("Go to my User page"),
|
||||
},
|
||||
];
|
@ -1,44 +1,16 @@
|
||||
import { SidebarItemProperties } from "#elements/sidebar/SidebarItem";
|
||||
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html, nothing } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
|
||||
/**
|
||||
* Given a record-like object, prefixes each key with a dot, allowing it to be spread into a
|
||||
* template literal.
|
||||
*
|
||||
* ```ts
|
||||
* interface MyElementProperties {
|
||||
* foo: string;
|
||||
* bar: number;
|
||||
* }
|
||||
*
|
||||
* const properties {} as LitPropertyRecord<MyElementProperties>
|
||||
*
|
||||
* console.log(properties) // { '.foo': string; '.bar': number }
|
||||
* ```
|
||||
*/
|
||||
export type LitPropertyRecord<T extends object> = {
|
||||
[K in keyof T as K extends string ? LitPropertyKey<K> : never]: T[K];
|
||||
};
|
||||
|
||||
/**
|
||||
* A type that represents a property key that can be used in a LitPropertyRecord.
|
||||
*
|
||||
* @see {@linkcode LitPropertyRecord}
|
||||
*/
|
||||
export type LitPropertyKey<K> = K extends string ? `.${K}` | `?${K}` | K : K;
|
||||
|
||||
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
|
||||
// commonplace and singular enough to merit its own handler.
|
||||
type SidebarEntry = [
|
||||
path: string | null,
|
||||
label: string,
|
||||
attributes?: LitPropertyRecord<SidebarItemProperties> | string[] | null,
|
||||
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
|
||||
children?: SidebarEntry[],
|
||||
];
|
||||
|
||||
@ -59,7 +31,8 @@ export function renderSidebarItem([
|
||||
properties.path = path;
|
||||
}
|
||||
|
||||
return html`<ak-sidebar-item label=${ifDefined(label)} ${spread(properties)}>
|
||||
return html`<ak-sidebar-item ${spread(properties)}>
|
||||
${label ? html`<span slot="label">${label}</span>` : nothing}
|
||||
${children ? renderSidebarItems(children) : nothing}
|
||||
</ak-sidebar-item>`;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import "#admin/AdminInterface/AboutModal";
|
||||
import type { AboutModal } from "#admin/AdminInterface/AboutModal";
|
||||
import { adminCommands } from "#admin/AdminInterface/AdminCommands";
|
||||
import { ROUTES } from "#admin/Routes";
|
||||
import { EVENT_API_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE } from "#common/constants";
|
||||
import { configureSentry } from "#common/sentry/index";
|
||||
@ -7,7 +8,6 @@ import { me } from "#common/users";
|
||||
import { WebsocketClient } from "#common/ws";
|
||||
import { SidebarToggleEventDetail } from "#components/ak-page-header";
|
||||
import { AuthenticatedInterface } from "#elements/AuthenticatedInterface";
|
||||
import "#elements/a11y/ak-skip-to-content";
|
||||
import "#elements/ak-locale-context/ak-locale-context";
|
||||
import "#elements/banner/EnterpriseStatusBanner";
|
||||
import "#elements/banner/EnterpriseStatusBanner";
|
||||
@ -22,8 +22,8 @@ import { getURLParam, updateURLParams } from "#elements/router/RouteMatch";
|
||||
import "#elements/router/RouterOutlet";
|
||||
import "#elements/sidebar/Sidebar";
|
||||
import "#elements/sidebar/SidebarItem";
|
||||
import "ninja-keys";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement, eventOptions, property, query } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
@ -121,6 +121,10 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
||||
.pf-c-drawer__panel {
|
||||
z-index: var(--pf-global--ZIndex--xl);
|
||||
}
|
||||
ninja-keys {
|
||||
--ninja-z-index: 99999;
|
||||
--ninja-accent-color: var(--ak-accent);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@ -164,18 +168,16 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
||||
}
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
me().then((session) => {
|
||||
this.user = session;
|
||||
this.user = await me();
|
||||
|
||||
const canAccessAdmin =
|
||||
this.user.user.isSuperuser ||
|
||||
// TODO: somehow add `access_admin_interface` to the API schema
|
||||
this.user.user.systemPermissions.includes("access_admin_interface");
|
||||
const canAccessAdmin =
|
||||
this.user.user.isSuperuser ||
|
||||
// TODO: somehow add `access_admin_interface` to the API schema
|
||||
this.user.user.systemPermissions.includes("access_admin_interface");
|
||||
|
||||
if (!canAccessAdmin && this.user.user.pk > 0) {
|
||||
window.location.assign("/if/user/");
|
||||
}
|
||||
});
|
||||
if (!canAccessAdmin && this.user.user.pk > 0) {
|
||||
window.location.assign("/if/user/");
|
||||
}
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
@ -194,14 +196,18 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
||||
};
|
||||
|
||||
return html` <ak-locale-context>
|
||||
<ak-skip-to-content></ak-skip-to-content>
|
||||
<ninja-keys
|
||||
.data=${adminCommands}
|
||||
noAutoLoadMdicons
|
||||
class="${this.activeTheme === UiThemeEnum.Dark ? "dark" : ""}"
|
||||
></ninja-keys>
|
||||
<div class="pf-c-page">
|
||||
<ak-page-navbar ?open=${this.sidebarOpen} @sidebar-toggle=${this.sidebarListener}>
|
||||
<ak-version-banner></ak-version-banner>
|
||||
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||
</ak-page-navbar>
|
||||
|
||||
<ak-sidebar ?hidden=${!this.sidebarOpen} class="${classMap(sidebarClasses)}">
|
||||
<ak-sidebar class="${classMap(sidebarClasses)}">
|
||||
${renderSidebarItems(AdminSidebarEntries)}
|
||||
${this.can(CapabilitiesEnum.IsEnterprise)
|
||||
? renderSidebarItems(AdminSidebarEnterpriseEntries)
|
||||
@ -213,10 +219,9 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
||||
<div class="pf-c-drawer__main">
|
||||
<div class="pf-c-drawer__content">
|
||||
<div class="pf-c-drawer__body">
|
||||
<div class="pf-c-page__main">
|
||||
<main class="pf-c-page__main">
|
||||
<ak-router-outlet
|
||||
role="main"
|
||||
aria-label="${msg("Main content")}"
|
||||
class="pf-c-page__main"
|
||||
tabindex="-1"
|
||||
id="main-content"
|
||||
@ -224,7 +229,7 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
||||
.routes=${ROUTES}
|
||||
>
|
||||
</ak-router-outlet>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<ak-notification-drawer
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { PFSize } from "@goauthentik/common/enums.js";
|
||||
import {
|
||||
@ -14,7 +13,7 @@ import { state } from "lit/decorators.js";
|
||||
|
||||
export interface AdminStatus {
|
||||
icon: string;
|
||||
message?: SlottedTemplateResult;
|
||||
message?: TemplateResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,8 +98,8 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
*
|
||||
* @returns TemplateResult displaying the value
|
||||
*/
|
||||
protected renderValue(): SlottedTemplateResult {
|
||||
return this.value ? html`${this.value}` : nothing;
|
||||
protected renderValue(): TemplateResult {
|
||||
return html`${this.value}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,7 +108,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
* @param status - AdminStatus object containing icon and message
|
||||
* @returns TemplateResult for status display
|
||||
*/
|
||||
private renderStatus(status: AdminStatus): SlottedTemplateResult {
|
||||
private renderStatus(status: AdminStatus): TemplateResult {
|
||||
return html`
|
||||
<p><i class="${status.icon}"></i> ${this.renderValue()}</p>
|
||||
${status.message ? html`<p class="subtext">${status.message}</p>` : nothing}
|
||||
@ -122,9 +121,9 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
* @param error - Error message to display
|
||||
* @returns TemplateResult for error display
|
||||
*/
|
||||
private renderError(error: string): SlottedTemplateResult {
|
||||
private renderError(error: string): TemplateResult {
|
||||
return html`
|
||||
<p><i aria-hidden="true" class="fa fa-times"></i> ${msg("Failed to fetch")}</p>
|
||||
<p><i class="fa fa-times"></i> ${msg("Failed to fetch")}</p>
|
||||
<p class="subtext">${error}</p>
|
||||
`;
|
||||
}
|
||||
@ -134,7 +133,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
*
|
||||
* @returns TemplateResult for loading spinner
|
||||
*/
|
||||
private renderLoading(): SlottedTemplateResult {
|
||||
private renderLoading(): TemplateResult {
|
||||
return html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`;
|
||||
}
|
||||
|
||||
@ -143,7 +142,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
*
|
||||
* @returns TemplateResult for current component state
|
||||
*/
|
||||
renderInner(): SlottedTemplateResult {
|
||||
renderInner(): TemplateResult {
|
||||
return html`
|
||||
<p class="center-value">
|
||||
${
|
||||
|
@ -88,8 +88,7 @@ export class RecentEventsCard extends Table<Event> {
|
||||
}
|
||||
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state
|
||||
><span slot="header">${msg("No Events found.")}</span>
|
||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import {
|
||||
AdminStatus,
|
||||
AdminStatusCard,
|
||||
@ -6,7 +5,7 @@ import {
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html, nothing } from "lit";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import { AdminApi, OutpostsApi, SystemInfo } from "@goauthentik/api";
|
||||
@ -85,12 +84,12 @@ export class SystemStatusCard extends AdminStatusCard<SystemInfo> {
|
||||
});
|
||||
}
|
||||
|
||||
renderHeader(): SlottedTemplateResult {
|
||||
return msg("System status");
|
||||
renderHeader(): TemplateResult {
|
||||
return html`${msg("System status")}`;
|
||||
}
|
||||
|
||||
renderValue(): SlottedTemplateResult {
|
||||
return this.statusSummary ? html`${this.statusSummary}` : nothing;
|
||||
renderValue(): TemplateResult {
|
||||
return html`${this.statusSummary}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
import {
|
||||
AkControlElement,
|
||||
formatFormElementAsJSON,
|
||||
} from "@goauthentik/elements/AkControlElement.js";
|
||||
import { AkControlElement } from "@goauthentik/elements/AkControlElement.js";
|
||||
import { type Spread } from "@goauthentik/elements/types";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
@ -26,32 +23,33 @@ const hasLegalScheme = (url: string) =>
|
||||
|
||||
@customElement("ak-admin-settings-footer-link")
|
||||
export class FooterLinkInput extends AkControlElement<FooterLink> {
|
||||
static styles = [
|
||||
PFBase,
|
||||
PFInputGroup,
|
||||
PFFormControl,
|
||||
css`
|
||||
.pf-c-input-group input#linkname {
|
||||
flex-grow: 1;
|
||||
width: 8rem;
|
||||
}
|
||||
`,
|
||||
];
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
PFInputGroup,
|
||||
PFFormControl,
|
||||
css`
|
||||
.pf-c-input-group input#linkname {
|
||||
flex-grow: 1;
|
||||
width: 8rem;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
public footerLink: FooterLink = {
|
||||
footerLink: FooterLink = {
|
||||
name: "",
|
||||
href: "",
|
||||
};
|
||||
|
||||
@property({ type: String })
|
||||
public name?: string | null;
|
||||
|
||||
@queryAll(".ak-form-control")
|
||||
protected controls?: HTMLInputElement[];
|
||||
controls?: HTMLInputElement[];
|
||||
|
||||
public override json() {
|
||||
return formatFormElementAsJSON<FooterLink>(this.controls);
|
||||
json() {
|
||||
return Object.fromEntries(
|
||||
Array.from(this.controls ?? []).map((control) => [control.name, control.value]),
|
||||
) as unknown as FooterLink;
|
||||
}
|
||||
|
||||
get isValid() {
|
||||
|
@ -1,49 +1,42 @@
|
||||
import { render } from "@goauthentik/elements/tests/utils.js";
|
||||
import { $, browser, expect } from "@wdio/globals";
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
import "../AdminSettingsFooterLinks.js";
|
||||
|
||||
describe("ak-admin-settings-footer-link", () => {
|
||||
afterEach(() =>
|
||||
browser.execute(() => {
|
||||
document.body.querySelector("ak-admin-settings-footer-link")?.remove();
|
||||
|
||||
if ("_$litPart$" in document.body) {
|
||||
delete document.body._$litPart$;
|
||||
afterEach(async () => {
|
||||
await browser.execute(async () => {
|
||||
await document.body.querySelector("ak-admin-settings-footer-link")?.remove();
|
||||
if (document.body._$litPart$) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
await delete document.body._$litPart$;
|
||||
}
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should render an empty control", async () => {
|
||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||
const link = $("ak-admin-settings-footer-link");
|
||||
|
||||
await expect(link.getProperty("isValid")).resolves.toStrictEqual(false);
|
||||
await expect(link.getProperty("toJson")).resolves.toEqual({
|
||||
name: "",
|
||||
href: "",
|
||||
});
|
||||
const link = await $("ak-admin-settings-footer-link");
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(false);
|
||||
await expect(await link.getProperty("toJson")).toEqual({ name: "", href: "" });
|
||||
});
|
||||
|
||||
it("should not be valid if just a name is filled in", async () => {
|
||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||
const link = $("ak-admin-settings-footer-link");
|
||||
const link = await $("ak-admin-settings-footer-link");
|
||||
await link.$('input[name="name"]').setValue("foo");
|
||||
await expect(link.getProperty("isValid")).resolves.toStrictEqual(false);
|
||||
await expect(link.getProperty("toJson")).resolves.toEqual({
|
||||
name: "foo",
|
||||
href: "",
|
||||
});
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(false);
|
||||
await expect(await link.getProperty("toJson")).toEqual({ name: "foo", href: "" });
|
||||
});
|
||||
|
||||
it("should be valid if just a URL is filled in", async () => {
|
||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||
const link = $("ak-admin-settings-footer-link");
|
||||
const link = await $("ak-admin-settings-footer-link");
|
||||
await link.$('input[name="href"]').setValue("https://foo.com");
|
||||
await expect(link.getProperty("isValid")).resolves.toStrictEqual(true);
|
||||
await expect(link.getProperty("toJson")).resolves.toEqual({
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(true);
|
||||
await expect(await link.getProperty("toJson")).toEqual({
|
||||
name: "",
|
||||
href: "https://foo.com",
|
||||
});
|
||||
@ -51,13 +44,11 @@ describe("ak-admin-settings-footer-link", () => {
|
||||
|
||||
it("should be valid if both are filled in", async () => {
|
||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||
const link = $("ak-admin-settings-footer-link");
|
||||
|
||||
const link = await $("ak-admin-settings-footer-link");
|
||||
await link.$('input[name="name"]').setValue("foo");
|
||||
await link.$('input[name="href"]').setValue("https://foo.com");
|
||||
|
||||
await expect(link.getProperty("isValid")).resolves.toStrictEqual(true);
|
||||
await expect(link.getProperty("toJson")).resolves.toEqual({
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(true);
|
||||
await expect(await link.getProperty("toJson")).toEqual({
|
||||
name: "foo",
|
||||
href: "https://foo.com",
|
||||
});
|
||||
@ -65,13 +56,13 @@ describe("ak-admin-settings-footer-link", () => {
|
||||
|
||||
it("should not be valid if the URL is not valid", async () => {
|
||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||
const link = $("ak-admin-settings-footer-link");
|
||||
const link = await $("ak-admin-settings-footer-link");
|
||||
await link.$('input[name="name"]').setValue("foo");
|
||||
await link.$('input[name="href"]').setValue("never://foo.com");
|
||||
await expect(link.getProperty("toJson")).resolves.toEqual({
|
||||
await expect(await link.getProperty("toJson")).toEqual({
|
||||
name: "foo",
|
||||
href: "never://foo.com",
|
||||
});
|
||||
await expect(link.getProperty("isValid")).resolves.toStrictEqual(false);
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
@ -5,7 +5,6 @@ import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-file-input";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-slug-input";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/components/ak-textarea-input";
|
||||
@ -131,14 +130,14 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
required
|
||||
help=${msg("Application's display Name.")}
|
||||
></ak-text-input>
|
||||
<ak-slug-input
|
||||
<ak-text-input
|
||||
name="slug"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
help=${msg("Internal application name used in URLs.")}
|
||||
input-hint="code"
|
||||
></ak-slug-input>
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
name="group"
|
||||
value=${ifDefined(this.instance?.group)}
|
||||
@ -177,8 +176,9 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
.options=${policyEngineModes}
|
||||
.value=${this.instance?.policyEngineMode}
|
||||
></ak-radio-input>
|
||||
<ak-form-group label="${msg("UI settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("UI settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="metaLaunchUrl"
|
||||
label=${msg("Launch URL")}
|
||||
|
@ -85,7 +85,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
];
|
||||
}
|
||||
|
||||
protected renderSidebarAfter(): TemplateResult {
|
||||
renderSidebarAfter(): TemplateResult {
|
||||
return html`<div class="pf-c-sidebar__panel pf-m-width-25">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
|
@ -5,8 +5,7 @@ import {
|
||||
WizardNavigationEvent,
|
||||
WizardUpdateEvent,
|
||||
} from "@goauthentik/components/ak-wizard/events";
|
||||
import type { AkControlElement } from "@goauthentik/elements/forms/Form";
|
||||
import { serializeForm } from "@goauthentik/elements/forms/Form";
|
||||
import { KeyUnknown, serializeForm } from "@goauthentik/elements/forms/Form";
|
||||
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
@ -30,21 +29,22 @@ export class ApplicationWizardStep extends WizardStep {
|
||||
|
||||
// As recommended in [WizardStep](../../../components/ak-wizard/WizardStep.ts), we override
|
||||
// these fields and provide them to all the child classes.
|
||||
protected wizardTitle = msg("New application");
|
||||
protected wizardDescription = msg("Create a new application and configure a provider for it.");
|
||||
public cancelable = true;
|
||||
wizardTitle = msg("New application");
|
||||
wizardDescription = msg("Create a new application and configure a provider for it.");
|
||||
canCancel = true;
|
||||
|
||||
// This should be overridden in the children for more precise targeting.
|
||||
@query("form")
|
||||
protected form!: HTMLFormElement;
|
||||
form!: HTMLFormElement;
|
||||
|
||||
get formValues(): Record<string, unknown> {
|
||||
get formValues(): KeyUnknown | undefined {
|
||||
const elements = [
|
||||
...this.form.querySelectorAll<HorizontalFormElement>("ak-form-element-horizontal"),
|
||||
...this.form.querySelectorAll<AkControlElement>("[data-ak-control]"),
|
||||
...Array.from(
|
||||
this.form.querySelectorAll<HorizontalFormElement>("ak-form-element-horizontal"),
|
||||
),
|
||||
...Array.from(this.form.querySelectorAll<HTMLElement>("[data-ak-control=true]")),
|
||||
];
|
||||
|
||||
return serializeForm(elements);
|
||||
return serializeForm(elements as unknown as NodeListOf<HorizontalFormElement>);
|
||||
}
|
||||
|
||||
protected removeErrors(
|
||||
|
@ -22,7 +22,7 @@ export class AkWizardTitle extends AKElement {
|
||||
|
||||
render() {
|
||||
return html`<div class="ak-bottom-spacing pf-c-content">
|
||||
<h3 data-test-id="wizard-heading"><slot></slot></h3>
|
||||
<h3><slot></slot></h3>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@ -33,12 +33,4 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-wizard-title": AkWizardTitle;
|
||||
}
|
||||
|
||||
interface WizardTestIDMap {
|
||||
heading: HTMLHeadingElement;
|
||||
}
|
||||
|
||||
interface TestIDSelectorMap {
|
||||
wizard: WizardTestIDMap;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { ApplicationWizardStep } from "@goauthentik/admin/applications/wizard/ApplicationWizardStep.js";
|
||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title.js";
|
||||
import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes";
|
||||
import { camelToSnake } from "@goauthentik/common/utils.js";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-slug-input";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import { type NavigableButton, type WizardButton } from "@goauthentik/components/ak-wizard/types";
|
||||
import { type KeyUnknown } from "@goauthentik/elements/forms/Form";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { isSlug } from "@goauthentik/elements/router/utils.js";
|
||||
import { snakeCase } from "change-case";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
@ -22,10 +23,11 @@ import { ApplicationWizardStateUpdate, ValidationRecord } from "../types";
|
||||
|
||||
const autoTrim = (v: unknown) => (typeof v === "string" ? v.trim() : v);
|
||||
|
||||
const trimMany = (o: Record<string, unknown>, vs: string[]) =>
|
||||
const trimMany = (o: KeyUnknown, vs: string[]) =>
|
||||
Object.fromEntries(vs.map((v) => [v, autoTrim(o[v])]));
|
||||
|
||||
const isStr = (v: unknown): v is string => typeof v === "string";
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const isStr = (v: any): v is string => typeof v === "string";
|
||||
|
||||
@customElement("ak-application-wizard-application-step")
|
||||
export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
@ -46,7 +48,9 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
errorMessages(name: string) {
|
||||
return this.errors.has(name)
|
||||
? [this.errors.get(name)]
|
||||
: (this.wizard.errors?.app?.[name] ?? this.wizard.errors?.app?.[snakeCase(name)] ?? []);
|
||||
: (this.wizard.errors?.app?.[name] ??
|
||||
this.wizard.errors?.app?.[camelToSnake(name)] ??
|
||||
[]);
|
||||
}
|
||||
|
||||
get buttons(): WizardButton[] {
|
||||
@ -113,11 +117,13 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
?invalid=${this.errors.has("name")}
|
||||
.errorMessages=${errors.name ?? this.errorMessages("name")}
|
||||
help=${msg("Application's display Name.")}
|
||||
id="ak-application-wizard-details-name"
|
||||
></ak-text-input>
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(app.slug)}
|
||||
label=${msg("Slug")}
|
||||
source="#ak-application-wizard-details-name"
|
||||
required
|
||||
?invalid=${errors.slug ?? this.errors.has("slug")}
|
||||
.errorMessages=${this.errorMessages("slug")}
|
||||
@ -142,8 +148,9 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
.value=${app.policyEngineMode}
|
||||
.errorMessages=${errors.policyEngineMode ?? []}
|
||||
></ak-radio-input>
|
||||
<ak-form-group label=${msg("UI Settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group aria-label=${msg("UI Settings")}>
|
||||
<span slot="header"> ${msg("UI Settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="metaLaunchUrl"
|
||||
label=${msg("Launch URL")}
|
||||
|
@ -115,8 +115,7 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
||||
.columns=${COLUMNS}
|
||||
.content=${[]}
|
||||
></ak-select-table>
|
||||
<ak-empty-state icon="pf-icon-module"
|
||||
><span slot="header">${msg("No bound policies.")} </span>
|
||||
<ak-empty-state header=${msg("No bound policies.")} icon="pf-icon-module">
|
||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||
<div slot="primary">
|
||||
<button
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { camelToSnake } from "@goauthentik/common/utils.js";
|
||||
import "@goauthentik/components/ak-number-input";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import { AKElement } from "@goauthentik/elements/Base.js";
|
||||
import type { AkControlElement } from "@goauthentik/elements/forms/Form";
|
||||
import { serializeForm } from "@goauthentik/elements/forms/Form";
|
||||
import { KeyUnknown, serializeForm } from "@goauthentik/elements/forms/Form";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { snakeCase } from "change-case";
|
||||
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
@ -31,13 +30,14 @@ export class ApplicationWizardProviderForm<T extends OneOfProvider> extends AKEl
|
||||
@query("form#providerform")
|
||||
form!: HTMLFormElement;
|
||||
|
||||
get formValues(): Record<string, unknown> {
|
||||
get formValues(): KeyUnknown | undefined {
|
||||
const elements = [
|
||||
...this.form.querySelectorAll<HorizontalFormElement>("ak-form-element-horizontal"),
|
||||
...this.form.querySelectorAll<AkControlElement>("[data-ak-control]"),
|
||||
...Array.from(
|
||||
this.form.querySelectorAll<HorizontalFormElement>("ak-form-element-horizontal"),
|
||||
),
|
||||
...Array.from(this.form.querySelectorAll<HTMLElement>("[data-ak-control=true]")),
|
||||
];
|
||||
|
||||
return serializeForm(elements);
|
||||
return serializeForm(elements as unknown as NodeListOf<HorizontalFormElement>);
|
||||
}
|
||||
|
||||
get valid() {
|
||||
@ -49,7 +49,7 @@ export class ApplicationWizardProviderForm<T extends OneOfProvider> extends AKEl
|
||||
return name in this.errors
|
||||
? [this.errors[name]]
|
||||
: (this.wizard.errors?.provider?.[name] ??
|
||||
this.wizard.errors?.provider?.[snakeCase(name)] ??
|
||||
this.wizard.errors?.provider?.[camelToSnake(name)] ??
|
||||
[]);
|
||||
}
|
||||
|
||||
|
@ -60,8 +60,9 @@ export class ApplicationWizardRACProviderForm extends ApplicationWizardProviderF
|
||||
input-hint="code"
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-group open label=" ${msg("Protocol settings")} ">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Property mappings")}
|
||||
name="propertyMappings"
|
||||
|
@ -176,8 +176,9 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ak-form-group label="${msg("Additional settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Additional settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Context")} name="context">
|
||||
<ak-codemirror
|
||||
mode=${CodeMirrorMode.YAML}
|
||||
|
@ -87,8 +87,9 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group label="${msg("Branding settings")} ">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Branding settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Title")} required name="brandingTitle">
|
||||
<input
|
||||
type="text"
|
||||
@ -169,8 +170,9 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group label="${msg("External user settings")} ">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("External user settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Default application")}
|
||||
name="defaultApplication"
|
||||
@ -213,8 +215,9 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group label="${msg("Default flows")} ">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Default flows")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
name="flowAuthentication"
|
||||
@ -292,8 +295,9 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group label="${msg("Other global settings")} ">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Other global settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Web Certificate")}
|
||||
name="webCertificate"
|
||||
|
@ -44,18 +44,19 @@ export class CoreGroupSearch extends CustomListenerElement(AKElement) {
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: String, reflect: true })
|
||||
public group?: string;
|
||||
group?: string;
|
||||
|
||||
@query("ak-search-select")
|
||||
public search!: SearchSelect<Group>;
|
||||
search!: SearchSelect<Group>;
|
||||
|
||||
@property({ type: String })
|
||||
public name?: string | null;
|
||||
name: string | null | undefined;
|
||||
|
||||
selectedGroup?: Group;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.selected = this.selected.bind(this);
|
||||
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
|
||||
}
|
||||
|
||||
@ -82,9 +83,9 @@ export class CoreGroupSearch extends CustomListenerElement(AKElement) {
|
||||
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
selected = (group: Group) => {
|
||||
selected(group: Group) {
|
||||
return this.group === group.pk;
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
|
@ -32,19 +32,13 @@ const renderValue = (item: CertificateKeyPair | undefined): string | undefined =
|
||||
@customElement("ak-crypto-certificate-search")
|
||||
export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement) {
|
||||
@property({ type: String, reflect: true })
|
||||
public certificate?: string;
|
||||
certificate?: string;
|
||||
|
||||
@query("ak-search-select")
|
||||
public search!: SearchSelect<CertificateKeyPair>;
|
||||
search!: SearchSelect<CertificateKeyPair>;
|
||||
|
||||
@property({ type: String })
|
||||
public name?: string | null;
|
||||
|
||||
@property({ type: String })
|
||||
public label?: string | undefined;
|
||||
|
||||
@property({ type: String })
|
||||
public placeholder?: string | undefined;
|
||||
name: string | null | undefined;
|
||||
|
||||
/**
|
||||
* Set to `true` to allow certificates without private key to show up. When set to `false`,
|
||||
@ -52,7 +46,7 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean, attribute: "nokey" })
|
||||
public noKey = false;
|
||||
noKey = false;
|
||||
|
||||
/**
|
||||
* Set this to true if, should there be only one certificate available, you want the system to
|
||||
@ -61,12 +55,16 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean, attribute: "singleton" })
|
||||
public singleton = false;
|
||||
singleton = false;
|
||||
|
||||
/**
|
||||
* @todo Document this.
|
||||
*/
|
||||
public selectedKeypair?: CertificateKeyPair;
|
||||
selectedKeypair?: CertificateKeyPair;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.selected = this.selected.bind(this);
|
||||
this.fetchObjects = this.fetchObjects.bind(this);
|
||||
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.selectedKeypair ? renderValue(this.selectedKeypair) : null;
|
||||
@ -85,13 +83,13 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchUpdate = (ev: CustomEvent) => {
|
||||
handleSearchUpdate(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
this.selectedKeypair = ev.detail.value;
|
||||
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
|
||||
};
|
||||
}
|
||||
|
||||
fetchObjects = async (query?: string): Promise<CertificateKeyPair[]> => {
|
||||
async fetchObjects(query?: string): Promise<CertificateKeyPair[]> {
|
||||
const args: CryptoCertificatekeypairsListRequest = {
|
||||
ordering: "name",
|
||||
hasKey: !this.noKey,
|
||||
@ -104,21 +102,19 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
|
||||
args,
|
||||
);
|
||||
return certificates.results;
|
||||
};
|
||||
}
|
||||
|
||||
selected = (item: CertificateKeyPair, items: CertificateKeyPair[]) => {
|
||||
selected(item: CertificateKeyPair, items: CertificateKeyPair[]) {
|
||||
return (
|
||||
(this.singleton && !this.certificate && items.length === 1) ||
|
||||
(!!this.certificate && this.certificate === item.pk)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ak-search-select
|
||||
name=${ifDefined(this.name ?? undefined)}
|
||||
label=${ifDefined(this.label ?? undefined)}
|
||||
placeholder=${ifDefined(this.placeholder ?? undefined)}
|
||||
.fetchObjects=${this.fetchObjects}
|
||||
.renderElement=${renderElement}
|
||||
.value=${renderValue}
|
||||
|
@ -5,7 +5,6 @@ import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
|
||||
import "@goauthentik/elements/forms/SearchSelect";
|
||||
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
@ -35,15 +34,13 @@ export function getFlowValue(flow: Flow | undefined): string | undefined {
|
||||
*/
|
||||
|
||||
export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement) {
|
||||
//#region Properties
|
||||
|
||||
/**
|
||||
* The type of flow we're looking for.
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: String })
|
||||
public flowType?: FlowsInstancesListDesignationEnum;
|
||||
flowType?: FlowsInstancesListDesignationEnum;
|
||||
|
||||
/**
|
||||
* The id of the current flow, if any. For stages where the flow is already defined.
|
||||
@ -51,7 +48,7 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: String })
|
||||
public currentFlow?: string | undefined;
|
||||
currentFlow?: string | undefined;
|
||||
|
||||
/**
|
||||
* If true, it is not valid to leave the flow blank.
|
||||
@ -59,7 +56,10 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean })
|
||||
public required?: boolean = false;
|
||||
required?: boolean = false;
|
||||
|
||||
@query("ak-search-select")
|
||||
search!: SearchSelect<T>;
|
||||
|
||||
/**
|
||||
* When specified and the object instance does not have a flow selected, auto-select the flow with the given slug.
|
||||
@ -70,29 +70,9 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
||||
defaultFlowSlug?: string;
|
||||
|
||||
@property({ type: String })
|
||||
public name?: string | null;
|
||||
name: string | null | undefined;
|
||||
|
||||
/**
|
||||
* The label of the input, for forms.
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: String })
|
||||
public label?: string;
|
||||
|
||||
/**
|
||||
* The textual placeholder for the search's <input> object, if currently empty. Used as the
|
||||
* native <input> object's `placeholder` field.
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: String })
|
||||
public placeholder: string = msg("Select a flow...");
|
||||
|
||||
@query("ak-search-select")
|
||||
protected search!: SearchSelect<T>;
|
||||
|
||||
protected selectedFlow?: T;
|
||||
selectedFlow?: T;
|
||||
|
||||
get value() {
|
||||
return this.selectedFlow ? getFlowValue(this.selectedFlow) : null;
|
||||
@ -100,16 +80,18 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.fetchObjects = this.fetchObjects.bind(this);
|
||||
this.selected = this.selected.bind(this);
|
||||
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
|
||||
}
|
||||
|
||||
handleSearchUpdate = (ev: CustomEvent) => {
|
||||
handleSearchUpdate(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
this.selectedFlow = ev.detail.value;
|
||||
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
|
||||
};
|
||||
}
|
||||
|
||||
fetchObjects = async (query?: string): Promise<Flow[]> => {
|
||||
async fetchObjects(query?: string): Promise<Flow[]> {
|
||||
const args: FlowsInstancesListRequest = {
|
||||
ordering: "slug",
|
||||
designation: this.flowType,
|
||||
@ -117,7 +99,7 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
||||
};
|
||||
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
|
||||
return flows.results;
|
||||
};
|
||||
}
|
||||
|
||||
/* This is the most commonly overridden method of this class. About half of the Flow Searches
|
||||
* use this method, but several have more complex needs, such as relating to the brand, or just
|
||||
@ -152,8 +134,6 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
||||
.renderElement=${renderElement}
|
||||
.renderDescription=${renderDescription}
|
||||
.value=${getFlowValue}
|
||||
placeholder=${ifDefined(this.placeholder ?? undefined)}
|
||||
label=${ifDefined(this.label ?? undefined)}
|
||||
name=${ifDefined(this.name ?? undefined)}
|
||||
@ak-change=${this.handleSearchUpdate}
|
||||
?blankable=${!this.required}
|
||||
|
@ -21,9 +21,14 @@ export class AkBrandedFlowSearch<T extends Flow> extends FlowSearch<T> {
|
||||
@property({ attribute: false, type: String })
|
||||
brandFlow?: string;
|
||||
|
||||
public selected = (flow: Flow): boolean => {
|
||||
constructor() {
|
||||
super();
|
||||
this.selected = this.selected.bind(this);
|
||||
}
|
||||
|
||||
selected(flow: Flow): boolean {
|
||||
return super.selected(flow) || flow.pk === this.brandFlow;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -32,14 +32,19 @@ export class AkSourceFlowSearch<T extends Flow> extends FlowSearch<T> {
|
||||
@property({ type: String })
|
||||
instanceId: string | undefined;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.selected = this.selected.bind(this);
|
||||
}
|
||||
|
||||
// If there's no instance or no currentFlowId for it and the flow resembles the fallback,
|
||||
// otherwise defer to the parent class.
|
||||
selected = (flow: Flow): boolean => {
|
||||
selected(flow: Flow): boolean {
|
||||
return (
|
||||
(!this.instanceId && !this.currentFlow && flow.slug === this.fallback) ||
|
||||
super.selected(flow)
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -8,35 +8,25 @@ import { html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-license-notice")
|
||||
export class AKLicenceNotice extends WithLicenseSummary(AKElement) {
|
||||
export class AkLicenceNotice extends WithLicenseSummary(AKElement) {
|
||||
static styles = [$PFBase];
|
||||
|
||||
@property()
|
||||
public label = msg("Enterprise only");
|
||||
|
||||
@property()
|
||||
public description = msg("Learn more about the enterprise license.");
|
||||
notice = msg("Enterprise only");
|
||||
|
||||
render() {
|
||||
if (this.hasEnterpriseLicense) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ak-alert class="pf-c-radio__description" inline plain>
|
||||
<a
|
||||
aria-label="${this.label}"
|
||||
aria-description="${this.description}"
|
||||
href="#/enterprise/licenses"
|
||||
>${this.label}</a
|
||||
>
|
||||
</ak-alert>
|
||||
`;
|
||||
return this.hasEnterpriseLicense
|
||||
? nothing
|
||||
: html`
|
||||
<ak-alert class="pf-c-radio__description" inline plain>
|
||||
<a href="#/enterprise/licenses">${this.notice}</a>
|
||||
</ak-alert>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-license-notice": AKLicenceNotice;
|
||||
"ak-license-notice": AkLicenceNotice;
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ describe("ak-enterprise-status-card", () => {
|
||||
it("should not error when no data is loaded", async () => {
|
||||
render(html`<ak-enterprise-status-card></ak-enterprise-status-card>`);
|
||||
|
||||
const status = $("ak-enterprise-status-card");
|
||||
await expect(status).resolves.toHaveText(msg("Loading"));
|
||||
const status = await $("ak-enterprise-status-card");
|
||||
await expect(status).toHaveText(msg("Loading"));
|
||||
});
|
||||
|
||||
it("should render empty when unlicensed", async () => {
|
||||
@ -35,22 +35,22 @@ describe("ak-enterprise-status-card", () => {
|
||||
</ak-enterprise-status-card>`,
|
||||
);
|
||||
|
||||
const status = $("ak-enterprise-status-card").$(
|
||||
const status = await $("ak-enterprise-status-card").$(
|
||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||
);
|
||||
await expect(status).resolves.toExist();
|
||||
await expect(status).resolves.toHaveText(msg("Unlicensed"));
|
||||
await expect(status).toExist();
|
||||
await expect(status).toHaveText(msg("Unlicensed"));
|
||||
|
||||
const internalUserProgress = $("ak-enterprise-status-card").$(
|
||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#internalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(internalUserProgress).resolves.toExist();
|
||||
await expect(internalUserProgress).resolves.toHaveAttr("aria-valuenow", "0");
|
||||
const externalUserProgress = $("ak-enterprise-status-card").$(
|
||||
await expect(internalUserProgress).toExist();
|
||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "0");
|
||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#externalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(externalUserProgress).resolves.toExist();
|
||||
await expect(externalUserProgress).resolves.toHaveAttr("aria-valuenow", "0");
|
||||
await expect(externalUserProgress).toExist();
|
||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "0");
|
||||
});
|
||||
|
||||
it("should show warnings when full", async () => {
|
||||
@ -72,35 +72,34 @@ describe("ak-enterprise-status-card", () => {
|
||||
</ak-enterprise-status-card>`,
|
||||
);
|
||||
|
||||
const status = $("ak-enterprise-status-card").$(
|
||||
const status = await $("ak-enterprise-status-card").$(
|
||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||
);
|
||||
await expect(status).resolves.toExist();
|
||||
await expect(status).resolves.toHaveText(msg("Valid"));
|
||||
await expect(status).toExist();
|
||||
await expect(status).toHaveText(msg("Valid"));
|
||||
|
||||
const internalUserProgress = $("ak-enterprise-status-card").$(
|
||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#internalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(internalUserProgress).resolves.toExist();
|
||||
await expect(internalUserProgress).resolves.toHaveAttr("aria-valuenow", "100");
|
||||
await expect(internalUserProgress).toExist();
|
||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "100");
|
||||
|
||||
await expect($("ak-enterprise-status-card").$(">>>#internalUsers")).toHaveElementClass(
|
||||
"pf-m-warning",
|
||||
);
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
|
||||
const externalUserProgress = $("ak-enterprise-status-card").$(
|
||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#externalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(externalUserProgress).resolves.toExist();
|
||||
await expect(externalUserProgress).resolves.toHaveAttr("aria-valuenow", "100");
|
||||
await expect(externalUserProgress).toExist();
|
||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "100");
|
||||
|
||||
await expect(
|
||||
$("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).resolves.toHaveElementClass("pf-m-warning");
|
||||
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
await expect(
|
||||
$("ak-enterprise-status-card").$(">>>#externalUsers"),
|
||||
).resolves.toHaveElementClass("pf-m-warning");
|
||||
await $("ak-enterprise-status-card").$(">>>#externalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
});
|
||||
|
||||
it("should show infinity when not licensed for a user type", async () => {
|
||||
@ -122,33 +121,33 @@ describe("ak-enterprise-status-card", () => {
|
||||
</ak-enterprise-status-card>`,
|
||||
);
|
||||
|
||||
const status = $("ak-enterprise-status-card").$(
|
||||
const status = await $("ak-enterprise-status-card").$(
|
||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||
);
|
||||
await expect(status).resolves.toExist();
|
||||
await expect(status).resolves.toHaveText(msg("Valid"));
|
||||
await expect(status).toExist();
|
||||
await expect(status).toHaveText(msg("Valid"));
|
||||
|
||||
const internalUserProgress = $("ak-enterprise-status-card").$(
|
||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#internalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(internalUserProgress).resolves.toExist();
|
||||
await expect(internalUserProgress).resolves.toHaveAttr("aria-valuenow", "100");
|
||||
await expect(internalUserProgress).toExist();
|
||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "100");
|
||||
|
||||
await expect($("ak-enterprise-status-card").$(">>>#internalUsers")).toHaveElementClass(
|
||||
"pf-m-warning",
|
||||
);
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
|
||||
const externalUserProgress = $("ak-enterprise-status-card").$(
|
||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#externalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(externalUserProgress).resolves.toExist();
|
||||
await expect(externalUserProgress).resolves.toHaveAttr("aria-valuenow", "∞");
|
||||
await expect(externalUserProgress).toExist();
|
||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "∞");
|
||||
|
||||
await expect(
|
||||
$("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).resolves.toHaveElementClass("pf-m-warning");
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
await expect(
|
||||
$("ak-enterprise-status-card").$(">>>#externalUsers"),
|
||||
).resolves.toHaveElementClass("pf-m-danger");
|
||||
await $("ak-enterprise-status-card").$(">>>#externalUsers"),
|
||||
).toHaveElementClass("pf-m-danger");
|
||||
});
|
||||
});
|
||||
|
@ -135,8 +135,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state icon="pf-icon-module">
|
||||
<span slot="header">${msg("No Stages bound")}</span>
|
||||
html`<ak-empty-state header=${msg("No Stages bound")} icon="pf-icon-module">
|
||||
<div slot="body">${msg("No stages are currently bound to this flow.")}</div>
|
||||
<div slot="primary">
|
||||
<ak-stage-wizard
|
||||
|
@ -3,7 +3,6 @@ import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/util
|
||||
import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes";
|
||||
import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-slug-input.js";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
@ -92,16 +91,17 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">${msg("Shown as the Title in Flow pages.")}</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
help=${msg("Visible in the URL.")}
|
||||
input-hint="code"
|
||||
></ak-slug-input>
|
||||
|
||||
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.slug)}"
|
||||
class="pf-c-form-control pf-m-monospace"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">${msg("Visible in the URL.")}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Designation")} required name="designation">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.designation === undefined}>
|
||||
@ -211,8 +211,9 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
||||
${msg("Required authentication level for this flow.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group label="${msg("Behavior settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Behavior settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal name="compatibilityMode">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
@ -285,8 +286,9 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group label="${msg("Appearance settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Appearance settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Layout")} required name="layout">
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
|
@ -231,8 +231,9 @@ export class OutpostForm extends ModelForm<Outpost, string> {
|
||||
selected-label="${msg("Selected Applications")}"
|
||||
></ak-dual-select-provider>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group label=${msg("Advanced settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group aria-label=${msg("Advanced settings")}>
|
||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Configuration")} name="config">
|
||||
<ak-codemirror
|
||||
mode=${CodeMirrorMode.YAML}
|
||||
|
@ -198,8 +198,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state icon="pf-icon-module"
|
||||
><span slot="header">${msg("No Policies bound.")}</span>
|
||||
html`<ak-empty-state header=${msg("No Policies bound.")} icon="pf-icon-module">
|
||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||
<div slot="primary">
|
||||
<ak-policy-wizard
|
||||
|
@ -64,8 +64,9 @@ export class DummyPolicyForm extends BasePolicyForm<DummyPolicy> {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal name="result">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
|
@ -76,8 +76,9 @@ export class EventMatcherPolicyForm extends BasePolicyForm<EventMatcherPolicy> {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Action")} name="action">
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<TypeCreate[]> => {
|
||||
|
@ -64,8 +64,9 @@ export class PasswordExpiryPolicyForm extends BasePolicyForm<PasswordExpiryPolic
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Maximum age (in days)")}
|
||||
required
|
||||
|
@ -67,8 +67,9 @@ export class ExpressionPolicyForm extends BasePolicyForm<ExpressionPolicy> {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Expression")}
|
||||
required
|
||||
|
@ -78,8 +78,9 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group label="${msg("Distance settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Distance settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal name="checkHistoryDistance">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
@ -184,8 +185,9 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> {
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group label="${msg("Static rule settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Static rule settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("ASNs")} name="asns">
|
||||
<input
|
||||
type="text"
|
||||
|
@ -44,8 +44,9 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
|
||||
}
|
||||
|
||||
renderStaticRules(): TemplateResult {
|
||||
return html` <ak-form-group label="${msg("Static rules")}">
|
||||
<div class="pf-c-form">
|
||||
return html` <ak-form-group>
|
||||
<span slot="header"> ${msg("Static rules")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Minimum length")}
|
||||
required
|
||||
@ -141,8 +142,9 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
|
||||
|
||||
renderHIBP(): TemplateResult {
|
||||
return html`
|
||||
<ak-form-group open label="${msg("HaveIBeenPwned settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("HaveIBeenPwned settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Allowed count")}
|
||||
required
|
||||
@ -165,8 +167,9 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
|
||||
|
||||
renderZxcvbn(): TemplateResult {
|
||||
return html`
|
||||
<ak-form-group open label="${msg("zxcvbn settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("zxcvbn settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Score threshold")}
|
||||
required
|
||||
|
@ -74,8 +74,9 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal name="checkIp">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
|
@ -62,8 +62,9 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group open label="${msg("General settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("General settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Username")}
|
||||
name="staticSettings.username"
|
||||
@ -88,8 +89,9 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group label="${msg("RDP settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("RDP settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Ignore server certificate")}
|
||||
name="staticSettings.ignore-cert"
|
||||
@ -132,8 +134,9 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group label="${msg("Advanced settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Expression")}
|
||||
required
|
||||
|
@ -3,7 +3,7 @@ import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
import { msg } from "@lit/localize";
|
||||
|
||||
export abstract class BaseProviderForm<T> extends ModelForm<T, number> {
|
||||
public override getSuccessMessage(): string {
|
||||
getSuccessMessage(): string {
|
||||
return this.instance
|
||||
? msg("Successfully updated provider.")
|
||||
: msg("Successfully created provider.");
|
||||
|
@ -28,38 +28,32 @@ import { Provider, ProvidersApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-list")
|
||||
export class ProviderListPage extends TablePage<Provider> {
|
||||
override searchEnabled(): boolean {
|
||||
searchEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
override pageTitle(): string {
|
||||
pageTitle(): string {
|
||||
return msg("Providers");
|
||||
}
|
||||
|
||||
override pageDescription(): string {
|
||||
pageDescription(): string {
|
||||
return msg("Provide support for protocols like SAML and OAuth to assigned applications.");
|
||||
}
|
||||
|
||||
override pageIcon(): string {
|
||||
pageIcon(): string {
|
||||
return "pf-icon pf-icon-integration";
|
||||
}
|
||||
|
||||
override checkbox = true;
|
||||
override clearOnRefresh = true;
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
|
||||
@property()
|
||||
public order = "name";
|
||||
order = "name";
|
||||
|
||||
public searchLabel = msg("Provider name");
|
||||
public searchPlaceholder = msg("Search for providers…");
|
||||
|
||||
override async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
|
||||
async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersAllList(
|
||||
await this.defaultEndpointConfig(),
|
||||
);
|
||||
}
|
||||
|
||||
override columns(): TableColumn[] {
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(msg("Name"), "name"),
|
||||
new TableColumn(msg("Application")),
|
||||
@ -68,9 +62,8 @@ export class ProviderListPage extends TablePage<Provider> {
|
||||
];
|
||||
}
|
||||
|
||||
override renderToolbarSelected(): TemplateResult {
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
|
||||
return html`<ak-forms-delete-bulk
|
||||
objectLabel=${msg("Provider(s)")}
|
||||
.objects=${this.selectedElements}
|
||||
@ -91,7 +84,7 @@ export class ProviderListPage extends TablePage<Provider> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
#rowApp(item: Provider): TemplateResult {
|
||||
rowApp(item: Provider): TemplateResult {
|
||||
if (item.assignedApplicationName) {
|
||||
return html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
|
||||
${msg("Assigned to application ")}
|
||||
@ -99,7 +92,6 @@ export class ProviderListPage extends TablePage<Provider> {
|
||||
>${item.assignedApplicationName}</a
|
||||
>`;
|
||||
}
|
||||
|
||||
if (item.assignedBackchannelApplicationName) {
|
||||
return html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
|
||||
${msg("Assigned to application (backchannel) ")}
|
||||
@ -107,15 +99,15 @@ export class ProviderListPage extends TablePage<Provider> {
|
||||
>${item.assignedBackchannelApplicationName}</a
|
||||
>`;
|
||||
}
|
||||
|
||||
return html`<i aria-hidden="true" class="pf-icon pf-icon-warning-triangle pf-m-warning"></i>
|
||||
${msg("Warning: Provider not assigned to any application.")}`;
|
||||
return html`<i class="pf-icon pf-icon-warning-triangle pf-m-warning"></i> ${msg(
|
||||
"Warning: Provider not assigned to any application.",
|
||||
)}`;
|
||||
}
|
||||
|
||||
override row(item: Provider): TemplateResult[] {
|
||||
row(item: Provider): TemplateResult[] {
|
||||
return [
|
||||
html`<a href="#/core/providers/${item.pk}"> ${item.name} </a>`,
|
||||
this.#rowApp(item),
|
||||
this.rowApp(item),
|
||||
html`${item.verboseName}`,
|
||||
html`<ak-forms-modal>
|
||||
<span slot="submit"> ${msg("Update")} </span>
|
||||
@ -128,20 +120,16 @@ export class ProviderListPage extends TablePage<Provider> {
|
||||
type=${item.component}
|
||||
>
|
||||
</ak-proxy-form>
|
||||
<button
|
||||
aria-label=${msg("Edit provider")}
|
||||
slot="trigger"
|
||||
class="pf-c-button pf-m-plain"
|
||||
>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i aria-hidden="true" class="fas fa-edit"></i>
|
||||
<i class="fas fa-edit"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>`,
|
||||
];
|
||||
}
|
||||
|
||||
override renderObjectCreate(): TemplateResult {
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`<ak-provider-wizard> </ak-provider-wizard> `;
|
||||
}
|
||||
}
|
||||
|
@ -25,16 +25,23 @@ import { ProvidersApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-wizard")
|
||||
export class ProviderWizard extends AKElement {
|
||||
static styles: CSSResult[] = [PFBase, PFButton];
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFButton];
|
||||
}
|
||||
|
||||
@property()
|
||||
createText = msg("Create");
|
||||
|
||||
@property({ attribute: false })
|
||||
public providerTypes: TypeCreate[] = [];
|
||||
providerTypes: TypeCreate[] = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
public finalHandler?: () => Promise<void>;
|
||||
finalHandler: () => Promise<void> = () => {
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
@query("ak-wizard")
|
||||
private wizard?: Wizard;
|
||||
wizard?: Wizard;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
@ -49,7 +56,9 @@ export class ProviderWizard extends AKElement {
|
||||
.steps=${["initial"]}
|
||||
header=${msg("New provider")}
|
||||
description=${msg("Create a new provider.")}
|
||||
.finalHandler=${this.finalHandler}
|
||||
.finalHandler=${() => {
|
||||
return this.finalHandler();
|
||||
}}
|
||||
>
|
||||
<ak-wizard-page-type-create
|
||||
name="selectProviderType"
|
||||
@ -73,15 +82,7 @@ export class ProviderWizard extends AKElement {
|
||||
</ak-wizard-page-form>
|
||||
`;
|
||||
})}
|
||||
<button
|
||||
aria-label=${msg("New Provider")}
|
||||
aria-description="${msg("Open the wizard to create a new provider.")}"
|
||||
type="button"
|
||||
slot="trigger"
|
||||
class="pf-c-button pf-m-primary"
|
||||
>
|
||||
${msg("Create")}
|
||||
</button>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${this.createText}</button>
|
||||
</ak-wizard>
|
||||
`;
|
||||
}
|
||||
|
@ -56,8 +56,9 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Credentials")}
|
||||
required
|
||||
@ -180,8 +181,9 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group open label="${msg("User filtering")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header">${msg("User filtering")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal name="excludeUsersServiceAccount">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
@ -232,8 +234,9 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group open label="${msg("Attribute mapping")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Attribute mapping")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User Property Mappings")}
|
||||
name="propertyMappings"
|
||||
|
@ -47,9 +47,7 @@ export function renderForm(
|
||||
) {
|
||||
return html`
|
||||
<ak-text-input
|
||||
autocomplete="on"
|
||||
name="name"
|
||||
placeholder=${msg("Provider name")}
|
||||
value=${ifDefined(provider?.name)}
|
||||
label=${msg("Name")}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
@ -82,8 +80,10 @@ export function renderForm(
|
||||
>
|
||||
</ak-switch-input>
|
||||
|
||||
<ak-form-group open label="${msg("Flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Flow settings")} </span>
|
||||
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Bind flow")}
|
||||
required
|
||||
@ -91,7 +91,6 @@ export function renderForm(
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
>
|
||||
<ak-branded-flow-search
|
||||
label=${msg("Bind flow")}
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
.brandFlow=${brand?.flowAuthentication}
|
||||
@ -120,8 +119,9 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="baseDn"
|
||||
label=${msg("Base DN")}
|
||||
@ -141,8 +141,6 @@ export function renderForm(
|
||||
.errorMessages=${errors?.certificate ?? []}
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
label=${msg("Certificate")}
|
||||
placeholder=${msg("Select a certificate...")}
|
||||
certificate=${ifDefined(provider?.certificate ?? nothing)}
|
||||
name="certificate"
|
||||
>
|
||||
|
@ -4,7 +4,6 @@ import {
|
||||
propertyMappingsSelector,
|
||||
} from "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderFormHelpers.js";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-hidden-text-input";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
@ -55,8 +54,9 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Client ID")} required name="clientId">
|
||||
<input
|
||||
type="text"
|
||||
@ -68,15 +68,21 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
|
||||
${msg("Client ID for the app registration.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-hidden-text-input
|
||||
name="clientSecret"
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Client Secret")}
|
||||
value="${this.instance?.clientSecret ?? ""}"
|
||||
input-hint="code"
|
||||
required
|
||||
.help=${msg("Client secret for the app registration.")}
|
||||
name="clientSecret"
|
||||
>
|
||||
</ak-hidden-text-input>
|
||||
<input
|
||||
type="text"
|
||||
value="${this.instance?.clientSecret ?? ""}"
|
||||
class="pf-c-form-control pf-m-monospace"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Client secret for the app registration.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Tenant ID")} required name="tenantId">
|
||||
<input
|
||||
type="text"
|
||||
@ -156,8 +162,9 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group open label="${msg("User filtering")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header">${msg("User filtering")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal name="excludeUsersServiceAccount">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
@ -208,8 +215,9 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group open label="${msg("Attribute mapping")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Attribute mapping")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User Property Mappings")}
|
||||
name="propertyMappings"
|
||||
|
@ -2,7 +2,7 @@ import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
|
||||
|
||||
import { CSSResult, css } from "lit";
|
||||
import { css } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import { ClientTypeEnum, OAuth2Provider, ProvidersApi } from "@goauthentik/api";
|
||||
@ -21,14 +21,16 @@ export async function oauth2ProvidersProvider(page = 1, search = "") {
|
||||
|
||||
return {
|
||||
pagination: oauthProviders.pagination,
|
||||
options: oauthProviders.results.map(providerToSelect),
|
||||
options: oauthProviders.results.map((provider) => providerToSelect(provider)),
|
||||
};
|
||||
}
|
||||
|
||||
export function oauth2ProviderSelector(instanceProviders: number[] | undefined) {
|
||||
if (!instanceProviders) {
|
||||
return async (mappings: DualSelectPair<OAuth2Provider>[]) =>
|
||||
mappings.filter(([, , , source]: DualSelectPair<OAuth2Provider>) => !source);
|
||||
mappings.filter(
|
||||
([_0, _1, _2, source]: DualSelectPair<OAuth2Provider>) => source !== undefined,
|
||||
);
|
||||
}
|
||||
|
||||
return async () => {
|
||||
@ -55,46 +57,41 @@ export function oauth2ProviderSelector(instanceProviders: number[] | undefined)
|
||||
|
||||
@customElement("ak-provider-oauth2-form")
|
||||
export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
|
||||
static styles: CSSResult[] = [
|
||||
...super.styles,
|
||||
css`
|
||||
@state()
|
||||
showClientSecret = true;
|
||||
|
||||
static get styles() {
|
||||
return super.styles.concat(css`
|
||||
ak-array-input {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
`);
|
||||
}
|
||||
|
||||
@state()
|
||||
protected showClientSecret = true;
|
||||
|
||||
override async loadInstance(pk: number): Promise<OAuth2Provider> {
|
||||
async loadInstance(pk: number): Promise<OAuth2Provider> {
|
||||
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2Retrieve({
|
||||
id: pk,
|
||||
});
|
||||
|
||||
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
override async send(data: OAuth2Provider): Promise<OAuth2Provider> {
|
||||
async send(data: OAuth2Provider): Promise<OAuth2Provider> {
|
||||
if (this.instance) {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Update({
|
||||
id: this.instance.pk,
|
||||
oAuth2ProviderRequest: data,
|
||||
});
|
||||
}
|
||||
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Create({
|
||||
oAuth2ProviderRequest: data,
|
||||
});
|
||||
}
|
||||
|
||||
override renderForm() {
|
||||
renderForm() {
|
||||
const showClientSecretCallback = (show: boolean) => {
|
||||
this.showClientSecret = show;
|
||||
};
|
||||
|
||||
return renderForm(this.instance ?? {}, [], this.showClientSecret, showClientSecretCallback);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
akOAuthRedirectURIInput,
|
||||
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI";
|
||||
import { ascii_letters, digits, randomString } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-hidden-text-input";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/components/ak-textarea-input";
|
||||
@ -125,9 +124,7 @@ export function renderForm(
|
||||
showClientSecretCallback: ShowClientSecret = defaultShowClientSecret,
|
||||
) {
|
||||
return html` <ak-text-input
|
||||
autocomplete="on"
|
||||
name="name"
|
||||
placeholder=${msg("Provider name")}
|
||||
label=${msg("Name")}
|
||||
value=${ifDefined(provider?.name)}
|
||||
required
|
||||
@ -139,8 +136,6 @@ export function renderForm(
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
label=${msg("Authorization flow")}
|
||||
placeholder=${msg("Select an authorization flow...")}
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
required
|
||||
@ -149,8 +144,9 @@ export function renderForm(
|
||||
${msg("Flow used when authorizing this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-radio-input
|
||||
name="clientType"
|
||||
label=${msg("Client type")}
|
||||
@ -170,16 +166,17 @@ export function renderForm(
|
||||
input-hint="code"
|
||||
>
|
||||
</ak-text-input>
|
||||
<ak-hidden-text-input
|
||||
<ak-text-input
|
||||
name="clientSecret"
|
||||
label=${msg("Client Secret")}
|
||||
value="${provider?.clientSecret ?? randomString(128, ascii_letters + digits)}"
|
||||
input-hint="code"
|
||||
?hidden=${!showClientSecret}
|
||||
>
|
||||
</ak-hidden-text-input>
|
||||
</ak-text-input>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Redirect URIs/Origins (RegEx)")}
|
||||
required
|
||||
name="redirectUris"
|
||||
>
|
||||
<ak-array-input
|
||||
@ -199,8 +196,6 @@ export function renderForm(
|
||||
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
|
||||
<!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
|
||||
<ak-crypto-certificate-search
|
||||
label=${msg("Signing Key")}
|
||||
placeholder=${msg("Select a signing key...")}
|
||||
certificate=${ifDefined(provider?.signingKey ?? undefined)}
|
||||
singleton
|
||||
></ak-crypto-certificate-search>
|
||||
@ -209,8 +204,6 @@ export function renderForm(
|
||||
<ak-form-element-horizontal label=${msg("Encryption Key")} name="encryptionKey">
|
||||
<!-- NOTE: 'null' cast to 'undefined' on encryptionKey to satisfy Lit requirements -->
|
||||
<ak-crypto-certificate-search
|
||||
label=${msg("Encryption Key")}
|
||||
placeholder=${msg("Select an encryption key...")}
|
||||
certificate=${ifDefined(provider?.encryptionKey ?? undefined)}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">${msg("Key used to encrypt the tokens.")}</p>
|
||||
@ -218,15 +211,14 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group label=${msg("Advanced flow settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
name="authenticationFlow"
|
||||
label=${msg("Authentication flow")}
|
||||
>
|
||||
<ak-flow-search
|
||||
label=${msg("Authentication flow")}
|
||||
placeHolder=${msg("Select an authentication flow...")}
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider?.authenticationFlow}
|
||||
></ak-flow-search>
|
||||
@ -242,8 +234,6 @@ export function renderForm(
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
label=${msg("Invalidation flow")}
|
||||
placeHolder=${msg("Select an invalidation flow...")}
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
defaultFlowSlug="default-provider-invalidation-flow"
|
||||
@ -256,8 +246,9 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="accessCodeValidity"
|
||||
label=${msg("Access code validity")}
|
||||
@ -340,8 +331,9 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group label="${msg("Machine-to-Machine authentication settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Federated OIDC Sources")}
|
||||
name="jwtFederationSources"
|
||||
|
@ -1,8 +1,5 @@
|
||||
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI";
|
||||
import {
|
||||
AkControlElement,
|
||||
formatFormElementAsJSON,
|
||||
} from "@goauthentik/elements/AkControlElement.js";
|
||||
import { AkControlElement } from "@goauthentik/elements/AkControlElement.js";
|
||||
import { type Spread } from "@goauthentik/elements/types";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
@ -46,7 +43,9 @@ export class OAuth2ProviderRedirectURI extends AkControlElement<RedirectURI> {
|
||||
controls?: HTMLInputElement[];
|
||||
|
||||
json() {
|
||||
return formatFormElementAsJSON<RedirectURI>(this.controls);
|
||||
return Object.fromEntries(
|
||||
Array.from(this.controls ?? []).map((control) => [control.name, control.value]),
|
||||
) as unknown as RedirectURI;
|
||||
}
|
||||
|
||||
get isValid() {
|
||||
|
@ -4,7 +4,6 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import renderDescriptionList from "@goauthentik/components/DescriptionList";
|
||||
import "@goauthentik/components/events/ObjectChangelog";
|
||||
import { IDGenerator } from "@goauthentik/core/id";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
@ -266,16 +265,12 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
<div class="pf-c-card__body">
|
||||
<form class="pf-c-form">
|
||||
<div class="pf-c-form__group">
|
||||
<label
|
||||
class="pf-c-form__label"
|
||||
for="${IDGenerator.elementID("providerInfo")}"
|
||||
>
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${msg("OpenID Configuration URL")}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
id="${IDGenerator.elementID("providerInfo")}"
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
@ -283,16 +278,12 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label
|
||||
class="pf-c-form__label"
|
||||
for="${IDGenerator.elementID("issuer")}"
|
||||
>
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${msg("OpenID Configuration Issuer")}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
id="${IDGenerator.elementID("issuer")}"
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
@ -301,16 +292,12 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
</div>
|
||||
<hr class="pf-c-divider" />
|
||||
<div class="pf-c-form__group">
|
||||
<label
|
||||
class="pf-c-form__label"
|
||||
for="${IDGenerator.elementID("authorize")}"
|
||||
>
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${msg("Authorize URL")}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
id="${IDGenerator.elementID("authorize")}"
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
@ -318,14 +305,10 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label
|
||||
class="pf-c-form__label"
|
||||
for="${IDGenerator.elementID("token")}"
|
||||
>
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text">${msg("Token URL")}</span>
|
||||
</label>
|
||||
<input
|
||||
id="${IDGenerator.elementID("token")}"
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
@ -333,16 +316,12 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label
|
||||
class="pf-c-form__label"
|
||||
for="${IDGenerator.elementID("userInfo")}"
|
||||
>
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${msg("Userinfo URL")}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
id="${IDGenerator.elementID("userInfo")}"
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
@ -350,14 +329,10 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label
|
||||
class="pf-c-form__label"
|
||||
for="${IDGenerator.elementID("logout")}"
|
||||
>
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text">${msg("Logout URL")}</span>
|
||||
</label>
|
||||
<input
|
||||
id="${IDGenerator.elementID("logout")}"
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
@ -365,14 +340,10 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label
|
||||
class="pf-c-form__label"
|
||||
for="${IDGenerator.elementID("jwks")}"
|
||||
>
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text">${msg("JWKS URL")}</span>
|
||||
</label>
|
||||
<input
|
||||
id="${IDGenerator.elementID("jwks")}"
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
@ -418,12 +389,9 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
${renderDescriptionList(
|
||||
[
|
||||
[
|
||||
html`<label for="${IDGenerator.elementID("preview-user")}"
|
||||
>${msg("Preview for user")}</label
|
||||
>`,
|
||||
msg("Preview for user"),
|
||||
html`
|
||||
<ak-search-select
|
||||
id="${IDGenerator.elementID("preview-user")}"
|
||||
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
||||
const args: CoreUsersListRequest = {
|
||||
ordering: "username",
|
||||
|
@ -228,8 +228,9 @@ export function renderForm(
|
||||
input-hint="code"
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Advanced protocol settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${provider?.certificate}
|
||||
@ -272,8 +273,9 @@ ${provider?.skipPathRegex}</textarea
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group label="${msg("Authentication settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Authentication settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-switch-input
|
||||
name="interceptHeaderAuth"
|
||||
label=${msg("Intercept header authentication")}
|
||||
@ -331,8 +333,9 @@ ${provider?.skipPathRegex}</textarea
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group label="${msg("Advanced flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
name="authenticationFlow"
|
||||
|
@ -115,8 +115,9 @@ export class EndpointForm extends ModelForm<Endpoint, string> {
|
||||
selected-label="${msg("Selected User Property Mappings")}"
|
||||
></ak-dual-select-dynamic-selected>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group label="${msg("Advanced settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Settings")} name="settings">
|
||||
<ak-codemirror
|
||||
mode="yaml"
|
||||
|
@ -115,8 +115,9 @@ export class RACProviderFormPage extends ModelForm<RACProvider, number> {
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Property mappings")}
|
||||
name="propertyMappings"
|
||||
|
@ -1,8 +1,6 @@
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
||||
import { ascii_letters, digits, randomString } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-hidden-text-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import "@goauthentik/elements/forms/SearchSelect";
|
||||
@ -44,7 +42,6 @@ export function renderForm(
|
||||
<ak-text-input
|
||||
name="name"
|
||||
label=${msg("Name")}
|
||||
placeholder=${msg("Provider name")}
|
||||
value=${ifDefined(provider?.name)}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
required
|
||||
@ -58,8 +55,6 @@ export function renderForm(
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
>
|
||||
<ak-branded-flow-search
|
||||
label=${msg("Authentication flow")}
|
||||
placeholder=${msg("Select an authentication flow...")}
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
.brandFlow=${brand?.flowAuthentication}
|
||||
@ -76,14 +71,17 @@ export function renderForm(
|
||||
>
|
||||
</ak-switch-input>
|
||||
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-hidden-text-input>
|
||||
name="sharedSecret" label=${msg("Shared secret")}
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="sharedSecret"
|
||||
label=${msg("Shared secret")}
|
||||
.errorMessages=${errors?.sharedSecret ?? []}
|
||||
value=${provider?.sharedSecret ?? randomString(128, ascii_letters + digits)}
|
||||
required input-hint="code" ></ak-hidden-text-input
|
||||
>
|
||||
required
|
||||
input-hint="code"
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
name="clientNetworks"
|
||||
label=${msg("Client Networks")}
|
||||
@ -106,16 +104,15 @@ export function renderForm(
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group label="${msg("Advanced flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Invalidation flow")}
|
||||
name="invalidationFlow"
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
label=${msg("Invalidation flow")}
|
||||
placeholder=${msg("Select an invalidation flow...")}
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
.errorMessages=${errors?.invalidationFlow ?? []}
|
||||
|
@ -84,8 +84,9 @@ export function renderForm(
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="acsUrl"
|
||||
label=${msg("ACS URL")}
|
||||
@ -121,8 +122,9 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group label="${msg("Advanced flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
name="authenticationFlow"
|
||||
@ -155,8 +157,9 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Signing Certificate")} name="signingKp">
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${provider?.signingKp}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user