web: Tactical change. Put all the variants on the second page; it's

a longer list, but it's also easier to manage than all those
required sub-options.
This commit is contained in:
Ken Sternberg 2023-08-07 15:56:12 -07:00
parent 303278964e
commit fe17f116ed
6 changed files with 163 additions and 166 deletions

View File

@ -0,0 +1,35 @@
import { msg } from "@lit/localize";
import type { TypeCreate } from "@goauthentik/api";
type ProviderType = [string, string, string] | [string, string, string, ProviderType[]];
type ProviderOption = TypeCreate & {
children?: TypeCreate[];
};
// prettier-ignore
const _providerTypesTable: ProviderType[] = [
["oauth2provider", msg("OAuth2/OpenID"), msg("Modern applications, APIs and Single-page applications.")],
["ldapprovider", msg("LDAP"), msg("Provide an LDAP interface for applications and users to authenticate against.")],
["proxyprovider-proxy", msg("Transparent Reverse Proxy"), msg("For transparent reverse proxies with required authentication")],
["proxyprovider-forwardsingle", msg("Forward Single Proxy"), msg("For nginx's auth_request or traefix's forwardAuth")],
["radiusprovider", msg("Radius"), msg("Allow applications to authenticate against authentik's users using Radius.")],
["samlprovider-manual", msg("SAML Manual configuration"), msg("Configure SAML provider manually")],
["samlprovider-import", msg("SAML Import Configuration"), msg("Create a SAML provider by importing its metadata")],
["scimprovider", msg("SCIM Provider"), msg("SCIM 2.0 provider to create users and groups in external applications")]
];
function mapProviders([modelName, name, description, children]: ProviderType): ProviderOption {
return {
modelName,
name,
description,
component: "",
...(children ? { children: children.map(mapProviders) } : {}),
};
}
export const providerTypesList = _providerTypesTable.map(mapProviders);
export default providerTypesList;

View File

@ -1,4 +1,3 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
@ -9,73 +8,45 @@ import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { html } from "lit"; import { html } from "lit";
import { state } from "lit/decorators.js";
import { map } from "lit/directives/map.js"; import { map } from "lit/directives/map.js";
import { ProvidersApi } from "@goauthentik/api";
import type { TypeCreate } from "@goauthentik/api"; import type { TypeCreate } from "@goauthentik/api";
import ApplicationWizardPageBase from "./ApplicationWizardPageBase"; import ApplicationWizardPageBase from "./ApplicationWizardPageBase";
import providerTypesList from "./ak-application-wizard-authentication-method-choice.choices";
// The provider description that comes from the server is fairly specific and not internationalized.
// We provide alternative descriptions that use the phrase 'authentication method' instead, and make
// it available to i18n.
//
// prettier-ignore
const alternativeDescription = new Map<string, string>([
["oauth2provider", msg("Modern applications, APIs and Single-page applications.")],
["samlprovider", msg("XML-based SSO standard. Use this if your application only supports SAML.")],
["proxyprovider", msg("Legacy applications which don't natively support SSO.")],
["ldapprovider", msg("Provide an LDAP interface for applications and users to authenticate against.")]
]);
@customElement("ak-application-wizard-authentication-method-choice") @customElement("ak-application-wizard-authentication-method-choice")
export class ApplicationWizardAuthenticationMethodChoice extends ApplicationWizardPageBase { export class ApplicationWizardAuthenticationMethodChoice extends ApplicationWizardPageBase {
@state()
providerTypes: TypeCreate[] = [];
constructor() { constructor() {
super(); super();
this.handleChoice = this.handleChoice.bind(this); this.handleChoice = this.handleChoice.bind(this);
this.renderProvider = this.renderProvider.bind(this); this.renderProvider = this.renderProvider.bind(this);
// If the provider doesn't supply a model to which to send our initialization, the user will
// have to use the older provider path.
new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => {
this.providerTypes = types.filter(({ modelName }) => modelName.trim() !== "");
});
} }
handleChoice(ev: InputEvent) { handleChoice(ev: InputEvent) {
const target = ev.target as HTMLInputElement; const target = ev.target as HTMLInputElement;
this.dispatchWizardUpdate({ providerType: target.value }); this.dispatchWizardUpdate({ providerType: target.value });
} }
renderProvider(type: TypeCreate) { renderProvider(type: TypeCreate) {
const description = alternativeDescription.has(type.modelName)
? alternativeDescription.get(type.modelName)
: type.description;
const label = type.name.replace(/\s+Provider/, "");
return html`<div class="pf-c-radio"> return html`<div class="pf-c-radio">
<input <input
class="pf-c-radio__input" class="pf-c-radio__input"
type="radio" type="radio"
name="type" name="type"
id=${type.component} id="provider-${type.modelName}"
value=${type.modelName} value=${type.modelName}
@change=${this.handleChoice} @change=${this.handleChoice}
/> />
<label class="pf-c-radio__label" for=${type.component}>${label}</label> <label class="pf-c-radio__label" for="provider-${type.modelName}">${type.name}</label>
<span class="pf-c-radio__description">${description}</span> <span class="pf-c-radio__description">${type.description}</span>
</div>`; </div>`;
} }
render() { render() {
return this.providerTypes.length > 0 return providerTypesList.length > 0
? html`<form class="pf-c-form pf-m-horizontal"> ? html`<form class="pf-c-form pf-m-horizontal">
${map(this.providerTypes, this.renderProvider)} ${map(providerTypesList, this.renderProvider)}
</form>` </form>`
: html`<ak-empty-state loading header=${msg("Loading")}></ak-empty-state>`; : html`<ak-empty-state loading header=${msg("Loading")}></ak-empty-state>`;
} }

View File

@ -1,16 +1,15 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement, state } from "@lit/reactive-element/decorators.js";
import { TemplateResult, html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input"; import "@goauthentik/components/ak-textarea-input";
import "@goauthentik/components/ak-toggle-group"; import "@goauthentik/components/ak-toggle-group";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { state } from "@lit/reactive-element/decorators.js";
import { TemplateResult, html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import { import {
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
@ -24,7 +23,8 @@ import {
import ApplicationWizardPageBase from "../ApplicationWizardPageBase"; import ApplicationWizardPageBase from "../ApplicationWizardPageBase";
@customElement("ak-application-wizard-authentication-by-proxy") type MaybeTemplateResult = TemplateResult | typeof nothing;
export class AkTypeProxyApplicationWizardPage extends ApplicationWizardPageBase { export class AkTypeProxyApplicationWizardPage extends ApplicationWizardPageBase {
constructor() { constructor() {
super(); super();
@ -72,6 +72,14 @@ export class AkTypeProxyApplicationWizardPage extends ApplicationWizardPageBase
return this.wizard.provider as ProxyProvider; return this.wizard.provider as ProxyProvider;
} }
renderModeDescription(): MaybeTemplateResult {
return nothing;
}
renderProxyMode() {
return html`<h2>This space intentionally left blank</h2>`;
}
renderHttpBasic(): TemplateResult { renderHttpBasic(): TemplateResult {
return html`<ak-text-input return html`<ak-text-input
name="basicAuthUserAttribute" name="basicAuthUserAttribute"
@ -94,120 +102,9 @@ export class AkTypeProxyApplicationWizardPage extends ApplicationWizardPageBase
</ak-text-input>`; </ak-text-input>`;
} }
renderModeSelector(): TemplateResult {
const setMode = (ev: CustomEvent<{ value: ProxyMode }>) => {
this.mode = ev.detail.value;
};
// prettier-ignore
return html`
<ak-toggle-group value=${this.mode} @ak-toggle=${setMode}>
<option value=${ProxyMode.Proxy}>${msg("Proxy")}</option>
<option value=${ProxyMode.ForwardSingle}>${msg("Forward auth (single application)")}</option>
<option value=${ProxyMode.ForwardDomain}>${msg("Forward auth (domain level)")}</option>
</ak-toggle-group>
`;
}
renderProxyModeProxy() {
return html`<p class="pf-u-mb-xl">
${msg(
"This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.",
)}
</p>
<ak-text-input
name="externalHost"
value=${ifDefined(this.instance?.externalHost)}
required
label=${msg("External host")}
help=${msg(
"The external URL you'll access the application at. Include any non-standard port.",
)}
></ak-text-input>
<ak-text-input
name="internalHost"
value=${ifDefined(this.instance?.internalHost)}
required
label=${msg("Internal host")}
help=${msg("Upstream host that the requests are forwarded to.")}
></ak-text-input>
<ak-switch-input
name="internalHostSslValidation"
?checked=${first(this.instance?.internalHostSslValidation, true)}
label=${msg("Internal host SSL Validation")}
help=${msg("Validate SSL Certificates of upstream servers.")}
>
</ak-switch-input>`;
}
renderProxyModeForwardSingle() {
return html`<p class="pf-u-mb-xl">
${msg(
"Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you).",
)}
</p>
<ak-text-input
name="externalHost"
value=${ifDefined(this.instance?.externalHost)}
required
label=${msg("External host")}
help=${msg(
"The external URL you'll access the application at. Include any non-standard port.",
)}
></ak-text-input>`;
}
renderProxyModeForwardDomain() {
return html`<p class="pf-u-mb-xl">
${msg(
"Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.",
)}
</p>
<div class="pf-u-mb-xl">
${msg("An example setup can look like this:")}
<ul class="pf-c-list">
<li>${msg("authentik running on auth.example.com")}</li>
<li>${msg("app1 running on app1.example.com")}</li>
</ul>
${msg(
"In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com.",
)}
</div>
<ak-text-input
name="externalHost"
value=${first(this.instance?.externalHost, window.location.origin)}
required
label=${msg("Authentication URL")}
help=${msg(
"The external URL you'll authenticate at. The authentik core server should be reachable under this URL.",
)}
></ak-text-input>
<ak-text-input
name="cookieDomain"
value=${ifDefined(this.instance?.cookieDomain)}
required
label=${msg("Cookie domain")}
help=${msg(
"Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.",
)}
></ak-text-input>`;
}
renderSettings() {
switch (this.mode) {
case ProxyMode.Proxy:
return this.renderProxyModeProxy();
case ProxyMode.ForwardSingle:
return this.renderProxyModeForwardSingle();
case ProxyMode.ForwardDomain:
return this.renderProxyModeForwardDomain();
case ProxyMode.UnknownDefaultOpenApi:
return html`<p>${msg("Unknown proxy mode")}</p>`;
}
}
render() { render() {
return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}> return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
${this.renderModeDescription()}
<ak-text-input <ak-text-input
name="name" name="name"
value=${ifDefined(this.instance?.name)} value=${ifDefined(this.instance?.name)}
@ -243,10 +140,8 @@ export class AkTypeProxyApplicationWizardPage extends ApplicationWizardPageBase
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<div class="pf-c-card pf-m-selectable pf-m-selected"> <div class="pf-c-card__footer">${this.renderProxyMode()}</div>
<div class="pf-c-card__body">${this.renderModeSelector()}</div>
<div class="pf-c-card__footer">${this.renderSettings()}</div>
</div>
<ak-text-input <ak-text-input
name="accessTokenValidity" name="accessTokenValidity"
value=${first(this.instance?.accessTokenValidity, "hours=24")} value=${first(this.instance?.accessTokenValidity, "hours=24")}

View File

@ -0,0 +1,49 @@
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators.js";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
@customElement("ak-application-wizard-authentication-for-reverse-proxy")
export class AkReverseProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
renderModeDescription() {
return html`<p class="pf-u-mb-xl">
${msg(
"This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.",
)}
</p>`;
}
renderProxyMode() {
return html` <ak-text-input
name="externalHost"
value=${ifDefined(this.instance?.externalHost)}
required
label=${msg("External host")}
help=${msg(
"The external URL you'll access the application at. Include any non-standard port.",
)}
></ak-text-input>
<ak-text-input
name="internalHost"
value=${ifDefined(this.instance?.internalHost)}
required
label=${msg("Internal host")}
help=${msg("Upstream host that the requests are forwarded to.")}
></ak-text-input>
<ak-switch-input
name="internalHostSslValidation"
?checked=${first(this.instance?.internalHostSslValidation, true)}
label=${msg("Internal host SSL Validation")}
help=${msg("Validate SSL Certificates of upstream servers.")}
>
</ak-switch-input>`;
}
}
export default AkReverseProxyApplicationWizardPage;

View File

@ -0,0 +1,36 @@
import "@goauthentik/components/ak-text-input";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators.js";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
@customElement("ak-application-wizard-authentication-for-single-forward-proxy")
export class AkForwardSingleProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
renderModeDescription() {
return html`<p class="pf-u-mb-xl">
${msg(
html`Use this provider with nginx's <code>auth_request</code> or traefik's
<code>forwardAuth</code>. Each application/domain needs its own provider.
Additionally, on each domain, <code>/outpost.goauthentik.io</code> must be
routed to the outpost (when using a managed outpost, this is done for you).`,
)}
</p>`;
}
renderProxyMode() {
return html`<ak-text-input
name="externalHost"
value=${ifDefined(this.instance?.externalHost)}
required
label=${msg("External host")}
help=${msg(
"The external URL you'll access the application at. Include any non-standard port.",
)}
></ak-text-input>`;
}
}
export default AkForwardSingleProxyApplicationWizardPage;

View File

@ -7,8 +7,9 @@ import AkApplicationWizardApplicationDetails from "../ak-application-wizard-appl
import "../ak-application-wizard-authentication-method-choice"; import "../ak-application-wizard-authentication-method-choice";
import "../ak-application-wizard-context"; import "../ak-application-wizard-context";
import "../ldap/ak-application-wizard-authentication-by-ldap"; import "../ldap/ak-application-wizard-authentication-by-ldap";
import "../proxy/ak-application-wizard-authentication-by-proxy";
import "../oauth/ak-application-wizard-authentication-by-oauth"; import "../oauth/ak-application-wizard-authentication-by-oauth";
import "../proxy/ak-application-wizard-authentication-for-reverse-proxy";
import "../proxy/ak-application-wizard-authentication-for-single-forward-proxy";
import "./ak-application-context-display-for-test"; import "./ak-application-context-display-for-test";
import { import {
dummyAuthenticationFlowsSearch, dummyAuthenticationFlowsSearch,
@ -103,7 +104,7 @@ const container = (testItem: TemplateResult) => {
</div>`; </div>`;
}; };
export const PageOne = () => { export const DescribeApplication = () => {
return container( return container(
html`<ak-application-wizard-context> html`<ak-application-wizard-context>
<ak-application-wizard-application-details></ak-application-wizard-application-details> <ak-application-wizard-application-details></ak-application-wizard-application-details>
@ -113,7 +114,7 @@ export const PageOne = () => {
); );
}; };
export const PageTwo = () => { export const ChooseAuthMethod = () => {
return container( return container(
html`<ak-application-wizard-context> html`<ak-application-wizard-context>
<ak-application-wizard-authentication-method-choice></ak-application-wizard-authentication-method-choice> <ak-application-wizard-authentication-method-choice></ak-application-wizard-authentication-method-choice>
@ -123,7 +124,7 @@ export const PageTwo = () => {
); );
}; };
export const PageThreeLdap = () => { export const ConfigureLdap = () => {
return container( return container(
html`<ak-application-wizard-context> html`<ak-application-wizard-context>
<ak-application-wizard-authentication-by-ldap></ak-application-wizard-authentication-by-ldap> <ak-application-wizard-authentication-by-ldap></ak-application-wizard-authentication-by-ldap>
@ -133,7 +134,7 @@ export const PageThreeLdap = () => {
); );
}; };
export const PageThreeOauth2 = () => { export const ConfigureOauth2 = () => {
return container( return container(
html`<ak-application-wizard-context> html`<ak-application-wizard-context>
<ak-application-wizard-authentication-by-oauth></ak-application-wizard-authentication-by-oauth> <ak-application-wizard-authentication-by-oauth></ak-application-wizard-authentication-by-oauth>
@ -143,10 +144,20 @@ export const PageThreeOauth2 = () => {
); );
}; };
export const PageThreeProxy = () => { export const ConfigureReverseProxy = () => {
return container( return container(
html`<ak-application-wizard-context> html`<ak-application-wizard-context>
<ak-application-wizard-authentication-by-proxy></ak-application-wizard-authentication-by-proxy> <ak-application-wizard-authentication-for-reverse-proxy></ak-application-wizard-authentication-for-reverse-proxy>
<hr />
<ak-application-context-display-for-test></ak-application-context-display-for-test>
</ak-application-wizard-context>`,
);
};
export const ConfigureSingleForwardProxy = () => {
return container(
html`<ak-application-wizard-context>
<ak-application-wizard-authentication-for-single-forward-proxy></ak-application-wizard-authentication-for-single-forward-proxy>
<hr /> <hr />
<ak-application-context-display-for-test></ak-application-context-display-for-test> <ak-application-context-display-for-test></ak-application-context-display-for-test>
</ak-application-wizard-context>`, </ak-application-wizard-context>`,