Compare commits
	
		
			1 Commits
		
	
	
		
			dependabot
			...
			fix-shared
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4f98c21f42 | 
							
								
								
									
										52
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										52
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -25,7 +25,6 @@ | |||||||
|                 "@formatjs/intl-listformat": "^7.5.7", |                 "@formatjs/intl-listformat": "^7.5.7", | ||||||
|                 "@fortawesome/fontawesome-free": "^6.6.0", |                 "@fortawesome/fontawesome-free": "^6.6.0", | ||||||
|                 "@goauthentik/api": "^2025.2.4-1745325566", |                 "@goauthentik/api": "^2025.2.4-1745325566", | ||||||
|                 "@lit-labs/ssr": "^3.2.2", |  | ||||||
|                 "@lit/context": "^1.1.2", |                 "@lit/context": "^1.1.2", | ||||||
|                 "@lit/localize": "^0.12.2", |                 "@lit/localize": "^0.12.2", | ||||||
|                 "@lit/reactive-element": "^2.0.4", |                 "@lit/reactive-element": "^2.0.4", | ||||||
| @ -66,6 +65,7 @@ | |||||||
|                 "remark-gfm": "^4.0.1", |                 "remark-gfm": "^4.0.1", | ||||||
|                 "remark-mdx-frontmatter": "^5.0.0", |                 "remark-mdx-frontmatter": "^5.0.0", | ||||||
|                 "style-mod": "^4.1.2", |                 "style-mod": "^4.1.2", | ||||||
|  |                 "trusted-types": "^2.0.0", | ||||||
|                 "ts-pattern": "^5.4.0", |                 "ts-pattern": "^5.4.0", | ||||||
|                 "unist-util-visit": "^5.0.0", |                 "unist-util-visit": "^5.0.0", | ||||||
|                 "webcomponent-qr-code": "^1.2.0", |                 "webcomponent-qr-code": "^1.2.0", | ||||||
| @ -2281,47 +2281,11 @@ | |||||||
|                 "@lezer/lr": "^1.0.0" |                 "@lezer/lr": "^1.0.0" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@lit-labs/ssr": { |  | ||||||
|             "version": "3.3.1", |  | ||||||
|             "resolved": "https://registry.npmjs.org/@lit-labs/ssr/-/ssr-3.3.1.tgz", |  | ||||||
|             "integrity": "sha512-JlF1PempxvzrGEpRFrF+Ki0MHzR3HA51SK8Zv0cFpW9p0bPW4k0FeCwrElCu371UEpXF7RcaE2wgYaE1az0XKg==", |  | ||||||
|             "dependencies": { |  | ||||||
|                 "@lit-labs/ssr-client": "^1.1.7", |  | ||||||
|                 "@lit-labs/ssr-dom-shim": "^1.3.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==", |  | ||||||
|             "dependencies": { |  | ||||||
|                 "@lit/reactive-element": "^2.0.4", |  | ||||||
|                 "lit": "^3.1.2", |  | ||||||
|                 "lit-html": "^3.1.2" |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         "node_modules/@lit-labs/ssr-dom-shim": { |         "node_modules/@lit-labs/ssr-dom-shim": { | ||||||
|             "version": "1.3.0", |             "version": "1.3.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", |             "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", | ||||||
|             "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==" |             "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@lit-labs/ssr/node_modules/@types/node": { |  | ||||||
|             "version": "16.18.126", |  | ||||||
|             "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", |  | ||||||
|             "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==" |  | ||||||
|         }, |  | ||||||
|         "node_modules/@lit/context": { |         "node_modules/@lit/context": { | ||||||
|             "version": "1.1.5", |             "version": "1.1.5", | ||||||
|             "resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.5.tgz", |             "resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.5.tgz", | ||||||
| @ -3557,6 +3521,7 @@ | |||||||
|             "version": "0.3.0", |             "version": "0.3.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@parse5/tools/-/tools-0.3.0.tgz", |             "resolved": "https://registry.npmjs.org/@parse5/tools/-/tools-0.3.0.tgz", | ||||||
|             "integrity": "sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==", |             "integrity": "sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==", | ||||||
|  |             "dev": true, | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "parse5": "^7.0.0" |                 "parse5": "^7.0.0" | ||||||
|             } |             } | ||||||
| @ -10723,6 +10688,7 @@ | |||||||
|             "version": "4.0.1", |             "version": "4.0.1", | ||||||
|             "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", |             "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", | ||||||
|             "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", |             "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", | ||||||
|  |             "dev": true, | ||||||
|             "engines": { |             "engines": { | ||||||
|                 "node": ">= 12" |                 "node": ">= 12" | ||||||
|             } |             } | ||||||
| @ -11343,6 +11309,7 @@ | |||||||
|             "version": "5.18.1", |             "version": "5.18.1", | ||||||
|             "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", |             "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", | ||||||
|             "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", |             "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", | ||||||
|  |             "dev": true, | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "graceful-fs": "^4.2.4", |                 "graceful-fs": "^4.2.4", | ||||||
|                 "tapable": "^2.2.0" |                 "tapable": "^2.2.0" | ||||||
| @ -13820,7 +13787,8 @@ | |||||||
|         "node_modules/graceful-fs": { |         "node_modules/graceful-fs": { | ||||||
|             "version": "4.2.11", |             "version": "4.2.11", | ||||||
|             "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", |             "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", | ||||||
|             "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" |             "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", | ||||||
|  |             "dev": true | ||||||
|         }, |         }, | ||||||
|         "node_modules/grapheme-splitter": { |         "node_modules/grapheme-splitter": { | ||||||
|             "version": "1.0.4", |             "version": "1.0.4", | ||||||
| @ -18256,6 +18224,7 @@ | |||||||
|             "version": "3.3.2", |             "version": "3.3.2", | ||||||
|             "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", |             "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", | ||||||
|             "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", |             "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", | ||||||
|  |             "dev": true, | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "data-uri-to-buffer": "^4.0.0", |                 "data-uri-to-buffer": "^4.0.0", | ||||||
|                 "fetch-blob": "^3.1.4", |                 "fetch-blob": "^3.1.4", | ||||||
| @ -22373,6 +22342,7 @@ | |||||||
|             "version": "2.2.1", |             "version": "2.2.1", | ||||||
|             "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", |             "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", | ||||||
|             "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", |             "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", | ||||||
|  |             "dev": true, | ||||||
|             "engines": { |             "engines": { | ||||||
|                 "node": ">=6" |                 "node": ">=6" | ||||||
|             } |             } | ||||||
| @ -22724,6 +22694,12 @@ | |||||||
|                 "url": "https://github.com/sponsors/wooorm" |                 "url": "https://github.com/sponsors/wooorm" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "node_modules/trusted-types": { | ||||||
|  |             "version": "2.0.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/trusted-types/-/trusted-types-2.0.0.tgz", | ||||||
|  |             "integrity": "sha512-Eam+AUp6lg04YjmYkuLNhEJX+6ByocrKTpY/TtfRK/gV6OmxeN0OwkIasor28SUJ606snArpPLGtPMGbqdaaUA==", | ||||||
|  |             "license": "W3C-20150513" | ||||||
|  |         }, | ||||||
|         "node_modules/ts-api-utils": { |         "node_modules/ts-api-utils": { | ||||||
|             "version": "2.1.0", |             "version": "2.1.0", | ||||||
|             "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", |             "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", | ||||||
|  | |||||||
| @ -13,7 +13,6 @@ | |||||||
|         "@formatjs/intl-listformat": "^7.5.7", |         "@formatjs/intl-listformat": "^7.5.7", | ||||||
|         "@fortawesome/fontawesome-free": "^6.6.0", |         "@fortawesome/fontawesome-free": "^6.6.0", | ||||||
|         "@goauthentik/api": "^2025.2.4-1745325566", |         "@goauthentik/api": "^2025.2.4-1745325566", | ||||||
|         "@lit-labs/ssr": "^3.2.2", |  | ||||||
|         "@lit/context": "^1.1.2", |         "@lit/context": "^1.1.2", | ||||||
|         "@lit/localize": "^0.12.2", |         "@lit/localize": "^0.12.2", | ||||||
|         "@lit/reactive-element": "^2.0.4", |         "@lit/reactive-element": "^2.0.4", | ||||||
| @ -54,6 +53,7 @@ | |||||||
|         "remark-gfm": "^4.0.1", |         "remark-gfm": "^4.0.1", | ||||||
|         "remark-mdx-frontmatter": "^5.0.0", |         "remark-mdx-frontmatter": "^5.0.0", | ||||||
|         "style-mod": "^4.1.2", |         "style-mod": "^4.1.2", | ||||||
|  |         "trusted-types": "^2.0.0", | ||||||
|         "ts-pattern": "^5.4.0", |         "ts-pattern": "^5.4.0", | ||||||
|         "unist-util-visit": "^5.0.0", |         "unist-util-visit": "^5.0.0", | ||||||
|         "webcomponent-qr-code": "^1.2.0", |         "webcomponent-qr-code": "^1.2.0", | ||||||
|  | |||||||
| @ -1,26 +1,110 @@ | |||||||
| import type { Config as DOMPurifyConfig } from "dompurify"; | import type { Config as DOMPurifyConfig } from "dompurify"; | ||||||
| import DOMPurify from "dompurify"; | import DOMPurify from "dompurify"; | ||||||
|  | import { trustedTypes } from "trusted-types"; | ||||||
|  |  | ||||||
| import { render } from "@lit-labs/ssr"; | import { render } from "lit"; | ||||||
| import { collectResult } from "@lit-labs/ssr/lib/render-result.js"; |  | ||||||
| import { TemplateResult, html } from "lit"; |  | ||||||
| import { unsafeHTML } from "lit/directives/unsafe-html.js"; | import { unsafeHTML } from "lit/directives/unsafe-html.js"; | ||||||
| import { until } from "lit/directives/until.js"; |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Trusted types policy that escapes HTML content in place. | ||||||
|  |  * | ||||||
|  |  * @see {@linkcode SanitizedTrustPolicy} to strip HTML content. | ||||||
|  |  * | ||||||
|  |  * @returns {TrustedHTML} All HTML content, escaped. | ||||||
|  |  */ | ||||||
|  | export const EscapeTrustPolicy = trustedTypes.createPolicy("authentik-escape", { | ||||||
|  |     createHTML: (untrustedHTML: string) => { | ||||||
|  |         return DOMPurify.sanitize(untrustedHTML, { | ||||||
|  |             RETURN_TRUSTED_TYPE: false, | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Trusted types policy, stripping all HTML content. | ||||||
|  |  * | ||||||
|  |  * @returns {TrustedHTML} Text content only, all HTML tags stripped. | ||||||
|  |  */ | ||||||
|  | export const SanitizedTrustPolicy = trustedTypes.createPolicy("authentik-sanitize", { | ||||||
|  |     createHTML: (untrustedHTML: string) => { | ||||||
|  |         return DOMPurify.sanitize(untrustedHTML, { | ||||||
|  |             RETURN_TRUSTED_TYPE: false, | ||||||
|  |             ALLOWED_TAGS: ["#text"], | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Trusted types policy, allowing a minimal set of _safe_ HTML tags supplied by | ||||||
|  |  * a trusted source, such as the brand API. | ||||||
|  |  */ | ||||||
|  | export const BrandedHTMLPolicy = trustedTypes.createPolicy("authentik-restrict", { | ||||||
|  |     createHTML: (untrustedHTML: string) => { | ||||||
|  |         return DOMPurify.sanitize(untrustedHTML, { | ||||||
|  |             RETURN_TRUSTED_TYPE: false, | ||||||
|  |             FORBID_TAGS: [ | ||||||
|  |                 "script", | ||||||
|  |                 "style", | ||||||
|  |                 "iframe", | ||||||
|  |                 "link", | ||||||
|  |                 "object", | ||||||
|  |                 "embed", | ||||||
|  |                 "applet", | ||||||
|  |                 "meta", | ||||||
|  |                 "base", | ||||||
|  |                 "form", | ||||||
|  |                 "input", | ||||||
|  |                 "textarea", | ||||||
|  |                 "select", | ||||||
|  |                 "button", | ||||||
|  |             ], | ||||||
|  |             FORBID_ATTR: [ | ||||||
|  |                 "onerror", | ||||||
|  |                 "onclick", | ||||||
|  |                 "onload", | ||||||
|  |                 "onmouseover", | ||||||
|  |                 "onmouseout", | ||||||
|  |                 "onmouseup", | ||||||
|  |                 "onmousedown", | ||||||
|  |                 "onfocus", | ||||||
|  |                 "onblur", | ||||||
|  |                 "onsubmit", | ||||||
|  |             ], | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export type AuthentikTrustPolicy = | ||||||
|  |     | typeof EscapeTrustPolicy | ||||||
|  |     | typeof SanitizedTrustPolicy | ||||||
|  |     | typeof BrandedHTMLPolicy; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Sanitize an untrusted HTML string using a trusted types policy. | ||||||
|  |  */ | ||||||
|  | export function sanitizeHTML(trustPolicy: AuthentikTrustPolicy, untrustedHTML: string) { | ||||||
|  |     return unsafeHTML(trustPolicy.createHTML(untrustedHTML).toString()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * DOMPurify configuration for strict sanitization. | ||||||
|  |  * | ||||||
|  |  * This configuration only allows text nodes and disallows all HTML tags. | ||||||
|  |  */ | ||||||
| export const DOM_PURIFY_STRICT = { | export const DOM_PURIFY_STRICT = { | ||||||
|     ALLOWED_TAGS: ["#text"], |     ALLOWED_TAGS: ["#text"], | ||||||
| } as const satisfies DOMPurifyConfig; | } as const satisfies DOMPurifyConfig; | ||||||
|  |  | ||||||
| export async function renderStatic(input: TemplateResult): Promise<string> { | /** | ||||||
|     return await collectResult(render(input)); |  * Render untrusted HTML to a string without escaping it. | ||||||
| } |  * | ||||||
|  |  * @returns {string} The rendered HTML string. | ||||||
|  |  */ | ||||||
|  | export function renderStaticHTMLUnsafe(untrustedHTML: unknown): string { | ||||||
|  |     const container = document.createElement("html"); | ||||||
|  |     render(untrustedHTML, container); | ||||||
|  |  | ||||||
| export function purify(input: TemplateResult): TemplateResult { |     const result = container.innerHTML; | ||||||
|     return html`${until( |  | ||||||
|         (async () => { |     return result; | ||||||
|             const rendered = await renderStatic(input); |  | ||||||
|             const purified = DOMPurify.sanitize(rendered); |  | ||||||
|             return html`${unsafeHTML(purified)}`; |  | ||||||
|         })(), |  | ||||||
|     )}`; |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										55
									
								
								web/src/elements/utils/iframe.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								web/src/elements/utils/iframe.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | /** | ||||||
|  |  * @file IFrame Utilities | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | interface IFrameLoadResult { | ||||||
|  |     contentWindow: Window; | ||||||
|  |     contentDocument: Document; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function pluckIFrameContent(iframe: HTMLIFrameElement) { | ||||||
|  |     const contentWindow = iframe.contentWindow; | ||||||
|  |     const contentDocument = iframe.contentDocument; | ||||||
|  |  | ||||||
|  |     if (!contentWindow) { | ||||||
|  |         throw new Error("Iframe contentWindow is not accessible"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!contentDocument) { | ||||||
|  |         throw new Error("Iframe contentDocument is not accessible"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         contentWindow, | ||||||
|  |         contentDocument, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function resolveIFrameContent(iframe: HTMLIFrameElement): Promise<IFrameLoadResult> { | ||||||
|  |     if (iframe.contentDocument?.readyState === "complete") { | ||||||
|  |         return Promise.resolve(pluckIFrameContent(iframe)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return new Promise((resolve) => { | ||||||
|  |         iframe.addEventListener("load", () => resolve(pluckIFrameContent(iframe)), { once: true }); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Creates a minimal HTML wrapper for an iframe. | ||||||
|  |  * | ||||||
|  |  * @deprecated Use the `contentDocument.body` directly instead. | ||||||
|  |  */ | ||||||
|  | export function createIFrameHTMLWrapper(bodyContent: string): string { | ||||||
|  |     const html = String.raw; | ||||||
|  |  | ||||||
|  |     return html`<!doctype html> | ||||||
|  |         <html> | ||||||
|  |             <head> | ||||||
|  |                 <meta charset="utf-8" /> | ||||||
|  |             </head> | ||||||
|  |             <body style="display:flex;flex-direction:row;justify-content:center;"> | ||||||
|  |                 ${bodyContent} | ||||||
|  |             </body> | ||||||
|  |         </html>`; | ||||||
|  | } | ||||||
| @ -1,4 +1,4 @@ | |||||||
| import { purify } from "@goauthentik/common/purify"; | import { BrandedHTMLPolicy, sanitizeHTML } from "@goauthentik/common/purify"; | ||||||
| import { AKElement } from "@goauthentik/elements/Base.js"; | import { AKElement } from "@goauthentik/elements/Base.js"; | ||||||
|  |  | ||||||
| import { msg } from "@lit/localize"; | import { msg } from "@lit/localize"; | ||||||
| @ -21,8 +21,6 @@ const styles = css` | |||||||
|     } |     } | ||||||
| `; | `; | ||||||
|  |  | ||||||
| const poweredBy: FooterLink = { name: msg("Powered by authentik"), href: null }; |  | ||||||
|  |  | ||||||
| @customElement("ak-brand-links") | @customElement("ak-brand-links") | ||||||
| export class BrandLinks extends AKElement { | export class BrandLinks extends AKElement { | ||||||
|     static get styles() { |     static get styles() { | ||||||
| @ -33,13 +31,21 @@ export class BrandLinks extends AKElement { | |||||||
|     links: FooterLink[] = []; |     links: FooterLink[] = []; | ||||||
|  |  | ||||||
|     render() { |     render() { | ||||||
|         const links = [...(this.links ?? []), poweredBy]; |         const links = [...(this.links ?? [])]; | ||||||
|  |  | ||||||
|         return html` <ul class="pf-c-list pf-m-inline"> |         return html` <ul class="pf-c-list pf-m-inline"> | ||||||
|             ${map(links, (link) => |             ${map(links, (link) => { | ||||||
|                 link.href |                 const children = sanitizeHTML(BrandedHTMLPolicy, link.name); | ||||||
|                     ? purify(html`<li><a href="${link.href}">${link.name}</a></li>`) |  | ||||||
|                     : html`<li><span>${link.name}</span></li>`, |                 if (link.href) { | ||||||
|             )} |                     return html`<li><a href="${link.href}">${children}</a></li>`; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return html`<li> | ||||||
|  |                     <span> ${children} </span> | ||||||
|  |                 </li>`; | ||||||
|  |             })} | ||||||
|  |             <li><span>${msg("Powered by authentik")}</span></li> | ||||||
|         </ul>`; |         </ul>`; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,15 +1,16 @@ | |||||||
| ///<reference types="@hcaptcha/types"/> | /// <reference types="@hcaptcha/types"/> | ||||||
| import { renderStatic } from "@goauthentik/common/purify"; | /// <reference types="turnstile-types"/> | ||||||
|  | import { renderStaticHTMLUnsafe } from "@goauthentik/common/purify"; | ||||||
| import "@goauthentik/elements/EmptyState"; | import "@goauthentik/elements/EmptyState"; | ||||||
| import { akEmptyState } from "@goauthentik/elements/EmptyState"; | import { akEmptyState } from "@goauthentik/elements/EmptyState"; | ||||||
| import { bound } from "@goauthentik/elements/decorators/bound"; | import { bound } from "@goauthentik/elements/decorators/bound"; | ||||||
| import "@goauthentik/elements/forms/FormElement"; | import "@goauthentik/elements/forms/FormElement"; | ||||||
|  | import { createIFrameHTMLWrapper } from "@goauthentik/elements/utils/iframe"; | ||||||
| import { ListenerController } from "@goauthentik/elements/utils/listenerController.js"; | import { ListenerController } from "@goauthentik/elements/utils/listenerController.js"; | ||||||
| import { randomId } from "@goauthentik/elements/utils/randomId"; | import { randomId } from "@goauthentik/elements/utils/randomId"; | ||||||
| import "@goauthentik/flow/FormStatic"; | import "@goauthentik/flow/FormStatic"; | ||||||
| import { BaseStage } from "@goauthentik/flow/stages/base"; | import { BaseStage } from "@goauthentik/flow/stages/base"; | ||||||
| import { P, match } from "ts-pattern"; | import { P, match } from "ts-pattern"; | ||||||
| import type * as _ from "turnstile-types"; |  | ||||||
|  |  | ||||||
| import { msg } from "@lit/localize"; | import { msg } from "@lit/localize"; | ||||||
| import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit"; | import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit"; | ||||||
| @ -56,40 +57,36 @@ type CaptchaHandler = { | |||||||
| // a resize. Because the Captcha is itself in an iframe, the reported height is often off by some | // a resize. Because the Captcha is itself in an iframe, the reported height is often off by some | ||||||
| // margin, so adding 2rem of height to our container adds padding and prevents scroll bars or hidden | // margin, so adding 2rem of height to our container adds padding and prevents scroll bars or hidden | ||||||
| // rendering. | // rendering. | ||||||
|  | function iframeTemplate(children: TemplateResult, challengeURL: string): TemplateResult { | ||||||
|  |     return html` ${children} | ||||||
|  |         <script> | ||||||
|  |             new ResizeObserver((entries) => { | ||||||
|  |                 const height = | ||||||
|  |                     document.body.offsetHeight + | ||||||
|  |                     parseFloat(getComputedStyle(document.body).fontSize) * 2; | ||||||
|  |  | ||||||
| const iframeTemplate = (captchaElement: TemplateResult, challengeUrl: string) => |                 window.parent.postMessage({ | ||||||
|     html`<!doctype html> |                     message: "resize", | ||||||
|         <head> |                     source: "goauthentik.io", | ||||||
|             <html> |                     context: "flow-executor", | ||||||
|                 <body style="display:flex;flex-direction:row;justify-content:center;"> |                     size: { height }, | ||||||
|                     ${captchaElement} |                 }); | ||||||
|                     <script> |             }).observe(document.querySelector(".ak-captcha-container")); | ||||||
|                         new ResizeObserver((entries) => { |         </script> | ||||||
|                             const height = |  | ||||||
|                                 document.body.offsetHeight + |         <script src=${challengeURL}></script> | ||||||
|                                 parseFloat(getComputedStyle(document.body).fontSize) * 2; |  | ||||||
|                             window.parent.postMessage({ |         <script> | ||||||
|                                 message: "resize", |             function callback(token) { | ||||||
|                                 source: "goauthentik.io", |                 window.parent.postMessage({ | ||||||
|                                 context: "flow-executor", |                     message: "captcha", | ||||||
|                                 size: { height }, |                     source: "goauthentik.io", | ||||||
|                             }); |                     context: "flow-executor", | ||||||
|                         }).observe(document.querySelector(".ak-captcha-container")); |                     token, | ||||||
|                     </script> |                 }); | ||||||
|                     <script src=${challengeUrl}></script> |             } | ||||||
|                     <script> |         </script>`; | ||||||
|                         function callback(token) { | } | ||||||
|                             window.parent.postMessage({ |  | ||||||
|                                 message: "captcha", |  | ||||||
|                                 source: "goauthentik.io", |  | ||||||
|                                 context: "flow-executor", |  | ||||||
|                                 token: token, |  | ||||||
|                             }); |  | ||||||
|                         } |  | ||||||
|                     </script> |  | ||||||
|                 </body> |  | ||||||
|             </html> |  | ||||||
|         </head>`; |  | ||||||
|  |  | ||||||
| @customElement("ak-stage-captcha") | @customElement("ak-stage-captcha") | ||||||
| export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeResponseRequest> { | export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeResponseRequest> { | ||||||
| @ -305,11 +302,25 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async renderFrame(captchaElement: TemplateResult) { |     async renderFrame(captchaElement: TemplateResult) { | ||||||
|         this.captchaFrame.contentWindow?.document.open(); |         const { contentDocument } = this.captchaFrame || {}; | ||||||
|         this.captchaFrame.contentWindow?.document.write( |  | ||||||
|             await renderStatic(iframeTemplate(captchaElement, this.challenge.jsUrl)), |         if (!contentDocument) { | ||||||
|  |             console.debug( | ||||||
|  |                 "authentik/stages/captcha: unable to render captcha frame, no contentDocument", | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         contentDocument.open(); | ||||||
|  |  | ||||||
|  |         contentDocument.write( | ||||||
|  |             createIFrameHTMLWrapper( | ||||||
|  |                 renderStaticHTMLUnsafe(iframeTemplate(captchaElement, this.challenge.jsUrl)), | ||||||
|  |             ), | ||||||
|         ); |         ); | ||||||
|         this.captchaFrame.contentWindow?.document.close(); |  | ||||||
|  |         contentDocument.close(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     renderBody() { |     renderBody() { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	