Compare commits
	
		
			14 Commits
		
	
	
		
			version/20
			...
			web/flow/t
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1a340eb437 | |||
| 8982798c95 | |||
| c3b33fdb07 | |||
| 9809b94030 | |||
| e7527c551b | |||
| 36b10b434a | |||
| 831797b871 | |||
| 5cc2c0f45f | |||
| 32442766f4 | |||
| 75790909a8 | |||
| e0d5df89ca | |||
| f25a9c624e | |||
| 914993a788 | |||
| 89dad07a66 | 
| @ -5,15 +5,14 @@ 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"; | ||||
| import { Interface } from "@goauthentik/elements/Interface"; | ||||
| import "@goauthentik/elements/LoadingOverlay"; | ||||
| import "@goauthentik/elements/ak-locale-context"; | ||||
| import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand"; | ||||
| import { themeImage } from "@goauthentik/elements/utils/images"; | ||||
| import "@goauthentik/flow/components/ak-brand-footer"; | ||||
| import "@goauthentik/flow/sources/apple/AppleLoginInit"; | ||||
| import "@goauthentik/flow/sources/plex/PlexLoginInit"; | ||||
| import "@goauthentik/flow/stages/FlowErrorStage"; | ||||
| @ -26,6 +25,7 @@ import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "l | ||||
| import { customElement, property, state } from "lit/decorators.js"; | ||||
| import { unsafeHTML } from "lit/directives/unsafe-html.js"; | ||||
| import { until } from "lit/directives/until.js"; | ||||
| import { html as staticHtml, unsafeStatic } from "lit/static-html.js"; | ||||
|  | ||||
| import PFBackgroundImage from "@patternfly/patternfly/components/BackgroundImage/background-image.css"; | ||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||
| @ -49,6 +49,52 @@ import { | ||||
|     UiThemeEnum, | ||||
| } from "@goauthentik/api"; | ||||
|  | ||||
| type StageRenderer = { | ||||
|     // Provide the lit-element tag if it's different from the challenge.component name | ||||
|     tag?: string; | ||||
|     // Provide a dynamic import whenever possible; otherwise, make sure you include it in the | ||||
|     // build-time imports above. | ||||
|     import?: () => Promise<unknown>; | ||||
| }; | ||||
| type StageRenderers = { [key: string]: StageRenderer }; | ||||
|  | ||||
| // authentik's standard stages and the Lit components that handle them. A "standard stage" conforms | ||||
| // to an API that takes two properties: | ||||
| // `.host=${host: StageHost} .challenge=${challenge: ChallengeTypes}` | ||||
| // Exceptions are handled in a switch/case statement below the renderer for these. | ||||
|  | ||||
| // All of that `async () => await import("@goauthentik/flow/...")` boilerplate cannot be abstracted | ||||
| // away because [import is not a function](https://v8.dev/features/dynamic-import), it is a | ||||
| // _statement_, and its contents are statically analyzed by bundlers, compilers, and the V8 | ||||
| // interpreter. | ||||
|  | ||||
| // Prettier ignore to keep the table looking like a table: | ||||
| // prettier-ignore | ||||
| const allStages: StageRenderers = { | ||||
|     "ak-stage-access-denied": { import: async () => await import("@goauthentik/flow/stages/access_denied/AccessDeniedStage") }, | ||||
|     "ak-stage-identification": { import: async () => await import("@goauthentik/flow/stages/identification/IdentificationStage") }, | ||||
|     "ak-stage-password": { import: async () => await import("@goauthentik/flow/stages/password/PasswordStage") }, | ||||
|     "ak-stage-captcha": { import: async () => await import("@goauthentik/flow/stages/captcha/CaptchaStage") }, | ||||
|     "ak-stage-consent": { import: async () => await import("@goauthentik/flow/stages/consent/ConsentStage") }, | ||||
|     "ak-stage-dummy": { import: async () => await import("@goauthentik/flow/stages/dummy/DummyStage") }, | ||||
|     "ak-stage-email": { import: async () => await import("@goauthentik/flow/stages/email/EmailStage") }, | ||||
|     "ak-stage-autosubmit": { import: async () => await import("@goauthentik/flow/stages/autosubmit/AutosubmitStage") }, | ||||
|     "ak-stage-prompt": { import: async () => await import("@goauthentik/flow/stages/prompt/PromptStage") }, | ||||
|     "ak-stage-authenticator-totp": { import: async () => await import("@goauthentik/flow/stages/authenticator_totp/AuthenticatorTOTPStage") }, | ||||
|     "ak-stage-authenticator-duo": { import: async () => await import("@goauthentik/flow/stages/authenticator_duo/AuthenticatorDuoStage") }, | ||||
|     "ak-stage-authenticator-static": { import: async () => await import("@goauthentik/flow/stages/authenticator_static/AuthenticatorStaticStage") }, | ||||
|     "ak-stage-authenticator-webauthn": { }, | ||||
|     "ak-stage-authenticator-sms": { import: async () => await import("@goauthentik/flow/stages/authenticator_sms/AuthenticatorSMSStage") }, | ||||
|     "ak-stage-authenticator-validate": { import: async () => await import("@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage") }, | ||||
|     "ak-stage-user-login": { import: async () => await import("@goauthentik/flow/stages/user_login/UserLoginStage") }, | ||||
|     "ak-source-plex": { tag: "ak-flow-source-plex" }, | ||||
|     "ak-source-oauth-apple": { tag: "ak-flow-source-oauth-apple" }, | ||||
|     "ak-provider-oauth2-device-code": { tag: "ak-flow-provider-oauth2-code", import: async () => await import("@goauthentik/flow/providers/oauth2/DeviceCode") }, | ||||
|     "ak-provider-oauth2-device-code-finish": { tag: "ak-flow-provider-oauth2-code-finish", import: async () => await import("@goauthentik/flow/providers/oauth2/DeviceCodeFinish") }, | ||||
|     "ak-stage-session-end": { import: async () => await import("@goauthentik/flow/providers/SessionEnd") }, | ||||
|     "ak-stage-flow-error": { }, | ||||
| } as const; | ||||
|  | ||||
| @customElement("ak-flow-executor") | ||||
| export class FlowExecutor extends Interface implements StageHost { | ||||
|     @property() | ||||
| @ -299,142 +345,21 @@ export class FlowExecutor extends Interface implements StageHost { | ||||
|         if (!this.challenge) { | ||||
|             return html`<ak-empty-state loading> </ak-empty-state>`; | ||||
|         } | ||||
|         const stage = allStages[this.challenge.component]; | ||||
|         if (stage) { | ||||
|             if (stage.import) { | ||||
|                 await stage.import(); | ||||
|             } | ||||
|             const tag = stage.tag ?? this.challenge.component; | ||||
|             // Prettier doesn't know what `staticHTML` is, will try to format it by | ||||
|             // prettier-ignore | ||||
|             return staticHtml`<${unsafeStatic(tag)} | ||||
|                 .host=${this as StageHost} | ||||
|                 .challenge=${this.challenge} | ||||
|             ></${unsafeStatic(tag)}>`; | ||||
|         } | ||||
|  | ||||
|         switch (this.challenge?.component) { | ||||
|             case "ak-stage-access-denied": | ||||
|                 await import("@goauthentik/flow/stages/access_denied/AccessDeniedStage"); | ||||
|                 return html`<ak-stage-access-denied | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-access-denied>`; | ||||
|             case "ak-stage-identification": | ||||
|                 await import("@goauthentik/flow/stages/identification/IdentificationStage"); | ||||
|                 return html`<ak-stage-identification | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-identification>`; | ||||
|             case "ak-stage-password": | ||||
|                 await import("@goauthentik/flow/stages/password/PasswordStage"); | ||||
|                 return html`<ak-stage-password | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-password>`; | ||||
|             case "ak-stage-captcha": | ||||
|                 await import("@goauthentik/flow/stages/captcha/CaptchaStage"); | ||||
|                 return html`<ak-stage-captcha | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-captcha>`; | ||||
|             case "ak-stage-consent": | ||||
|                 await import("@goauthentik/flow/stages/consent/ConsentStage"); | ||||
|                 return html`<ak-stage-consent | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-consent>`; | ||||
|             case "ak-stage-dummy": | ||||
|                 await import("@goauthentik/flow/stages/dummy/DummyStage"); | ||||
|                 return html`<ak-stage-dummy | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-dummy>`; | ||||
|             case "ak-stage-email": | ||||
|                 await import("@goauthentik/flow/stages/email/EmailStage"); | ||||
|                 return html`<ak-stage-email | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-email>`; | ||||
|             case "ak-stage-autosubmit": | ||||
|                 await import("@goauthentik/flow/stages/autosubmit/AutosubmitStage"); | ||||
|                 return html`<ak-stage-autosubmit | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-autosubmit>`; | ||||
|             case "ak-stage-prompt": | ||||
|                 await import("@goauthentik/flow/stages/prompt/PromptStage"); | ||||
|                 return html`<ak-stage-prompt | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-prompt>`; | ||||
|             case "ak-stage-authenticator-totp": | ||||
|                 await import("@goauthentik/flow/stages/authenticator_totp/AuthenticatorTOTPStage"); | ||||
|                 return html`<ak-stage-authenticator-totp | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-totp>`; | ||||
|             case "ak-stage-authenticator-duo": | ||||
|                 await import("@goauthentik/flow/stages/authenticator_duo/AuthenticatorDuoStage"); | ||||
|                 return html`<ak-stage-authenticator-duo | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-duo>`; | ||||
|             case "ak-stage-authenticator-static": | ||||
|                 await import( | ||||
|                     "@goauthentik/flow/stages/authenticator_static/AuthenticatorStaticStage" | ||||
|                 ); | ||||
|                 return html`<ak-stage-authenticator-static | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-static>`; | ||||
|             case "ak-stage-authenticator-webauthn": | ||||
|                 return html`<ak-stage-authenticator-webauthn | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-webauthn>`; | ||||
|             case "ak-stage-authenticator-sms": | ||||
|                 await import("@goauthentik/flow/stages/authenticator_sms/AuthenticatorSMSStage"); | ||||
|                 return html`<ak-stage-authenticator-sms | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-sms>`; | ||||
|             case "ak-stage-authenticator-validate": | ||||
|                 await import( | ||||
|                     "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage" | ||||
|                 ); | ||||
|                 return html`<ak-stage-authenticator-validate | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-validate>`; | ||||
|             case "ak-stage-user-login": | ||||
|                 await import("@goauthentik/flow/stages/user_login/UserLoginStage"); | ||||
|                 return html`<ak-stage-user-login | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-user-login>`; | ||||
|             // Sources | ||||
|             case "ak-source-plex": | ||||
|                 return html`<ak-flow-source-plex | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-flow-source-plex>`; | ||||
|             case "ak-source-oauth-apple": | ||||
|                 return html`<ak-flow-source-oauth-apple | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-flow-source-oauth-apple>`; | ||||
|             // Providers | ||||
|             case "ak-provider-oauth2-device-code": | ||||
|                 await import("@goauthentik/flow/providers/oauth2/DeviceCode"); | ||||
|                 return html`<ak-flow-provider-oauth2-code | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-flow-provider-oauth2-code>`; | ||||
|             case "ak-provider-oauth2-device-code-finish": | ||||
|                 await import("@goauthentik/flow/providers/oauth2/DeviceCodeFinish"); | ||||
|                 return html`<ak-flow-provider-oauth2-code-finish | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-flow-provider-oauth2-code-finish>`; | ||||
|             case "ak-stage-session-end": | ||||
|                 await import("@goauthentik/flow/providers/SessionEnd"); | ||||
|                 return html`<ak-stage-session-end | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-session-end>`; | ||||
|             // Internal stages | ||||
|             case "ak-stage-flow-error": | ||||
|                 return html`<ak-stage-flow-error | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-flow-error>`; | ||||
|             case "xak-flow-redirect": | ||||
|                 return html`<ak-stage-redirect | ||||
|                     .host=${this as StageHost} | ||||
| @ -504,11 +429,9 @@ export class FlowExecutor extends Interface implements StageHost { | ||||
|                                             > | ||||
|                                                 <img | ||||
|                                                     src="${themeImage( | ||||
|                                                         first( | ||||
|                                                             this.brand?.brandingLogo, | ||||
|                                                             globalAK()?.brand.brandingLogo, | ||||
|                                                         this.brand?.brandingLogo ?? | ||||
|                                                             globalAK()?.brand.brandingLogo ?? | ||||
|                                                             DefaultBrand.brandingLogo, | ||||
|                                                         ), | ||||
|                                                     )}" | ||||
|                                                     alt="authentik Logo" | ||||
|                                                 /> | ||||
| @ -516,25 +439,9 @@ export class FlowExecutor extends Interface implements StageHost { | ||||
|                                             ${until(this.renderChallenge())} | ||||
|                                         </div> | ||||
|                                         <footer class="pf-c-login__footer"> | ||||
|                                             <ul class="pf-c-list pf-m-inline"> | ||||
|                                                 ${this.brand?.uiFooterLinks?.map((link) => { | ||||
|                                                     if (link.href) { | ||||
|                                                         return html`${purify( | ||||
|                                                             html`<li> | ||||
|                                                                 <a href="${link.href}" | ||||
|                                                                     >${link.name}</a | ||||
|                                                                 > | ||||
|                                                             </li>`, | ||||
|                                                         )}`; | ||||
|                                                     } | ||||
|                                                     return html`<li> | ||||
|                                                         <span>${link.name}</span> | ||||
|                                                     </li>`; | ||||
|                                                 })} | ||||
|                                                 <li> | ||||
|                                                     <span>${msg("Powered by authentik")}</span> | ||||
|                                                 </li> | ||||
|                                             </ul> | ||||
|                                             <ak-brand-links | ||||
|                                                 .links=${this.brand?.uiFooterLinks ?? []} | ||||
|                                             ></ak-brand-links> | ||||
|                                         </footer> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|  | ||||
							
								
								
									
										51
									
								
								web/src/flow/components/ak-brand-footer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								web/src/flow/components/ak-brand-footer.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| import { purify } from "@goauthentik/common/purify"; | ||||
| import { AKElement } from "@goauthentik/elements/Base.js"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| import { css, html } from "lit"; | ||||
| import { customElement, property } from "lit/decorators.js"; | ||||
| import { map } from "lit/directives/map.js"; | ||||
|  | ||||
| import PFList from "@patternfly/patternfly/components/List/list.css"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
|  | ||||
| import { FooterLink } from "@goauthentik/api"; | ||||
|  | ||||
| const styles = css` | ||||
|     .pf-c-list a { | ||||
|         color: unset; | ||||
|     } | ||||
|     ul.pf-c-list.pf-m-inline { | ||||
|         justify-content: center; | ||||
|         padding: calc(var(--pf-global--spacer--xs) / 2) 0px; | ||||
|     } | ||||
| `; | ||||
|  | ||||
| const salesMark: FooterLink = { name: msg("Powered by authentik"), href: "" }; | ||||
|  | ||||
| @customElement("ak-brand-links") | ||||
| export class BrandLinks extends AKElement { | ||||
|     static get styles() { | ||||
|         return [PFBase, PFList, styles]; | ||||
|     } | ||||
|  | ||||
|     @property({ type: Array, attribute: false }) | ||||
|     links: FooterLink[] = []; | ||||
|  | ||||
|     render() { | ||||
|         const links = [...(this.links ?? []), salesMark]; | ||||
|         return html` <ul class="pf-c-list pf-m-inline"> | ||||
|             ${map(links, (link) => | ||||
|                 link.href | ||||
|                     ? purify(html`<li><a href="${link.href}">${link.name}</a></li>`) | ||||
|                     : html`<li><span>${link.name}</span></li>`, | ||||
|             )} | ||||
|         </ul>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|     interface HTMLElementTagNameMap { | ||||
|         "ak-brand-links": BrandLinks; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	