web: better converter configuration, CSS repair, and forward-domain-proxy

1. Forward Domain Proxy.  I wasn't sure if this method was appropriate for the wizard,
   but Jens says it is.  I've added it.

2. In the process of doing so, I decided that the Provider.converter field was overly
   complexified; I tried too hard to reduce the number of functions I needed to define,
   but in the process outsourced some of the logic of converting the Wizard's dataset
   into a property typed request to the `commit` phase, which was inappropriate.  All
   of the logic about a provider, aside from its display, should be here with the code
   that distinguishes between providers.  This commit makes it so.

3. Small CSS fix: the fields inherited from the Proxy provider forms had some unexpected
   CSS which was causing a bit of a weird indent.  That has been rectified.
This commit is contained in:
Ken Sternberg 2023-10-09 14:56:29 -07:00
parent 478258d88f
commit 889adecad1
6 changed files with 136 additions and 101 deletions

View file

@ -2,7 +2,8 @@ import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import type { ProviderModelEnum as ProviderModelEnumType, TypeCreate } from "@goauthentik/api";
import { ProviderModelEnum } from "@goauthentik/api";
import { ProviderModelEnum, ProxyMode,
} from "@goauthentik/api";
import type {
LDAPProviderRequest,
ModelRequest,
@ -17,10 +18,10 @@ import { OneOfProvider } from "../types";
type ProviderRenderer = () => TemplateResult;
type ProviderType = [string, string, string, ProviderRenderer, ProviderModelEnumType];
type ModelConverter = (provider: OneOfProvider) => ModelRequest;
type ProviderType = [string, string, string, ProviderRenderer, ProviderModelEnumType, ModelConverter];
export type LocalTypeCreate = TypeCreate & {
formName: string;
modelName: ProviderModelEnumType;
@ -35,6 +36,10 @@ const _providerModelsTable: ProviderType[] = [
msg("Modern applications, APIs and Single-page applications."),
() => html`<ak-application-wizard-authentication-by-oauth></ak-application-wizard-authentication-by-oauth>`,
ProviderModelEnum.Oauth2Oauth2provider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.Oauth2Oauth2provider,
...(provider as OAuth2ProviderRequest),
}),
],
[
"ldapprovider",
@ -42,20 +47,49 @@ const _providerModelsTable: ProviderType[] = [
msg("Provide an LDAP interface for applications and users to authenticate against."),
() => html`<ak-application-wizard-authentication-by-ldap></ak-application-wizard-authentication-by-ldap>`,
ProviderModelEnum.LdapLdapprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.LdapLdapprovider,
...(provider as LDAPProviderRequest),
}),
],
[
"proxyprovider-proxy",
msg("Transparent Reverse Proxy"),
msg("For transparent reverse proxies with required authentication"),
() => html`<ak-application-wizard-authentication-for-reverse-proxy></ak-application-wizard-authentication-for-reverse-proxy>`,
ProviderModelEnum.ProxyProxyprovider
ProviderModelEnum.ProxyProxyprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.ProxyProxyprovider,
...(provider as ProxyProviderRequest),
mode: ProxyMode.Proxy,
}),
],
[
"proxyprovider-forwardsingle",
msg("Forward Single Proxy"),
msg("Forward Auth Single Application"),
msg("For nginx's auth_request or traefix's forwardAuth"),
() => html`<ak-application-wizard-authentication-for-single-forward-proxy></ak-application-wizard-authentication-for-single-forward-proxy>`,
ProviderModelEnum.ProxyProxyprovider
ProviderModelEnum.ProxyProxyprovider ,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.ProxyProxyprovider,
...(provider as ProxyProviderRequest),
mode: ProxyMode.ForwardSingle,
}),
],
[
"proxyprovider-forwarddomain",
msg("Forward Auth Domain Level"),
msg("For nginx's auth_request or traefix's forwardAuth per root domain"),
() => html`<ak-application-wizard-authentication-for-forward-proxy-domain></ak-application-wizard-authentication-for-forward-proxy-domain>`,
ProviderModelEnum.ProxyProxyprovider ,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.ProxyProxyprovider,
...(provider as ProxyProviderRequest),
mode: ProxyMode.ForwardDomain,
}),
],
[
@ -63,93 +97,54 @@ const _providerModelsTable: ProviderType[] = [
msg("SAML Configuration"),
msg("Configure SAML provider manually"),
() => html`<ak-application-wizard-authentication-by-saml-configuration></ak-application-wizard-authentication-by-saml-configuration>`,
ProviderModelEnum.SamlSamlprovider
ProviderModelEnum.SamlSamlprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.SamlSamlprovider,
...(provider as SAMLProviderRequest),
}),
],
[
"radiusprovider",
msg("RADIUS Configuration"),
msg("Configure RADIUS provider manually"),
() => html`<ak-application-wizard-authentication-by-radius></ak-application-wizard-authentication-by-radius>`,
ProviderModelEnum.RadiusRadiusprovider
ProviderModelEnum.RadiusRadiusprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.RadiusRadiusprovider,
...(provider as RadiusProviderRequest),
}),
],
[
"scimprovider",
msg("SCIM Manual configuration"),
msg("Configure SCIM provider manually"),
() => html`<ak-application-wizard-authentication-by-scim></ak-application-wizard-authentication-by-scim>`,
ProviderModelEnum.ScimScimprovider
],
];
const converters = new Map<ProviderModelEnumType, ModelConverter>([
[
ProviderModelEnum.Oauth2Oauth2provider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.Oauth2Oauth2provider,
...(provider as OAuth2ProviderRequest),
}),
],
[
ProviderModelEnum.LdapLdapprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.LdapLdapprovider,
...(provider as LDAPProviderRequest),
}),
],
[
ProviderModelEnum.ProxyProxyprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.ProxyProxyprovider,
...(provider as ProxyProviderRequest),
}),
],
[
ProviderModelEnum.SamlSamlprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.SamlSamlprovider,
...(provider as SAMLProviderRequest),
}),
],
[
ProviderModelEnum.ScimScimprovider,
(provider: OneOfProvider) => ({
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.ScimScimprovider,
...(provider as SCIMProviderRequest),
}),
],
[
ProviderModelEnum.RadiusRadiusprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.RadiusRadiusprovider,
...(provider as RadiusProviderRequest),
}),
],
]);
// Contract enforcement
const getConverter = (modelName: ProviderModelEnumType): ModelConverter => {
const maybeConverter = converters.get(modelName);
if (!maybeConverter) {
throw new Error(`ModelName lookup failed in model converter definition: ${"modelName"}`);
}
return maybeConverter;
};
],
];
function mapProviders([formName, name, description, _, modelName]: ProviderType): LocalTypeCreate {
function mapProviders([formName, name, description, _, modelName, converter]: ProviderType): LocalTypeCreate {
return {
formName,
name,
description,
component: "",
modelName,
converter: getConverter(modelName),
converter
};
}
export const providerModelsList = _providerModelsTable.map(mapProviders);
export const providerRendererList = new Map<string, ProviderRenderer>(
_providerModelsTable.map(([modelName, _0, _1, renderer]) => [modelName, renderer]),
_providerModelsTable.map(([modelName, _0, _1, renderer]) => [modelName, renderer])
);
export default providerModelsList;

View file

@ -58,7 +58,7 @@ const runningState: State = {
};
const errorState: State = {
state: "error",
label: msg("There was an error in saving your application:"),
label: msg("Authentik was unable to save this application:"),
icon: ["fa-times-circle", "pf-m-danger"],
};
@ -68,21 +68,6 @@ const successState: State = {
icon: ["fa-check-circle", "pf-m-success"],
};
function extract(o: Record<string, any>): string[] {
function inner(o: Record<string, any>): string[] {
if (typeof o !== "object") {
return [];
}
if (Array.isArray(o)) {
return o;
}
return Object.keys(o)
.map((k) => inner(o[k]))
.flat();
}
return inner(o);
}
@customElement("ak-application-wizard-commit-application")
export class ApplicationWizardCommitApplication extends BasePanel {
static get styles() {
@ -126,26 +111,10 @@ export class ApplicationWizardCommitApplication extends BasePanel {
);
}
const provider = (() => {
if (this.wizard.providerModel === "proxyprovider-forwardsingle") {
return {
...providerModel.converter(this.wizard.provider),
mode: ProxyMode.ForwardSingle,
};
}
if (this.wizard.providerModel === "proxyprovider-proxy") {
return {
...providerModel.converter(this.wizard.provider),
mode: ProxyMode.Proxy,
};
}
return providerModel.converter(this.wizard.provider);
})();
const request: TransactionApplicationRequest = {
providerModel: providerModel.modelName as ProviderModelType,
app: cleanApplication(this.wizard.app),
provider,
provider: providerModel.converter(this.wizard.provider)
};
this.send(request);
@ -153,6 +122,21 @@ export class ApplicationWizardCommitApplication extends BasePanel {
}
}
decodeErrors(body: Record<string, any>) {
const spaceify = (src: Record<string, string>) =>
Object.values(src).map((msg) => `\u00a0\u00a0\u00a0\u00a0${msg}`);
let errs: string[] = [];
if (body["app"] !== undefined) {
errs = [...errs, msg("In the Application:"), ...spaceify(body["app"])];
}
if (body["provider"] !== undefined) {
errs = [...errs, msg("In the Provider:"), ...spaceify(body["provider"])];
}
console.log(body, errs);
return errs;
}
async send(
data: TransactionApplicationRequest,
): Promise<TransactionApplicationResponse | void> {
@ -170,7 +154,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
})
.catch((resolution: any) => {
resolution.response.json().then((body: Record<string, any>) => {
this.errors = extract(body);
this.errors = this.decodeErrors(body);
this.commitState = errorState;
});
});

View file

@ -6,6 +6,7 @@ import "./ldap/ak-application-wizard-authentication-by-ldap";
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 "./proxy/ak-application-wizard-authentication-for-forward-domain-proxy";
import "./radius/ak-application-wizard-authentication-by-radius";
import "./saml/ak-application-wizard-authentication-by-saml-configuration";
import "./scim/ak-application-wizard-authentication-by-scim";

View file

@ -127,7 +127,7 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
</p>
</ak-form-element-horizontal>
<div class="pf-c-card__footer">${this.renderProxyMode()}</div>
${this.renderProxyMode()}
<ak-text-input
name="accessTokenValidity"

View file

@ -0,0 +1,55 @@
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-forward-proxy-domain")
export class AkForwardDomainProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
renderModeDescription() {
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>`;
}
renderProxyMode() {
return html`
<ak-text-input
name="externalHost"
label=${msg("External host")}
value=${ifDefined(this.instance?.externalHost)}
required
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"
label=${msg("Cookie domain")}
value="${ifDefined(this.instance?.cookieDomain)}"
required
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>
`;
}
}
export default AkForwardDomainProxyApplicationWizardPage;

View file

@ -30,7 +30,7 @@ class ApplicationStep implements ApplicationStepType {
class ProviderMethodStep implements ApplicationStepType {
id = "provider-method";
label = "Authentication Method";
label = "Provider Type";
disabled = false;
valid = false;
@ -47,7 +47,7 @@ class ProviderMethodStep implements ApplicationStepType {
class ProviderStepDetails implements ApplicationStepType {
id = "provider-details";
label = "Authentication Details";
label = "Provider Configuration";
disabled = true;
valid = false;
get buttons() {
@ -61,7 +61,7 @@ class ProviderStepDetails implements ApplicationStepType {
class SubmitApplicationStep implements ApplicationStepType {
id = "submit";
label = "Submit New Application";
label = "Submit Application";
disabled = true;
valid = false;