Merge branch 'main' into web/update-provider-forms-for-invalidation

* main: (44 commits)
  web/admin: add strict dompurify config for diagram (#11783)
  core: bump cryptography from 43.0.1 to 43.0.3 (#11750)
  web: bump API Client version (#11781)
  sources: add Kerberos (#10815)
  root: rework CSRF middleware to set secure flag (#11753)
  web/admin: improve invalidation flow default & field grouping (#11769)
  providers/scim: add comparison with existing group on update and delta update users (#11414)
  website: bump mermaid from 10.6.0 to 10.9.3 in /website (#11766)
  web/flows: use dompurify for footer links (#11773)
  core, web: update translations (#11775)
  core: bump goauthentik.io/api/v3 from 3.2024083.10 to 3.2024083.11 (#11776)
  website: bump @types/react from 18.3.11 to 18.3.12 in /website (#11777)
  website: bump http-proxy-middleware from 2.0.6 to 2.0.7 in /website (#11771)
  web: bump API Client version (#11770)
  stages: authenticator_endpoint_gdtc (#10477)
  core: add prompt_data to auth flow (#11702)
  tests/e2e: fix dex tests failing (#11761)
  web/rac: disable DPI scaling (#11757)
  web/admin: update flow background (#11758)
  website/docs: fix some broken links (#11742)
  ...
This commit is contained in:
Ken Sternberg
2024-10-23 14:00:31 -07:00
160 changed files with 8608 additions and 1090 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

83
web/package-lock.json generated
View File

@ -23,7 +23,8 @@
"@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2024.8.3-1729166675",
"@goauthentik/api": "^2024.8.3-1729699127",
"@lit-labs/ssr": "^3.2.2",
"@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2",
"@lit/reactive-element": "^2.0.4",
@ -41,6 +42,7 @@
"construct-style-sheets-polyfill": "^3.1.0",
"core-js": "^3.38.1",
"country-flag-icons": "^1.5.13",
"dompurify": "^3.1.7",
"fuse.js": "^7.0.0",
"guacamole-common-js": "^1.5.0",
"lit": "^3.2.0",
@ -69,6 +71,7 @@
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41",
"@types/codemirror": "^5.60.15",
"@types/dompurify": "^3.0.5",
"@types/eslint__js": "^8.42.3",
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.2",
@ -1772,9 +1775,9 @@
}
},
"node_modules/@goauthentik/api": {
"version": "2024.8.3-1729166675",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.8.3-1729166675.tgz",
"integrity": "sha512-mAcVaMHB2KGGGigUgu3aurSo05yBLarbfvEnvhUs2pvxaTSuX5Zezl34OlFh/gGWQEt0Z7St7GGaY256F3E74g=="
"version": "2024.8.3-1729699127",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.8.3-1729699127.tgz",
"integrity": "sha512-luo0SAASR6BTTtLszDgfdwofBejv4F3hCHgPxeSoTSFgE8/A2+zJD8EtWPZaa1udDkwPa9lbIeJSSmbgFke3jA=="
},
"node_modules/@goauthentik/web": {
"resolved": "",
@ -2459,11 +2462,50 @@
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lit-labs/ssr": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr/-/ssr-3.2.2.tgz",
"integrity": "sha512-He5TzeNPM9ECmVpgXRYmVlz0UA5YnzHlT43kyLi2Lu6mUidskqJVonk9W5K699+2DKhoXp8Ra4EJmHR6KrcW1Q==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit-labs/ssr-client": "^1.1.7",
"@lit-labs/ssr-dom-shim": "^1.2.0",
"@lit/reactive-element": "^2.0.4",
"@parse5/tools": "^0.3.0",
"@types/node": "^16.0.0",
"enhanced-resolve": "^5.10.0",
"lit": "^3.1.2",
"lit-element": "^4.0.4",
"lit-html": "^3.1.2",
"node-fetch": "^3.2.8",
"parse5": "^7.1.1"
},
"engines": {
"node": ">=13.9.0"
}
},
"node_modules/@lit-labs/ssr-client": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-client/-/ssr-client-1.1.7.tgz",
"integrity": "sha512-VvqhY/iif3FHrlhkzEPsuX/7h/NqnfxLwVf0p8ghNIlKegRyRqgeaJevZ57s/u/LiFyKgqksRP5n+LmNvpxN+A==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit/reactive-element": "^2.0.4",
"lit": "^3.1.2",
"lit-html": "^3.1.2"
}
},
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz",
"integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ=="
},
"node_modules/@lit-labs/ssr/node_modules/@types/node": {
"version": "16.18.114",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.114.tgz",
"integrity": "sha512-7oAtnxrgkMNzyzT443UDWwzkmYew81F1ZSPm3/lsITJfW/WludaSOpegTvUG+UdapcbrtWOtY/E4LyTkhPGJ5Q==",
"license": "MIT"
},
"node_modules/@lit/context": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.2.tgz",
@ -3027,7 +3069,6 @@
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@parse5/tools/-/tools-0.3.0.tgz",
"integrity": "sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==",
"dev": true,
"dependencies": {
"parse5": "^7.0.0"
}
@ -5629,6 +5670,16 @@
"@types/node": "*"
}
},
"node_modules/@types/dompurify": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
"integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/trusted-types": "*"
}
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
@ -9676,7 +9727,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"dev": true,
"engines": {
"node": ">= 12"
}
@ -10053,9 +10103,10 @@
}
},
"node_modules/dompurify": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
"integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz",
"integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==",
"license": "(MPL-2.0 OR Apache-2.0)"
},
"node_modules/domutils": {
"version": "3.1.0",
@ -10278,7 +10329,6 @@
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@ -10316,7 +10366,6 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"engines": {
"node": ">=0.12"
},
@ -12518,8 +12567,7 @@
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"node_modules/grapheme-splitter": {
"version": "1.0.4",
@ -14969,6 +15017,12 @@
"uuid": "^9.0.1"
}
},
"node_modules/mermaid/node_modules/dompurify": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
"integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==",
"license": "(MPL-2.0 OR Apache-2.0)"
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@ -15649,7 +15703,6 @@
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"dev": true,
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
@ -16588,7 +16641,6 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
"dev": true,
"dependencies": {
"entities": "^4.4.0"
},
@ -19574,7 +19626,6 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
"dev": true,
"engines": {
"node": ">=6"
}

View File

@ -11,7 +11,8 @@
"@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2024.8.3-1729166675",
"@goauthentik/api": "^2024.8.3-1729699127",
"@lit-labs/ssr": "^3.2.2",
"@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2",
"@lit/reactive-element": "^2.0.4",
@ -29,6 +30,7 @@
"construct-style-sheets-polyfill": "^3.1.0",
"core-js": "^3.38.1",
"country-flag-icons": "^1.5.13",
"dompurify": "^3.1.7",
"fuse.js": "^7.0.0",
"guacamole-common-js": "^1.5.0",
"lit": "^3.2.0",
@ -57,6 +59,7 @@
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41",
"@types/codemirror": "^5.60.15",
"@types/dompurify": "^3.0.5",
"@types/eslint__js": "^8.42.3",
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.2",

View File

@ -1,33 +1,14 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { first } from "@goauthentik/common/utils";
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 { renderForm } from "@goauthentik/admin/providers/ldap/LDAPProviderFormForm.js";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import { html } from "lit";
import { FlowsInstancesListDesignationEnum } from "@goauthentik/api";
import type { LDAPProvider } from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
import {
bindModeOptions,
cryptoCertificateHelp,
gidStartNumberHelp,
mfaSupportHelp,
searchModeOptions,
tlsServerNameHelp,
uidStartNumberHelp,
} from "./LDAPOptionsAndHelp";
@customElement("ak-application-wizard-authentication-by-ldap")
export class ApplicationWizardApplicationDetails extends WithBrandConfig(BaseProviderPanel) {
@ -37,115 +18,7 @@ export class ApplicationWizardApplicationDetails extends WithBrandConfig(BasePro
return html` <ak-wizard-title>${msg("Configure LDAP Provider")}</ak-wizard-title>
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
value=${ifDefined(provider?.name)}
label=${msg("Name")}
.errorMessages=${errors?.name ?? []}
required
help=${msg("Method's display Name.")}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Bind flow")}
?required=${true}
name="authorizationFlow"
.errorMessages=${errors?.authorizationFlow ?? []}
>
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.brandFlow=${this.brand.flowAuthentication}
required
></ak-branded-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used for users to authenticate.")}
</p>
</ak-form-element-horizontal>
<ak-radio-input
label=${msg("Bind mode")}
name="bindMode"
.options=${bindModeOptions}
.value=${provider?.bindMode}
help=${msg("Configure how the outpost authenticates requests.")}
>
</ak-radio-input>
<ak-radio-input
label=${msg("Search mode")}
name="searchMode"
.options=${searchModeOptions}
.value=${provider?.searchMode}
help=${msg(
"Configure how the outpost queries the core authentik server's users.",
)}
>
</ak-radio-input>
<ak-switch-input
name="openInNewTab"
label=${msg("Code-based MFA Support")}
?checked=${provider?.mfaSupport ?? true}
help=${mfaSupportHelp}
>
</ak-switch-input>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="baseDn"
label=${msg("Base DN")}
required
value="${first(provider?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}"
.errorMessages=${errors?.baseDn ?? []}
help=${msg(
"LDAP DN under which bind requests and search requests can be made.",
)}
>
</ak-text-input>
<ak-form-element-horizontal
label=${msg("Certificate")}
name="certificate"
.errorMessages=${errors?.certificate ?? []}
>
<ak-crypto-certificate-search
certificate=${ifDefined(provider?.certificate ?? nothing)}
name="certificate"
>
</ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">${cryptoCertificateHelp}</p>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("TLS Server name")}
name="tlsServerName"
value="${first(provider?.tlsServerName, "")}"
.errorMessages=${errors?.tlsServerName ?? []}
help=${tlsServerNameHelp}
></ak-text-input>
<ak-number-input
label=${msg("UID start number")}
required
name="uidStartNumber"
value="${first(provider?.uidStartNumber, 2000)}"
.errorMessages=${errors?.uidStartNumber ?? []}
help=${uidStartNumberHelp}
></ak-number-input>
<ak-number-input
label=${msg("GID start number")}
required
name="gidStartNumber"
value="${first(provider?.gidStartNumber, 4000)}"
.errorMessages=${errors?.gidStartNumber ?? []}
help=${gidStartNumberHelp}
></ak-number-input>
</div>
</ak-form-group>
${renderForm(provider, errors, this.brand)}
</form>`;
}
}

View File

@ -119,21 +119,6 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
name="invalidationFlow"
label=${msg("Invalidation flow")}
.errorMessages=${errors?.invalidationFlow ?? []}
?required=${true}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${this.instance?.invalidationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when logging out of this provider.")}
</p>
</ak-form-element-horizontal>
${this.renderProxyMode()}
@ -176,9 +161,11 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
<ak-textarea-input
name="skipPathRegex"
label=${this.mode === ProxyMode.ForwardDomain
? msg("Unauthenticated URLs")
: msg("Unauthenticated Paths")}
label=${
this.mode === ProxyMode.ForwardDomain
? msg("Unauthenticated URLs")
: msg("Unauthenticated Paths")
}
value=${ifDefined(this.instance?.skipPathRegex)}
.errorMessages=${errors?.skipPathRegex ?? []}
.bighelp=${html` <p class="pf-c-form__helper-text">
@ -195,6 +182,39 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
</ak-textarea-input>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Advanced flow settings")} </span>
<ak-form-element-horizontal
name="authenticationFlow"
label=${msg("Authentication flow")}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${this.instance?.authenticationFlow}
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg(
"Flow used when a user access this provider and is not authenticated.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Invalidation flow")}
name="invalidationFlow"
required
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${this.instance?.invalidationFlow}
defaultFlowSlug="default-provider-invalidation-flow"
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when logging out of this provider.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">${msg("Authentication settings")}</span>
<div slot="body" class="pf-c-form">

View File

@ -103,21 +103,6 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
name="invalidationFlow"
label=${msg("Invalidation flow")}
.errorMessages=${errors?.invalidationFlow ?? []}
?required=${true}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${provider?.invalidationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when logging out of this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
@ -160,6 +145,39 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Advanced flow settings")} </span>
<ak-form-element-horizontal
name="authenticationFlow"
label=${msg("Authentication flow")}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authenticationFlow}
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg(
"Flow used when a user access this provider and is not authenticated.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Invalidation flow")}
name="invalidationFlow"
required
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${provider?.invalidationFlow}
defaultFlowSlug="default-provider-invalidation-flow"
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when logging out of this provider.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Advanced protocol settings")} </span>
<div slot="body" class="pf-c-form">
@ -181,52 +199,60 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane
)}
</p>
</ak-form-element-horizontal>
${this.hasSigningKp
? html` <ak-form-element-horizontal name="signAssertion">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(provider?.signAssertion, true)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
${
this.hasSigningKp
? html` <ak-form-element-horizontal name="signAssertion">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(provider?.signAssertion, true)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i
class="fas fa-check"
aria-hidden="true"
></i>
</span>
</span>
</span>
<span class="pf-c-switch__label"
>${msg("Sign assertions")}</span
>
</label>
<p class="pf-c-form__helper-text">
${msg(
"When enabled, the assertion element of the SAML response will be signed.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="signResponse">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(provider?.signResponse, false)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
<span class="pf-c-switch__label"
>${msg("Sign assertions")}</span
>
</label>
<p class="pf-c-form__helper-text">
${msg(
"When enabled, the assertion element of the SAML response will be signed.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="signResponse">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(provider?.signResponse, false)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i
class="fas fa-check"
aria-hidden="true"
></i>
</span>
</span>
</span>
<span class="pf-c-switch__label"
>${msg("Sign responses")}</span
>
</label>
<p class="pf-c-form__helper-text">
${msg(
"When enabled, the assertion element of the SAML response will be signed.",
)}
</p>
</ak-form-element-horizontal>`
: nothing}
<span class="pf-c-switch__label"
>${msg("Sign responses")}</span
>
</label>
<p class="pf-c-form__helper-text">
${msg(
"When enabled, the assertion element of the SAML response will be signed.",
)}
</p>
</ak-form-element-horizontal>`
: nothing
}
<ak-form-element-horizontal
label=${msg("Verification Certificate")}

View File

@ -61,6 +61,14 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
@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.
*
* @attr
*/
@property()
defaultFlowSlug?: string;
@property({ type: String })
name: string | null | undefined;
@ -97,9 +105,12 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
* use this method, but several have more complex needs, such as relating to the brand, or just
* returning false.
*/
selected(flow: Flow): boolean {
return this.currentFlow === flow.pk;
let selected = this.currentFlow === flow.pk;
if (!this.currentFlow && this.defaultFlowSlug && flow.slug === this.defaultFlowSlug) {
selected = true;
}
return selected;
}
connectedCallback() {

View File

@ -6,6 +6,7 @@ import "@goauthentik/admin/property-mappings/PropertyMappingProviderRadiusForm";
import "@goauthentik/admin/property-mappings/PropertyMappingProviderSAMLForm";
import "@goauthentik/admin/property-mappings/PropertyMappingProviderSCIMForm";
import "@goauthentik/admin/property-mappings/PropertyMappingProviderScopeForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSourceKerberosForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSourceLDAPForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSourceOAuthForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSourcePlexForm";

View File

@ -0,0 +1,40 @@
import { BasePropertyMappingForm } from "@goauthentik/admin/property-mappings/BasePropertyMappingForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { customElement } from "lit/decorators.js";
import { KerberosSourcePropertyMapping, PropertymappingsApi } from "@goauthentik/api";
@customElement("ak-property-mapping-source-kerberos-form")
export class PropertyMappingSourceKerberosForm extends BasePropertyMappingForm<KerberosSourcePropertyMapping> {
docLink(): string {
return "/docs/sources/property-mappings/expressions?utm_source=authentik";
}
loadInstance(pk: string): Promise<KerberosSourcePropertyMapping> {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSourceKerberosRetrieve({
pmUuid: pk,
});
}
async send(data: KerberosSourcePropertyMapping): Promise<KerberosSourcePropertyMapping> {
if (this.instance) {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSourceKerberosUpdate({
pmUuid: this.instance.pk,
kerberosSourcePropertyMappingRequest: data,
});
} else {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSourceKerberosCreate({
kerberosSourcePropertyMappingRequest: data,
});
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-property-mapping-source-kerberos-form": PropertyMappingSourceKerberosForm;
}
}

View File

@ -6,6 +6,7 @@ import "@goauthentik/admin/property-mappings/PropertyMappingProviderRadiusForm";
import "@goauthentik/admin/property-mappings/PropertyMappingProviderSAMLForm";
import "@goauthentik/admin/property-mappings/PropertyMappingProviderSCIMForm";
import "@goauthentik/admin/property-mappings/PropertyMappingProviderScopeForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSourceKerberosForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSourceLDAPForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSourceOAuthForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSourcePlexForm";

View File

@ -8,7 +8,12 @@ import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { GoogleWorkspaceProviderGroup, ProvidersApi, SyncObjectModelEnum } from "@goauthentik/api";
import {
GoogleWorkspaceProviderGroup,
ProvidersApi,
ProvidersGoogleWorkspaceSyncObjectCreateRequest,
SyncObjectModelEnum,
} from "@goauthentik/api";
@customElement("ak-provider-google-workspace-groups-list")
export class GoogleWorkspaceProviderGroupList extends Table<GoogleWorkspaceProviderGroup> {
@ -31,8 +36,11 @@ export class GoogleWorkspaceProviderGroupList extends Table<GoogleWorkspaceProvi
<ak-sync-object-form
.provider=${this.providerId}
model=${SyncObjectModelEnum.Group}
.sync=${new ProvidersApi(DEFAULT_CONFIG)
.providersGoogleWorkspaceSyncObjectCreate}
.sync=${(data: ProvidersGoogleWorkspaceSyncObjectCreateRequest) => {
return new ProvidersApi(
DEFAULT_CONFIG,
).providersGoogleWorkspaceSyncObjectCreate(data);
}}
slot="form"
>
</ak-sync-object-form>

View File

@ -8,7 +8,12 @@ import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { GoogleWorkspaceProviderUser, ProvidersApi, SyncObjectModelEnum } from "@goauthentik/api";
import {
GoogleWorkspaceProviderUser,
ProvidersApi,
ProvidersGoogleWorkspaceSyncObjectCreateRequest,
SyncObjectModelEnum,
} from "@goauthentik/api";
@customElement("ak-provider-google-workspace-users-list")
export class GoogleWorkspaceProviderUserList extends Table<GoogleWorkspaceProviderUser> {
@ -31,8 +36,11 @@ export class GoogleWorkspaceProviderUserList extends Table<GoogleWorkspaceProvid
<ak-sync-object-form
.provider=${this.providerId}
model=${SyncObjectModelEnum.User}
.sync=${new ProvidersApi(DEFAULT_CONFIG)
.providersGoogleWorkspaceSyncObjectCreate}
.sync=${(data: ProvidersGoogleWorkspaceSyncObjectCreateRequest) => {
return new ProvidersApi(
DEFAULT_CONFIG,
).providersGoogleWorkspaceSyncObjectCreate(data);
}}
slot="form"
>
</ak-sync-object-form>

View File

@ -2,24 +2,17 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
FlowsInstancesListDesignationEnum,
LDAPAPIAccessMode,
LDAPProvider,
ProvidersApi,
} from "@goauthentik/api";
import { LDAPProvider, ProvidersApi } from "@goauthentik/api";
import { renderForm } from "./LDAPProviderFormForm.js";
@customElement("ak-provider-ldap-form")
export class LDAPProviderFormPage extends WithBrandConfig(BaseProviderForm<LDAPProvider>) {
@ -42,210 +35,8 @@ export class LDAPProviderFormPage extends WithBrandConfig(BaseProviderForm<LDAPP
}
}
// All Provider objects have an Authorization flow, but not all providers have an Authentication
// flow. LDAP needs only one field, but it is not an Authorization field, it is an
// Authentication field. So, yeah, we're using the authorization field to store the
// authentication information, which is why the ak-branded-flow-search call down there looks so
// weird-- we're looking up Authentication flows, but we're storing them in the Authorization
// field of the target Provider.
renderForm(): TemplateResult {
return html` <ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Bind mode")} name="bindMode">
<ak-radio
.options=${[
{
label: msg("Cached binding"),
value: LDAPAPIAccessMode.Cached,
default: true,
description: html`${msg(
"Flow is executed and session is cached in memory. Flow is executed when session expires",
)}`,
},
{
label: msg("Direct binding"),
value: LDAPAPIAccessMode.Direct,
description: html`${msg(
"Always execute the configured bind flow to authenticate the user",
)}`,
},
]}
.value=${this.instance?.bindMode}
>
</ak-radio>
<p class="pf-c-form__helper-text">
${msg("Configure how the outpost authenticates requests.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Search mode")} name="searchMode">
<ak-radio
.options=${[
{
label: msg("Cached querying"),
value: LDAPAPIAccessMode.Cached,
default: true,
description: html`${msg(
"The outpost holds all users and groups in-memory and will refresh every 5 Minutes",
)}`,
},
{
label: msg("Direct querying"),
value: LDAPAPIAccessMode.Direct,
description: html`${msg(
"Always returns the latest data, but slower than cached querying",
)}`,
},
]}
.value=${this.instance?.searchMode}
>
</ak-radio>
<p class="pf-c-form__helper-text">
${msg("Configure how the outpost queries the core authentik server's users.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="mfaSupport">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(this.instance?.mfaSupport, true)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label">${msg("Code-based MFA Support")}</span>
</label>
<p class="pf-c-form__helper-text">
${msg(
"When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Bind flow")}
name="authorizationFlow"
required
>
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${this.instance?.authorizationFlow}
.brandFlow=${this.brand?.flowAuthentication}
required
></ak-branded-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used for users to authenticate.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Unbind flow")}
name="invalidationFlow"
required
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${this.instance?.invalidationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used for unbinding users.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Base DN")}
?required=${true}
name="baseDn"
>
<input
type="text"
value="${first(this.instance?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg(
"LDAP DN under which bind requests and search requests can be made.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
<ak-crypto-certificate-search
.certificate=${this.instance?.certificate}
></ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">
${msg(
"The certificate for the above configured Base DN. As a fallback, the provider uses a self-signed certificate.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("TLS Server name")}
name="tlsServerName"
>
<input
type="text"
value="${first(this.instance?.tlsServerName, "")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg(
"DNS name for which the above configured certificate should be used. The certificate cannot be detected based on the base DN, as the SSL/TLS negotiation happens before such data is exchanged.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("UID start number")}
?required=${true}
name="uidStartNumber"
>
<input
type="number"
value="${first(this.instance?.uidStartNumber, 2000)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg(
"The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("GID start number")}
?required=${true}
name="gidStartNumber"
>
<input
type="number"
value="${first(this.instance?.gidStartNumber, 4000)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg(
"The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber",
)}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>`;
renderForm() {
return renderForm(this.instance ?? {}, [], this.brand);
}
}

View File

@ -0,0 +1,176 @@
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-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";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import { msg } from "@lit/localize";
import { html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import {
CurrentBrand,
FlowsInstancesListDesignationEnum,
LDAPProvider,
ValidationError,
} from "@goauthentik/api";
import {
bindModeOptions,
cryptoCertificateHelp,
gidStartNumberHelp,
mfaSupportHelp,
searchModeOptions,
tlsServerNameHelp,
uidStartNumberHelp,
} from "./LDAPOptionsAndHelp.js";
// All Provider objects have an Authorization flow, but not all providers have an Authentication
// flow. LDAP needs only one field, but it is not an Authorization field, it is an Authentication
// field. So, yeah, we're using the authorization field to store the authentication information,
// which is why the ak-branded-flow-search call down there looks so weird-- we're looking up
// Authentication flows, but we're storing them in the Authorization field of the target Provider.
export function renderForm(
provider?: Partial<LDAPProvider>,
errors: ValidationError = {},
brand?: CurrentBrand,
) {
return html`
<ak-text-input
name="name"
value=${ifDefined(provider?.name)}
label=${msg("Name")}
.errorMessages=${errors?.name ?? []}
required
help=${msg("Method's display Name.")}
></ak-text-input>
<ak-radio-input
label=${msg("Bind mode")}
name="bindMode"
.options=${bindModeOptions}
.value=${provider?.bindMode}
help=${msg("Configure how the outpost authenticates requests.")}
>
</ak-radio-input>
<ak-radio-input
label=${msg("Search mode")}
name="searchMode"
.options=${searchModeOptions}
.value=${provider?.searchMode}
help=${msg("Configure how the outpost queries the core authentik server's users.")}
>
</ak-radio-input>
<ak-switch-input
name="openInNewTab"
label=${msg("Code-based MFA Support")}
?checked=${provider?.mfaSupport ?? true}
help=${mfaSupportHelp}
>
</ak-switch-input>
<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=${true}
name="authorizationFlow"
.errorMessages=${errors?.authorizationFlow ?? []}
>
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.brandFlow=${brand?.flowAuthentication}
required
></ak-branded-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used for users to authenticate.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Unbind flow")}
name="invalidationFlow"
required
>
<ak-branded-flow-search
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${provider?.invalidationFlow}
.brandFlow=${brand.flowInvalidation}
.errorMessages=${errors?.invalidationFlow ?? []}
required
></ak-branded-flow-search>
<p class="pf-c-form__helper-text">${msg("Flow used for unbinding users.")}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<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")}
required
value="${provider?.baseDn ?? "DC=ldap,DC=goauthentik,DC=io"}"
.errorMessages=${errors?.baseDn ?? []}
help=${msg(
"LDAP DN under which bind requests and search requests can be made.",
)}
>
</ak-text-input>
<ak-form-element-horizontal
label=${msg("Certificate")}
name="certificate"
.errorMessages=${errors?.certificate ?? []}
>
<ak-crypto-certificate-search
certificate=${ifDefined(provider?.certificate ?? nothing)}
name="certificate"
>
</ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">${cryptoCertificateHelp}</p>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("TLS Server name")}
name="tlsServerName"
value="${provider?.tlsServerName ?? ""}"
.errorMessages=${errors?.tlsServerName ?? []}
help=${tlsServerNameHelp}
></ak-text-input>
<ak-number-input
label=${msg("UID start number")}
required
name="uidStartNumber"
value="${provider?.uidStartNumber ?? 2000}"
.errorMessages=${errors?.uidStartNumber ?? []}
help=${uidStartNumberHelp}
></ak-number-input>
<ak-number-input
label=${msg("GID start number")}
required
name="gidStartNumber"
value="${provider?.gidStartNumber ?? 4000}"
.errorMessages=${errors?.gidStartNumber ?? []}
help=${gidStartNumberHelp}
></ak-number-input>
</div>
</ak-form-group>
`;
}

View File

@ -8,7 +8,12 @@ import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { MicrosoftEntraProviderGroup, ProvidersApi, SyncObjectModelEnum } from "@goauthentik/api";
import {
MicrosoftEntraProviderGroup,
ProvidersApi,
ProvidersMicrosoftEntraSyncObjectCreateRequest,
SyncObjectModelEnum,
} from "@goauthentik/api";
@customElement("ak-provider-microsoft-entra-groups-list")
export class MicrosoftEntraProviderGroupList extends Table<MicrosoftEntraProviderGroup> {
@ -28,8 +33,11 @@ export class MicrosoftEntraProviderGroupList extends Table<MicrosoftEntraProvide
<ak-sync-object-form
.provider=${this.providerId}
model=${SyncObjectModelEnum.Group}
.sync=${new ProvidersApi(DEFAULT_CONFIG)
.providersMicrosoftEntraSyncObjectCreate}
.sync=${(data: ProvidersMicrosoftEntraSyncObjectCreateRequest) => {
return new ProvidersApi(
DEFAULT_CONFIG,
).providersMicrosoftEntraSyncObjectCreate(data);
}}
slot="form"
>
</ak-sync-object-form>

View File

@ -8,7 +8,12 @@ import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { MicrosoftEntraProviderUser, ProvidersApi, SyncObjectModelEnum } from "@goauthentik/api";
import {
MicrosoftEntraProviderUser,
ProvidersApi,
ProvidersMicrosoftEntraSyncObjectCreateRequest,
SyncObjectModelEnum,
} from "@goauthentik/api";
@customElement("ak-provider-microsoft-entra-users-list")
export class MicrosoftEntraProviderUserList extends Table<MicrosoftEntraProviderUser> {
@ -31,8 +36,11 @@ export class MicrosoftEntraProviderUserList extends Table<MicrosoftEntraProvider
<ak-sync-object-form
.provider=${this.providerId}
model=${SyncObjectModelEnum.User}
.sync=${new ProvidersApi(DEFAULT_CONFIG)
.providersMicrosoftEntraSyncObjectCreate}
.sync=${(data: ProvidersMicrosoftEntraSyncObjectCreateRequest) => {
return new ProvidersApi(
DEFAULT_CONFIG,
).providersMicrosoftEntraSyncObjectCreate(data);
}}
slot="form"
>
</ak-sync-object-form>

View File

@ -122,10 +122,23 @@ export function renderForm(
name="name"
label=${msg("Name")}
value=${ifDefined(provider?.name)}
.errorMessages=${errors?.name ?? []}
required
></ak-text-input>
<ak-form-element-horizontal
name="authorizationFlow"
label=${msg("Authorization flow")}
?required=${true}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${provider?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
@ -144,7 +157,6 @@ export function renderForm(
name="clientId"
label=${msg("Client ID")}
value="${first(provider?.clientId, randomString(40, ascii_letters + digits))}"
.errorMessages=${errors?.clientId ?? []}
required
>
</ak-text-input>
@ -156,26 +168,20 @@ export function renderForm(
randomString(128, ascii_letters + digits),
)}"
?hidden=${!showClientSecret}
.errorMessages=${errors?.clientSecret ?? []}
>
</ak-text-input>
<ak-textarea-input
name="redirectUris"
label=${msg("Redirect URIs/Origins (RegEx)")}
.value=${provider?.redirectUris}
.errorMessages=${errors?.redirectUriHelp ?? []}
.bighelp=${redirectUriHelp}
>
</ak-textarea-input>
<ak-form-element-horizontal
label=${msg("Signing Key")}
name="signingKey"
.errorMessages=${errors?.signingKey ?? []}
>
<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
certificate=${ifDefined(provider.signingKey ?? undefined)}
certificate=${ifDefined(provider?.signingKey ?? undefined)}
singleton
></ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">${msg("Key used to sign the tokens.")}</p>
@ -183,7 +189,7 @@ 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
certificate=${ifDefined(provider.encryptionKey ?? undefined)}
certificate=${ifDefined(provider?.encryptionKey ?? undefined)}
></ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">${msg("Key used to encrypt the tokens.")}</p>
</ak-form-element-horizontal>
@ -191,7 +197,7 @@ export function renderForm(
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<span slot="header"> ${msg("Advanced flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
name="authenticationFlow"
@ -207,30 +213,15 @@ export function renderForm(
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
name="authorizationFlow"
label=${msg("Authorization flow")}
.errorMessages=${errors?.authorizationFlow ?? []}
?required=${true}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${provider?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Invalidation flow")}
name="invalidationFlow"
.errorMessages=${errors?.invalidationFlow ?? []}
required
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${provider?.invalidationFlow}
defaultFlowSlug="default-provider-invalidation-flow"
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
@ -248,7 +239,6 @@ export function renderForm(
label=${msg("Access code validity")}
required
value="${first(provider?.accessCodeValidity, "minutes=1")}"
.errorMessages=${errors?.accessCodeValidity ?? []}
.bighelp=${html`<p class="pf-c-form__helper-text">
${msg("Configure how long access codes are valid for.")}
</p>
@ -260,7 +250,6 @@ export function renderForm(
label=${msg("Access Token validity")}
value="${first(provider?.accessTokenValidity, "minutes=5")}"
required
.errorMessages=${errors?.accessTokenValidity ?? []}
.bighelp=${html` <p class="pf-c-form__helper-text">
${msg("Configure how long access tokens are valid for.")}
</p>
@ -272,19 +261,14 @@ export function renderForm(
name="refreshTokenValidity"
label=${msg("Refresh Token validity")}
value="${first(provider?.refreshTokenValidity, "days=30")}"
required
.errorMessages=${errors?.refreshTokenValidity ?? []}
?required=${true}
.bighelp=${html` <p class="pf-c-form__helper-text">
${msg("Configure how long refresh tokens are valid for.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
>
</ak-text-input>
<ak-form-element-horizontal
label=${msg("Scopes")}
name="propertyMappings"
.errorMessages=${errors?.propertyMappings ?? []}
>
<ak-form-element-horizontal label=${msg("Scopes")} name="propertyMappings">
<ak-dual-select-dynamic-selected
.provider=${oauth2PropertyMappingsProvider}
.selector=${makeOAuth2PropertyMappingsSelector(provider?.propertyMappings)}
@ -332,11 +316,7 @@ export function renderForm(
<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("Trusted OIDC Sources")}
name="jwksSources"
.errorMessages=${errors?.jwksSources ?? []}
>
<ak-form-element-horizontal label=${msg("Trusted OIDC Sources")} name="jwksSources">
<ak-dual-select-dynamic-selected
.provider=${oauth2SourcesProvider}
.selector=${makeSourceSelector(provider?.jwksSources)}

View File

@ -258,6 +258,20 @@ export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> {
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Authorization flow")}
required
name="authorizationFlow"
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${this.instance?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<div class="pf-c-card pf-m-selectable pf-m-selected">
<div class="pf-c-card__body">${this.renderModeSelector()}</div>
@ -394,7 +408,7 @@ ${this.instance?.skipPathRegex}</textarea
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<span slot="header"> ${msg("Advanced flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Authentication flow")}
@ -411,20 +425,6 @@ ${this.instance?.skipPathRegex}</textarea
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Authorization flow")}
required
name="authorizationFlow"
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${this.instance?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Invalidation flow")}
name="invalidationFlow"
@ -433,6 +433,7 @@ ${this.instance?.skipPathRegex}</textarea
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${this.instance?.invalidationFlow}
defaultFlowSlug="default-provider-invalidation-flow"
required
></ak-flow-search>
<p class="pf-c-form__helper-text">

View File

@ -89,6 +89,20 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Authorization flow")}
required
name="authorizationFlow"
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${this.instance?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
@ -155,7 +169,7 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<span slot="header"> ${msg("Advanced flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Authentication flow")}
@ -172,20 +186,6 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Authorization flow")}
required
name="authorizationFlow"
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${this.instance?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Invalidation flow")}
name="invalidationFlow"
@ -194,6 +194,7 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${this.instance?.invalidationFlow}
defaultFlowSlug="default-provider-invalidation-flow"
required
></ak-flow-search>
<p class="pf-c-form__helper-text">

View File

@ -54,6 +54,7 @@ export class SAMLProviderImportForm extends Form<SAMLProvider> {
>
<ak-flow-search-no-default
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
defaultFlowSlug="default-provider-invalidation-flow"
required
></ak-flow-search-no-default>
<p class="pf-c-form__helper-text">

View File

@ -38,12 +38,15 @@ export async function scimPropertyMappingsProvider(page = 1, search = "") {
};
}
export function makeSCIMPropertyMappingsSelector(instanceMappings: string[] | undefined) {
export function makeSCIMPropertyMappingsSelector(
instanceMappings: string[] | undefined,
defaultSelected: string,
) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, mapping]: DualSelectPair<SCIMMapping>) =>
mapping?.managed === "goauthentik.io/providers/scim/user";
mapping?.managed === defaultSelected;
}
@customElement("ak-provider-scim-form")
@ -189,6 +192,7 @@ export class SCIMProviderFormPage extends BaseProviderForm<SCIMProvider> {
.provider=${scimPropertyMappingsProvider}
.selector=${makeSCIMPropertyMappingsSelector(
this.instance?.propertyMappings,
"goauthentik.io/providers/scim/user",
)}
available-label=${msg("Available User Property Mappings")}
selected-label=${msg("Selected User Property Mappings")}
@ -205,6 +209,7 @@ export class SCIMProviderFormPage extends BaseProviderForm<SCIMProvider> {
.provider=${scimPropertyMappingsProvider}
.selector=${makeSCIMPropertyMappingsSelector(
this.instance?.propertyMappingsGroup,
"goauthentik.io/providers/scim/group",
)}
available-label=${msg("Available Group Property Mappings")}
selected-label=${msg("Selected Group Property Mappings")}

View File

@ -8,7 +8,12 @@ import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ProvidersApi, SCIMProviderGroup, SyncObjectModelEnum } from "@goauthentik/api";
import {
ProvidersApi,
ProvidersScimSyncObjectCreateRequest,
SCIMProviderGroup,
SyncObjectModelEnum,
} from "@goauthentik/api";
@customElement("ak-provider-scim-groups-list")
export class SCIMProviderGroupList extends Table<SCIMProviderGroup> {
@ -29,7 +34,9 @@ export class SCIMProviderGroupList extends Table<SCIMProviderGroup> {
<ak-sync-object-form
.provider=${this.providerId}
model=${SyncObjectModelEnum.Group}
.sync=${new ProvidersApi(DEFAULT_CONFIG).providersScimSyncObjectCreate}
.sync=${(data: ProvidersScimSyncObjectCreateRequest) => {
return new ProvidersApi(DEFAULT_CONFIG).providersScimSyncObjectCreate(data);
}}
slot="form"
>
</ak-sync-object-form>

View File

@ -8,7 +8,12 @@ import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ProvidersApi, SCIMProviderUser, SyncObjectModelEnum } from "@goauthentik/api";
import {
ProvidersApi,
ProvidersScimSyncObjectCreateRequest,
SCIMProviderUser,
SyncObjectModelEnum,
} from "@goauthentik/api";
@customElement("ak-provider-scim-users-list")
export class SCIMProviderUserList extends Table<SCIMProviderUser> {
@ -29,7 +34,9 @@ export class SCIMProviderUserList extends Table<SCIMProviderUser> {
<ak-sync-object-form
.provider=${this.providerId}
model=${SyncObjectModelEnum.User}
.sync=${new ProvidersApi(DEFAULT_CONFIG).providersScimSyncObjectCreate}
.sync=${(data: ProvidersScimSyncObjectCreateRequest) => {
return new ProvidersApi(DEFAULT_CONFIG).providersScimSyncObjectCreate(data);
}}
slot="form"
>
</ak-sync-object-form>

View File

@ -1,4 +1,5 @@
import "@goauthentik/admin/sources/SourceWizard";
import "@goauthentik/admin/sources/kerberos/KerberosSourceForm";
import "@goauthentik/admin/sources/ldap/LDAPSourceForm";
import "@goauthentik/admin/sources/oauth/OAuthSourceForm";
import "@goauthentik/admin/sources/plex/PlexSourceForm";

View File

@ -1,3 +1,4 @@
import "@goauthentik/admin/sources/kerberos/KerberosSourceViewPage";
import "@goauthentik/admin/sources/ldap/LDAPSourceViewPage";
import "@goauthentik/admin/sources/oauth/OAuthSourceViewPage";
import "@goauthentik/admin/sources/plex/PlexSourceViewPage";
@ -36,6 +37,10 @@ export class SourceViewPage extends AKElement {
return html`<ak-empty-state ?loading=${true} ?fullHeight=${true}></ak-empty-state>`;
}
switch (this.source?.component) {
case "ak-source-kerberos-form":
return html`<ak-source-kerberos-view
sourceSlug=${this.source.slug}
></ak-source-kerberos-view>`;
case "ak-source-ldap-form":
return html`<ak-source-ldap-view
sourceSlug=${this.source.slug}

View File

@ -1,3 +1,4 @@
import "@goauthentik/admin/sources/kerberos/KerberosSourceForm";
import "@goauthentik/admin/sources/ldap/LDAPSourceForm";
import "@goauthentik/admin/sources/oauth/OAuthSourceForm";
import "@goauthentik/admin/sources/plex/PlexSourceForm";

View File

@ -0,0 +1,39 @@
import { AKElement } from "@goauthentik/elements/Base";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFList from "@patternfly/patternfly/components/List/list.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
@customElement("ak-source-kerberos-connectivity")
export class KerberosSourceConnectivity extends AKElement {
@property()
connectivity?: {
[key: string]: {
[key: string]: string;
};
};
static get styles(): CSSResult[] {
return [PFBase, PFList];
}
render(): TemplateResult {
if (!this.connectivity) {
return html``;
}
return html`<ul class="pf-c-list">
${Object.keys(this.connectivity).map((serverKey) => {
return html`<li>${serverKey}: ${this.connectivity![serverKey]}</li>`;
})}
</ul>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-source-kerberos-connectivity": KerberosSourceConnectivity;
}
}

View File

@ -0,0 +1,456 @@
import "@goauthentik/admin/common/ak-flow-search/ak-source-flow-search";
import { iconHelperText, placeholderHelperText } from "@goauthentik/admin/helperText";
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
import {
GroupMatchingModeToLabel,
UserMatchingModeToLabel,
} from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input";
import {
CapabilitiesEnum,
WithCapabilitiesConfig,
} from "@goauthentik/elements/Interface/capabilitiesProvider";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
FlowsInstancesListDesignationEnum,
GroupMatchingModeEnum,
KerberosSource,
KerberosSourcePropertyMapping,
KerberosSourceRequest,
PropertymappingsApi,
SourcesApi,
UserMatchingModeEnum,
} from "@goauthentik/api";
async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsSourceKerberosList({
ordering: "managed",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]),
};
}
function makePropertyMappingsSelector(object: string, instanceMappings?: string[]) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, mapping]: DualSelectPair<KerberosSourcePropertyMapping>) =>
object == "user" &&
mapping?.managed?.startsWith("goauthentik.io/sources/kerberos/user/default/");
}
@customElement("ak-source-kerberos-form")
export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<KerberosSource>) {
async loadInstance(pk: string): Promise<KerberosSource> {
const source = await new SourcesApi(DEFAULT_CONFIG).sourcesKerberosRetrieve({
slug: pk,
});
this.clearIcon = false;
return source;
}
@state()
clearIcon = false;
async send(data: KerberosSource): Promise<KerberosSource> {
let source: KerberosSource;
if (this.instance) {
source = await new SourcesApi(DEFAULT_CONFIG).sourcesKerberosPartialUpdate({
slug: this.instance.slug,
patchedKerberosSourceRequest: data,
});
} else {
source = await new SourcesApi(DEFAULT_CONFIG).sourcesKerberosCreate({
kerberosSourceRequest: data as unknown as KerberosSourceRequest,
});
}
const c = await config();
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
const icon = this.getFormFiles()["icon"];
if (icon || this.clearIcon) {
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
slug: source.slug,
file: icon,
clear: this.clearIcon,
});
}
} else {
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconUrlCreate({
slug: source.slug,
filePathRequest: {
url: data.icon || "",
},
});
}
return source;
}
renderForm(): TemplateResult {
return html` <ak-text-input
name="name"
label=${msg("Name")}
value=${ifDefined(this.instance?.name)}
required
></ak-text-input>
<ak-text-input
name="slug"
label=${msg("Slug")}
value=${ifDefined(this.instance?.slug)}
required
></ak-text-input>
<ak-switch-input
name="enabled"
?checked=${first(this.instance?.enabled, true)}
label=${msg("Enabled")}
></ak-switch-input>
<ak-switch-input
name="passwordLoginUpdateInternalPassword"
?checked=${first(this.instance?.passwordLoginUpdateInternalPassword, false)}
label=${msg("Update internal password on login")}
help=${msg(
"When the user logs in to authentik using this source password backend, update their credentials in authentik.",
)}
></ak-switch-input>
<ak-switch-input
name="syncUsers"
?checked=${first(this.instance?.syncUsers, true)}
label=${msg("Sync users")}
></ak-switch-input>
<ak-switch-input
name="syncUsersPassword"
?checked=${first(this.instance?.syncUsersPassword, true)}
label=${msg("User password writeback")}
help=${msg(
"Enable this option to write password changes made in authentik back to Kerberos. Ignored if sync is disabled.",
)}
></ak-switch-input>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Realm settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="realm"
label=${msg("Realm")}
value=${ifDefined(this.instance?.realm)}
placeholder="AUTHENTIK.COMPANY"
required
></ak-text-input>
<ak-textarea-input
name="krb5Conf"
label=${msg("Kerberos 5 configuration")}
value=${ifDefined(this.instance?.krb5Conf)}
help=${msg(
"Kerberos 5 configuration. See man krb5.conf(5) for configuration format. If left empty, a default krb5.conf will be used.",
)}
></ak-textarea-input>
<ak-form-element-horizontal
label=${msg("User matching mode")}
?required=${true}
name="userMatchingMode"
>
<select class="pf-c-form-control">
<option
value=${UserMatchingModeEnum.Identifier}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.Identifier}
>
${UserMatchingModeToLabel(UserMatchingModeEnum.Identifier)}
</option>
<option
value=${UserMatchingModeEnum.EmailLink}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.EmailLink}
>
${UserMatchingModeToLabel(UserMatchingModeEnum.EmailLink)}
</option>
<option
value=${UserMatchingModeEnum.EmailDeny}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.EmailDeny}
>
${UserMatchingModeToLabel(UserMatchingModeEnum.EmailDeny)}
</option>
<option
value=${UserMatchingModeEnum.UsernameLink}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.UsernameLink}
>
${UserMatchingModeToLabel(UserMatchingModeEnum.UsernameLink)}
</option>
<option
value=${UserMatchingModeEnum.UsernameDeny}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.UsernameDeny}
>
${UserMatchingModeToLabel(UserMatchingModeEnum.UsernameDeny)}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Group matching mode")}
?required=${true}
name="groupMatchingMode"
>
<select class="pf-c-form-control">
<option
value=${GroupMatchingModeEnum.Identifier}
?selected=${this.instance?.groupMatchingMode ===
GroupMatchingModeEnum.Identifier}
>
${UserMatchingModeToLabel(UserMatchingModeEnum.Identifier)}
</option>
<option
value=${GroupMatchingModeEnum.NameLink}
?selected=${this.instance?.groupMatchingMode ===
GroupMatchingModeEnum.NameLink}
>
${GroupMatchingModeToLabel(GroupMatchingModeEnum.NameLink)}
</option>
<option
value=${GroupMatchingModeEnum.NameDeny}
?selected=${this.instance?.groupMatchingMode ===
GroupMatchingModeEnum.NameDeny}
>
${GroupMatchingModeToLabel(GroupMatchingModeEnum.NameDeny)}
</option>
</select>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group .expanded=${false}>
<span slot="header"> ${msg("Sync connection settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="syncPrincipal"
label=${msg("Sync principal")}
value=${ifDefined(this.instance?.syncPrincipal)}
help=${msg("Principal used to authenticate to the KDC for syncing.")}
></ak-text-input>
<ak-form-element-horizontal
name="syncPassword"
label=${msg("Sync password")}
?writeOnly=${this.instance !== undefined}
>
<input type="text" value="" class="pf-c-form-control" />
<p class="pf-c-form__helper-text">
${msg(
"Password used to authenticate to the KDC for syncing. Optional if Sync keytab or Sync credentials cache is provided.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
name="syncKeytab"
label=${msg("Sync keytab")}
?writeOnly=${this.instance !== undefined}
>
<textarea class="pf-c-form-control"></textarea>
<p class="pf-c-form__helper-text">
${msg(
"Keytab used to authenticate to the KDC for syncing. Optional if Sync password or Sync credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.",
)}
</p>
</ak-form-element-horizontal>
<ak-text-input
name="syncCcache"
label=${msg("Sync credentials cache")}
value=${ifDefined(this.instance?.syncCcache)}
help=${msg(
"Credentials cache used to authenticate to the KDC for syncing. Optional if Sync password or Sync keytab is provided. Must be in the form TYPE:residual.",
)}
></ak-text-input>
</div>
</ak-form-group>
<ak-form-group .expanded=${false}>
<span slot="header"> ${msg("SPNEGO settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="spnegoServerName"
label=${msg("SPNEGO server name")}
value=${ifDefined(this.instance?.spnegoServerName)}
help=${msg(
"Force the use of a specific server name for SPNEGO. Must be in the form HTTP@domain",
)}
></ak-text-input>
<ak-form-element-horizontal
name="spnegoKeytab"
label=${msg("SPNEGO keytab")}
?writeOnly=${this.instance !== undefined}
>
<textarea class="pf-c-form-control"></textarea>
<p class="pf-c-form__helper-text">
${msg(
"Keytab used for SPNEGO. Optional if SPNEGO credentials cache is provided. Must be base64 encoded or in the form TYPE:residual.",
)}
</p>
</ak-form-element-horizontal>
<ak-text-input
name="spnegoCcache"
label=${msg("SPNEGO credentials cache")}
value=${ifDefined(this.instance?.spnegoCcache)}
help=${msg(
"Credentials cache used for SPNEGO. Optional if SPNEGO keytab is provided. Must be in the form TYPE:residual.",
)}
></ak-text-input>
</div>
</ak-form-group>
<ak-form-group ?expanded=${false}>
<span slot="header"> ${msg("Kerberos Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="userPropertyMappings"
>
<ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider}
.selector=${makePropertyMappingsSelector(
"user",
this.instance?.userPropertyMappings,
)}
available-label="${msg("Available User Property Mappings")}"
selected-label="${msg("Selected User Property Mappings")}"
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text">
${msg("Property mappings for user creation.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Group Property Mappings")}
name="groupPropertyMappings"
>
<ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider}
.selector=${makePropertyMappingsSelector(
"group",
this.instance?.groupPropertyMappings,
)}
available-label="${msg("Available Group Property Mappings")}"
selected-label="${msg("Selected Group Property Mappings")}"
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text">
${msg("Property mappings for group creation.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Authentication flow")}
name="authenticationFlow"
>
<ak-source-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${this.instance?.authenticationFlow}
.instanceId=${this.instance?.pk}
fallback="default-source-authentication"
></ak-source-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow to use when authenticating existing users.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Enrollment flow")}
name="enrollmentFlow"
>
<ak-source-flow-search
flowType=${FlowsInstancesListDesignationEnum.Enrollment}
.currentFlow=${this.instance?.enrollmentFlow}
.instanceId=${this.instance?.pk}
fallback="default-source-enrollment"
></ak-source-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow to use when enrolling new users.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Additional settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="userPathTemplate"
label=${msg("User path")}
value=${first(
this.instance?.userPathTemplate,
"goauthentik.io/sources/%(slug)s",
)}
help=${placeholderHelperText}
></ak-text-input>
</div>
${this.can(CapabilitiesEnum.CanSaveMedia)
? html`<ak-form-element-horizontal label=${msg("Icon")} name="icon">
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.icon
? html`
<p class="pf-c-form__helper-text">
${msg("Currently set to:")} ${this.instance?.icon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.icon
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label">
${msg("Clear icon")}
</span>
</label>
<p class="pf-c-form__helper-text">
${msg("Delete currently set icon.")}
</p>
</ak-form-element-horizontal>
`
: html``}`
: html`<ak-form-element-horizontal label=${msg("Icon")} name="icon">
<input
type="text"
value="${first(this.instance?.icon, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">${iconHelperText}</p>
</ak-form-element-horizontal>`}
</ak-form-group>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-source-kerberos-form": KerberosSourceForm;
}
}

View File

@ -0,0 +1,213 @@
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
import "@goauthentik/admin/sources/kerberos/KerberosSourceConnectivity";
import "@goauthentik/admin/sources/kerberos/KerberosSourceForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import "@goauthentik/components/events/ObjectChangelog";
import MDSourceKerberosBrowser from "@goauthentik/docs/users-sources/sources/protocols/kerberos/browser.md";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/Markdown";
import "@goauthentik/elements/SyncStatusCard";
import "@goauthentik/elements/Tabs";
import "@goauthentik/elements/buttons/ActionButton";
import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/forms/ModalForm";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import PFList from "@patternfly/patternfly/components/List/list.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import {
KerberosSource,
RbacPermissionsAssignedByUsersListModelEnum,
SourcesApi,
SyncStatus,
} from "@goauthentik/api";
@customElement("ak-source-kerberos-view")
export class KerberosSourceViewPage extends AKElement {
@property({ type: String })
set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG)
.sourcesKerberosRetrieve({
slug: slug,
})
.then((source) => {
this.source = source;
});
}
@property({ attribute: false })
source!: KerberosSource;
@state()
syncState?: SyncStatus;
static get styles(): CSSResult[] {
return [PFBase, PFPage, PFButton, PFGrid, PFContent, PFCard, PFDescriptionList, PFList];
}
constructor() {
super();
this.addEventListener(EVENT_REFRESH, () => {
if (!this.source?.slug) return;
this.sourceSlug = this.source?.slug;
});
}
load(): void {
new SourcesApi(DEFAULT_CONFIG)
.sourcesKerberosSyncStatusRetrieve({
slug: this.source.slug,
})
.then((state) => {
this.syncState = state;
});
}
renderSyncCards(): TemplateResult {
if (!this.source.syncUsers) {
return html``;
}
return html`
<div class="pf-c-card pf-l-grid__item pf-m-2-col">
<div class="pf-c-card__title">
<p>${msg("Connectivity")}</p>
</div>
<div class="pf-c-card__body">
<ak-source-kerberos-connectivity
.connectivity=${this.source.connectivity}
></ak-source-kerberos-connectivity>
</div>
</div>
<div class="pf-l-grid__item pf-m-10-col">
<ak-sync-status-card
.fetch=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesKerberosSyncStatusRetrieve({
slug: this.source?.slug,
});
}}
.triggerSync=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesKerberosPartialUpdate({
slug: this.source?.slug || "",
patchedKerberosSourceRequest: {},
});
}}
></ak-sync-status-card>
</div>
`;
}
render(): TemplateResult {
if (!this.source) {
return html``;
}
return html`<ak-tabs>
<section
slot="page-overview"
data-tab-title="${msg("Overview")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
@activate=${() => {
this.load();
}}
>
<div class="pf-l-grid pf-m-gutter">
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("Name")}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.name}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("Realm")}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.realm}
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${msg("Update")} </span>
<span slot="header"> ${msg("Update Kerberos Source")} </span>
<ak-source-kerberos-form
slot="form"
.instancePk=${this.source.slug}
>
</ak-source-kerberos-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${msg("Edit")}
</button>
</ak-forms-modal>
</div>
</div>
${this.renderSyncCards()}
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
<div class="pf-c-card__body">
<ak-markdown
.md=${MDSourceKerberosBrowser}
meta="users-sources/protocols/kerberos/browser.md"
;
></ak-markdown>
</div>
</div>
</div>
</section>
<section
slot="page-changelog"
data-tab-title="${msg("Changelog")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-l-grid pf-m-gutter">
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.source.pk || ""}
targetModelApp="authentik_sources_kerberos"
targetModelName="kerberossource"
>
</ak-object-changelog>
</div>
</div>
</div>
</section>
<ak-rbac-object-permission-page
slot="page-permissions"
data-tab-title="${msg("Permissions")}"
model=${RbacPermissionsAssignedByUsersListModelEnum.SourcesKerberosKerberossource}
objectPk=${this.source.pk}
></ak-rbac-object-permission-page>
</ak-tabs>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-source-kerberos-view": KerberosSourceViewPage;
}
}

View File

@ -2,6 +2,7 @@ import "@goauthentik/admin/rbac/ObjectPermissionModal";
import "@goauthentik/admin/stages/StageWizard";
import "@goauthentik/admin/stages/authenticator_duo/AuthenticatorDuoStageForm";
import "@goauthentik/admin/stages/authenticator_duo/DuoDeviceImportForm";
import "@goauthentik/admin/stages/authenticator_endpoint_gdtc/AuthenticatorEndpointGDTCStageForm";
import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm";
import "@goauthentik/admin/stages/authenticator_static/AuthenticatorStaticStageForm";
import "@goauthentik/admin/stages/authenticator_totp/AuthenticatorTOTPStageForm";
@ -25,8 +26,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/forms/DeleteBulkForm";
import "@goauthentik/elements/forms/ModalForm";
import "@goauthentik/elements/forms/ProxyForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { TableColumn } from "@goauthentik/elements/table/Table";
import { PaginatedResponse, TableColumn } from "@goauthentik/elements/table/Table";
import { TablePage } from "@goauthentik/elements/table/TablePage";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";

View File

@ -0,0 +1,75 @@
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { AuthenticatorEndpointGDTCStage, StagesApi } from "@goauthentik/api";
@customElement("ak-stage-authenticator-endpoint-gdtc-form")
export class AuthenticatorEndpointGDTCStageForm extends BaseStageForm<AuthenticatorEndpointGDTCStage> {
loadInstance(pk: string): Promise<AuthenticatorEndpointGDTCStage> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorEndpointGdtcRetrieve({
stageUuid: pk,
});
}
async send(data: AuthenticatorEndpointGDTCStage): Promise<AuthenticatorEndpointGDTCStage> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorEndpointGdtcPartialUpdate({
stageUuid: this.instance.pk || "",
patchedAuthenticatorEndpointGDTCStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorEndpointGdtcCreate({
authenticatorEndpointGDTCStageRequest: data,
});
}
}
renderForm(): TemplateResult {
return html` <span>
${msg(
"Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${first(this.instance?.name, "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group expanded>
<span slot="header"> ${msg("Google Verified Access API")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Credentials")}
required
name="credentials"
>
<ak-codemirror
mode=${CodeMirrorMode.JavaScript}
.value="${first(this.instance?.credentials, {})}"
></ak-codemirror>
<p class="pf-c-form__helper-text">
${msg("Google Cloud credentials file.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-stage-authenticator-endpoint-gdtc-form": AuthenticatorEndpointGDTCStageForm;
}
}

View File

@ -66,6 +66,10 @@ export class PasswordStageForm extends BaseStageForm<PasswordStage> {
name: BackendsEnum.SourcesLdapAuthLdapBackend,
label: msg("User database + LDAP password"),
},
{
name: BackendsEnum.SourcesKerberosAuthKerberosBackend,
label: msg("User database + Kerberos password"),
},
];
return html` <span>

View File

@ -1,11 +1,12 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { deviceTypeName } from "@goauthentik/common/labels";
import { getRelativeTime } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/DeleteBulkForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
import { msg } from "@lit/localize";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
@ -54,20 +55,21 @@ export class UserDeviceTable extends Table<Device> {
async deleteWrapper(device: Device) {
const api = new AuthenticatorsApi(DEFAULT_CONFIG);
const id = { id: device.pk };
switch (device.type) {
case "authentik_stages_authenticator_duo.DuoDevice":
return api.authenticatorsAdminDuoDestroy(id);
return api.authenticatorsAdminDuoDestroy({ id: parseInt(device.pk, 10) });
case "authentik_stages_authenticator_sms.SMSDevice":
return api.authenticatorsAdminSmsDestroy(id);
return api.authenticatorsAdminSmsDestroy({ id: parseInt(device.pk, 10) });
case "authentik_stages_authenticator_totp.TOTPDevice":
return api.authenticatorsAdminTotpDestroy(id);
return api.authenticatorsAdminTotpDestroy({ id: parseInt(device.pk, 10) });
case "authentik_stages_authenticator_static.StaticDevice":
return api.authenticatorsAdminStaticDestroy(id);
return api.authenticatorsAdminStaticDestroy({ id: parseInt(device.pk, 10) });
case "authentik_stages_authenticator_webauthn.WebAuthnDevice":
return api.authenticatorsAdminWebauthnDestroy(id);
return api.authenticatorsAdminWebauthnDestroy({ id: parseInt(device.pk, 10) });
default:
break;
throw new SentryIgnoredError(
msg(str`Device type ${device.verboseName} cannot be deleted`),
);
}
}
@ -103,10 +105,14 @@ export class UserDeviceTable extends Table<Device> {
html`${deviceTypeName(item)}
${item.extraDescription ? ` - ${item.extraDescription}` : ""}`,
html`${item.confirmed ? msg("Yes") : msg("No")}`,
html`<div>${getRelativeTime(item.created)}</div>
<small>${item.created.toLocaleString()}</small>`,
html`<div>${getRelativeTime(item.lastUpdated)}</div>
<small>${item.lastUpdated.toLocaleString()}</small>`,
html`${item.created.getTime() > 0
? html`<div>${getRelativeTime(item.created)}</div>
<small>${item.created.toLocaleString()}</small>`
: html`-`}`,
html`${item.lastUpdated
? html`<div>${getRelativeTime(item.lastUpdated)}</div>
<small>${item.lastUpdated.toLocaleString()}</small>`
: html`-`}`,
html`${item.lastUsed
? html`<div>${getRelativeTime(item.lastUsed)}</div>
<small>${item.lastUsed.toLocaleString()}</small>`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 882 KiB

After

Width:  |  Height:  |  Size: 671 KiB

21
web/src/common/purify.ts Normal file
View File

@ -0,0 +1,21 @@
import DOMPurify from "dompurify";
import { render } from "@lit-labs/ssr";
import { collectResult } from "@lit-labs/ssr/lib/render-result.js";
import { TemplateResult, html } from "lit";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { until } from "lit/directives/until.js";
export const DOM_PURIFY_STRICT: DOMPurify.Config = {
ALLOWED_TAGS: ["#text"],
};
export function purify(input: TemplateResult): TemplateResult {
return html`${until(
(async () => {
const rendered = await collectResult(render(input));
const purified = DOMPurify.sanitize(rendered);
return html`${unsafeHTML(purified)}`;
})(),
)}`;
}

View File

@ -1,4 +1,5 @@
import { EVENT_REFRESH, EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
import { DOM_PURIFY_STRICT } from "@goauthentik/common/purify";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/EmptyState";
import mermaid, { MermaidConfig } from "mermaid";
@ -47,6 +48,8 @@ export class Diagram extends AKElement {
curve: "linear",
},
htmlLabels: false,
securityLevel: "strict",
dompurifyConfig: DOM_PURIFY_STRICT,
};
mermaid.initialize(this.config);
}

View File

@ -86,8 +86,8 @@ export class RacInterface extends Interface {
static domSize(): { width: number; height: number } {
const size = document.body.getBoundingClientRect();
return {
width: size.width * window.devicePixelRatio,
height: size.height * window.devicePixelRatio,
width: size.width,
height: size.height,
};
}
@ -175,7 +175,6 @@ export class RacInterface extends Interface {
const params = new URLSearchParams();
params.set("screen_width", Math.floor(RacInterface.domSize().width).toString());
params.set("screen_height", Math.floor(RacInterface.domSize().height).toString());
params.set("screen_dpi", (window.devicePixelRatio * 96).toString());
this.client.connect(params.toString());
}

View File

@ -5,6 +5,7 @@ import {
TITLE_DEFAULT,
} from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global";
import { purify } from "@goauthentik/common/purify";
import { configureSentry } from "@goauthentik/common/sentry";
import { first } from "@goauthentik/common/utils";
import { WebsocketClient } from "@goauthentik/common/ws";
@ -16,6 +17,7 @@ import { themeImage } from "@goauthentik/elements/utils/images";
import "@goauthentik/flow/sources/apple/AppleLoginInit";
import "@goauthentik/flow/sources/plex/PlexLoginInit";
import "@goauthentik/flow/stages/FlowErrorStage";
import "@goauthentik/flow/stages/FlowFrameStage";
import "@goauthentik/flow/stages/RedirectStage";
import { StageHost, SubmitOptions } from "@goauthentik/flow/stages/base";
@ -170,6 +172,19 @@ export class FlowExecutor extends Interface implements StageHost {
this.addEventListener(EVENT_FLOW_INSPECTOR_TOGGLE, () => {
this.inspectorOpen = !this.inspectorOpen;
});
window.addEventListener("message", (event) => {
const msg: {
source?: string;
context?: string;
message: string;
} = event.data;
if (msg.source !== "goauthentik.io" || msg.context !== "flow-executor") {
return;
}
if (msg.message === "submit") {
this.submit({} as FlowChallengeResponseRequest);
}
});
}
async getTheme(): Promise<UiThemeEnum> {
@ -429,6 +444,11 @@ export class FlowExecutor extends Interface implements StageHost {
</ak-stage-redirect>`;
case "xak-flow-shell":
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
case "xak-flow-frame":
return html`<xak-flow-frame
.host=${this as StageHost}
.challenge=${this.challenge}
></xak-flow-frame>`;
default:
return html`Invalid native challenge element`;
}
@ -499,9 +519,13 @@ export class FlowExecutor extends Interface implements StageHost {
<ul class="pf-c-list pf-m-inline">
${this.brand?.uiFooterLinks?.map((link) => {
if (link.href) {
return html`<li>
<a href="${link.href}">${link.name}</a>
</li>`;
return html`${purify(
html`<li>
<a href="${link.href}"
>${link.name}</a
>
</li>`,
)}`;
}
return html`<li>
<span>${link.name}</span>

View File

@ -114,7 +114,7 @@ export class InputPassword extends AKElement {
this.input.type = "password";
this.input.name = this.name;
this.input.placeholder = this.placeholder;
this.input.autofocus = true;
this.input.autofocus = this.grabFocus;
this.input.autocomplete = "current-password";
this.input.classList.add("pf-c-form-control");
this.input.required = true;

View File

@ -0,0 +1,54 @@
import "@goauthentik/elements/EmptyState";
import "@goauthentik/flow/FormStatic";
import { BaseStage } from "@goauthentik/flow/stages/base";
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement } from "lit/decorators.js";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { FrameChallenge, FrameChallengeResponseRequest } from "@goauthentik/api";
@customElement("xak-flow-frame")
export class FlowFrameStage extends BaseStage<FrameChallenge, FrameChallengeResponseRequest> {
static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, css``];
}
render(): TemplateResult {
if (!this.challenge) {
return html`<ak-empty-state loading> </ak-empty-state>`;
}
return html` <header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
</header>
<div class="pf-c-login__main-body">
${this.challenge.loadingOverlay
? html`<ak-empty-state
loading
header=${this.challenge.loadingText ?? undefined}
>
</ak-empty-state>`
: nothing}
<iframe
style=${this.challenge.loadingOverlay
? "width:0;height:0;position:absolute;"
: ""}
src=${this.challenge.url}
></iframe>
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links"></ul>
</footer>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"xak-flow-frame": FlowFrameStage;
}
}

View File

@ -43,8 +43,17 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
firstUpdated(): void {
if (this.promptUser) {
document.addEventListener("keydown", (ev) => {
if (ev.key === "Enter") {
this.redirect();
}
});
return;
}
this.redirect();
}
redirect() {
console.debug(
"authentik/stages/redirect: redirecting to url from server",
this.challenge.to,

View File

@ -1,8 +1,9 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { msg } from "@lit/localize";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@ -10,11 +11,11 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { AuthenticatorsApi, Device } from "@goauthentik/api";
@customElement("ak-user-mfa-form")
export class MFADeviceForm extends ModelForm<Device, number> {
export class MFADeviceForm extends ModelForm<Device, string> {
@property()
deviceType!: string;
async loadInstance(pk: number): Promise<Device> {
async loadInstance(pk: string): Promise<Device> {
const devices = await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsAllList();
return devices.filter((device) => {
return device.pk === pk && device.type === this.deviceType;
@ -29,36 +30,38 @@ export class MFADeviceForm extends ModelForm<Device, number> {
switch (this.instance?.type) {
case "authentik_stages_authenticator_duo.DuoDevice":
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsDuoUpdate({
id: this.instance?.pk,
id: parseInt(this.instance?.pk, 10),
duoDeviceRequest: device,
});
break;
case "authentik_stages_authenticator_sms.SMSDevice":
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsSmsUpdate({
id: this.instance?.pk,
id: parseInt(this.instance?.pk, 10),
sMSDeviceRequest: device,
});
break;
case "authentik_stages_authenticator_totp.TOTPDevice":
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpUpdate({
id: this.instance?.pk,
id: parseInt(this.instance?.pk, 10),
tOTPDeviceRequest: device,
});
break;
case "authentik_stages_authenticator_static.StaticDevice":
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticUpdate({
id: this.instance?.pk,
id: parseInt(this.instance?.pk, 10),
staticDeviceRequest: device,
});
break;
case "authentik_stages_authenticator_webauthn.WebAuthnDevice":
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsWebauthnUpdate({
id: this.instance?.pk,
id: parseInt(this.instance?.pk, 10),
webAuthnDeviceRequest: device,
});
break;
default:
break;
throw new SentryIgnoredError(
msg(str`Device type ${device.verboseName} cannot be edited`),
);
}
return device;
}

View File

@ -1,4 +1,5 @@
import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { deviceTypeName } from "@goauthentik/common/labels";
import { getRelativeTime } from "@goauthentik/common/utils";
import "@goauthentik/elements/buttons/Dropdown";
@ -10,7 +11,7 @@ import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/tab
import "@goauthentik/user/user-settings/mfa/MFADeviceForm";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg } from "@lit/localize";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@ -89,7 +90,7 @@ export class MFADevicesPage extends Table<Device> {
async deleteWrapper(device: Device) {
const api = new AuthenticatorsApi(DEFAULT_CONFIG);
const id = { id: device.pk };
const id = { id: parseInt(device.pk, 10) };
switch (device.type) {
case "authentik_stages_authenticator_duo.DuoDevice":
return api.authenticatorsDuoDestroy(id);
@ -102,7 +103,9 @@ export class MFADevicesPage extends Table<Device> {
case "authentik_stages_authenticator_webauthn.WebAuthnDevice":
return api.authenticatorsWebauthnDestroy(id);
default:
break;
throw new SentryIgnoredError(
msg(str`Device type ${device.verboseName} cannot be deleted`),
);
}
}

View File

@ -44,10 +44,7 @@ describe("Configure Oauth2 Providers", () => {
[setTypeCreate, "selectProviderType", "OAuth2/OpenID Provider"],
[clickButton, "Next"],
[setTextInput, "name", newProviderName],
[setFormGroup, /Flow settings/, "open"],
[setSearchSelect, "authenticationFlow", "default-authentication-flow"],
[setSearchSelect, "authorizationFlow", "default-provider-authorization-explicit-consent"],
[setSearchSelect, "invalidationFlow", "default-invalidation-flow"],
]);
await ProviderWizardView.pause();
@ -77,3 +74,23 @@ describe("Configure LDAP Providers", () => {
await ProviderWizardView.nextButton.click();
});
});
describe("Configure Radius Providers", () => {
it("Should configure a simple Radius Provider", async () => {
const newProviderName = `New Radius Provider - ${randomId()}`;
await reachTheProvider();
await $("ak-wizard-page-type-create").waitForDisplayed();
// prettier-ignore
await fillOutFields([
[setTypeCreate, "selectProviderType", "Radius Provider"],
[clickButton, "Next"],
[setTextInput, "name", newProviderName],
[setSearchSelect, "authorizationFlow", "default-authentication-flow"],
]);
await ProviderWizardView.pause();
await ProviderWizardView.nextButton.click();
});
});

View File

@ -6922,6 +6922,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -7187,6 +7187,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -6839,6 +6839,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -9120,6 +9120,26 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
<target>Se reconnecter à <x id="0" equiv-text="${this.challenge.applicationName}"/></target>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
<target>Clé de chiffrement</target>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
<target>Clé utilisée pour chiffrer les jetons.</target>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -8756,6 +8756,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -8602,6 +8602,24 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -9021,6 +9021,24 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -9061,4 +9061,22 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body></file></xliff>

View File

@ -9084,6 +9084,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -6832,6 +6832,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -5770,6 +5770,24 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -9093,27 +9093,55 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s0159c800f16ee1a5">
<source>Flow used when logging out of this provider.</source>
<target>登出此提供程序时使用的流程。</target>
</trans-unit>
<trans-unit id="s821d70ef9496e6f7">
<source>Unbind flow</source>
<target>取消绑定流程</target>
</trans-unit>
<trans-unit id="s6773ec2a233bbf41">
<source>Flow used for unbinding users.</source>
<target>用于取消绑定用户的流程。</target>
</trans-unit>
<trans-unit id="s99060120f626df5b">
<source>Verify SCIM server's certificates</source>
<target>验证 SCIM 服务器证书</target>
</trans-unit>
<trans-unit id="sac88482c48453fc8">
<source>You've logged out of <x id="0" equiv-text="${this.challenge.applicationName}"/>. You can go back to the overview to launch another application, or log out of your authentik account.</source>
<target>您已成功登出 <x id="0" equiv-text="${this.challenge.applicationName}"/>。现在您可以返回总览页来启动其他应用,或者登出您的 authentik 账户。</target>
</trans-unit>
<trans-unit id="s3108167b562674e2">
<source>Go back to overview</source>
<target>返回总览</target>
</trans-unit>
<trans-unit id="sdb749e793de55478">
<source>Log out of <x id="0" equiv-text="${this.challenge.brandName}"/></source>
<target>登出 <x id="0" equiv-text="${this.challenge.brandName}"/></target>
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
<target>重新登录 <x id="0" equiv-text="${this.challenge.applicationName}"/></target>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
<target>加密密钥</target>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
<target>用于加密令牌的密钥。</target>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -6880,6 +6880,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>

View File

@ -836,7 +836,7 @@
</trans-unit>
<trans-unit id="sff69c1a637f899a6">
<source>Bind flow</source>
<target>Bind 流程</target>
<target>绑定流程</target>
</trans-unit>
<trans-unit id="s319040353f479853">
@ -1918,12 +1918,12 @@
</trans-unit>
<trans-unit id="sb7794c2910b1a9ec">
<source>Bind DN</source>
<target>Bind DN</target>
<target>绑定 DN</target>
</trans-unit>
<trans-unit id="s5694f9421c428227">
<source>Bind Password</source>
<target>Bind 密码</target>
<target>绑定密码</target>
</trans-unit>
<trans-unit id="s086e1bbe7c97ea16">
@ -2847,7 +2847,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="sb7684e2910a33a1f">
<source>Bind CN</source>
<target>Bind CN</target>
<target>绑定 CN</target>
</trans-unit>
<trans-unit id="s3de6db803012016a">
@ -8731,10 +8731,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>Search returned no results.</source>
<target>搜索未返回结果。</target>
</trans-unit>
<trans-unit id="s11ec812e25ceef8a">
<source>No messages found</source>
<target>未找到消息</target>
</trans-unit>
<trans-unit id="s23446284b56ca0cc">
<source>Reputation score(s)</source>
<target>信誉分数</target>
@ -9094,6 +9090,46 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s25bacc19d98b444e">
<source>Parent Group</source>
<target>父组</target>
</trans-unit>
<trans-unit id="s0159c800f16ee1a5">
<source>Flow used when logging out of this provider.</source>
<target>登出此提供程序时使用的流程。</target>
</trans-unit>
<trans-unit id="s821d70ef9496e6f7">
<source>Unbind flow</source>
<target>取消绑定流程</target>
</trans-unit>
<trans-unit id="s6773ec2a233bbf41">
<source>Flow used for unbinding users.</source>
<target>用于取消绑定用户的流程。</target>
</trans-unit>
<trans-unit id="s99060120f626df5b">
<source>Verify SCIM server's certificates</source>
<target>验证 SCIM 服务器证书</target>
</trans-unit>
<trans-unit id="sac88482c48453fc8">
<source>You've logged out of <x id="0" equiv-text="${this.challenge.applicationName}"/>. You can go back to the overview to launch another application, or log out of your authentik account.</source>
<target>您已成功登出 <x id="0" equiv-text="${this.challenge.applicationName}"/>。现在您可以返回总览页来启动其他应用,或者登出您的 authentik 账户。</target>
</trans-unit>
<trans-unit id="s3108167b562674e2">
<source>Go back to overview</source>
<target>返回总览</target>
</trans-unit>
<trans-unit id="sdb749e793de55478">
<source>Log out of <x id="0" equiv-text="${this.challenge.brandName}"/></source>
<target>登出 <x id="0" equiv-text="${this.challenge.brandName}"/></target>
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
<target>重新登录 <x id="0" equiv-text="${this.challenge.applicationName}"/></target>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
<target>加密密钥</target>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
<target>用于加密令牌的密钥。</target>
</trans-unit>
</body>
</file>

View File

@ -8717,6 +8717,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s521681ed1d5ff814">
<source>Log back into <x id="0" equiv-text="${this.challenge.applicationName}"/></source>
</trans-unit>
<trans-unit id="sc991a35f5e88d1d3">
<source>Encryption Key</source>
</trans-unit>
<trans-unit id="s8a598f7aef81c3bc">
<source>Key used to encrypt the tokens.</source>
</trans-unit>
<trans-unit id="sbfee780fa0a2c83e">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be deleted</source>
</trans-unit>
<trans-unit id="s336936629cdeb3e5">
<source>Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.</source>
</trans-unit>
<trans-unit id="s85fe794c71b4ace8">
<source>Google Verified Access API</source>
</trans-unit>
<trans-unit id="s013620384af7c8b4">
<source>Device type <x id="0" equiv-text="${device.verboseName}"/> cannot be edited</source>
</trans-unit>
</body>
</file>