diff --git a/web/.babelrc b/web/.babelrc index fba4e89f0..6da7e1428 100644 --- a/web/.babelrc +++ b/web/.babelrc @@ -1,8 +1,5 @@ { - "presets": [ - "@babel/env", - "@babel/typescript" - ], + "presets": ["@babel/env", "@babel/typescript"], "plugins": [ ["@babel/plugin-proposal-private-methods", { "loose": true }], [ diff --git a/web/.eslintrc.json b/web/.eslintrc.json index 82e527d4f..c3943c953 100644 --- a/web/.eslintrc.json +++ b/web/.eslintrc.json @@ -14,11 +14,7 @@ "ecmaVersion": 12, "sourceType": "module" }, - "plugins": [ - "@typescript-eslint", - "lit", - "custom-elements" - ], + "plugins": ["@typescript-eslint", "lit", "custom-elements"], "rules": { "indent": "off", "linebreak-style": ["error", "unix"], diff --git a/web/.prettierignore b/web/.prettierignore new file mode 100644 index 000000000..d4e855680 --- /dev/null +++ b/web/.prettierignore @@ -0,0 +1,9 @@ +# don't ever lint node_modules +node_modules +# don't lint build output (make sure it's set to your correct build folder name) +dist +# don't lint nyc coverage output +coverage +# don't lint generated code +api/ +azure-pipelines.yml diff --git a/web/.prettierrc.json b/web/.prettierrc.json new file mode 100644 index 000000000..b16c4b41e --- /dev/null +++ b/web/.prettierrc.json @@ -0,0 +1,19 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "embeddedLanguageFormatting": "auto", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxBracketSameLine": false, + "jsxSingleQuote": false, + "printWidth": 100, + "proseWrap": "preserve", + "quoteProps": "consistent", + "requirePragma": false, + "semi": true, + "singleQuote": false, + "tabWidth": 4, + "trailingComma": "all", + "useTabs": false, + "vueIndentScriptAndStyle": false +} diff --git a/web/azure-pipelines.yml b/web/azure-pipelines.yml index 039e3608b..21bead4d5 100644 --- a/web/azure-pipelines.yml +++ b/web/azure-pipelines.yml @@ -49,6 +49,28 @@ stages: command: 'custom' workingDir: 'web/' customCommand: 'run lint' + - job: prettier + pool: + vmImage: 'ubuntu-latest' + steps: + - task: NodeTool@0 + inputs: + versionSpec: '16.x' + displayName: 'Install Node.js' + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifactName: 'ts_api_client' + path: "web/api/" + - task: Npm@1 + inputs: + command: 'install' + workingDir: 'web/' + - task: Npm@1 + inputs: + command: 'custom' + workingDir: 'web/' + customCommand: 'run prettier-check' - job: lit_analyse pool: vmImage: 'ubuntu-latest' diff --git a/web/package-lock.json b/web/package-lock.json index 7884dddfb..0cb76aa3d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -47,6 +47,7 @@ "lit-element": "^2.5.1", "lit-html": "^1.4.1", "moment": "^2.29.1", + "prettier": "^2.3.2", "rapidoc": "^9.0.0", "rollup": "^2.55.1", "rollup-plugin-commonjs": "^10.1.0", @@ -66,8 +67,8 @@ }, "api": { "name": "authentik-api", - "version": "0.0.1", - "dependencies": { + "version": "1.0.0", + "devDependencies": { "typescript": "^3.9.5" } }, @@ -75,6 +76,7 @@ "version": "3.9.9", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6405,6 +6407,17 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/pretty-format": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", @@ -10217,7 +10230,8 @@ "typescript": { "version": "3.9.9", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", - "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==" + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "dev": true } } }, @@ -12932,6 +12946,11 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, + "prettier": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==" + }, "pretty-format": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", diff --git a/web/package.json b/web/package.json index bd62da2aa..60f6563a9 100644 --- a/web/package.json +++ b/web/package.json @@ -8,7 +8,9 @@ "build": "lingui compile && rollup -c ./rollup.config.js", "watch": "lingui compile && rollup -c -w", "lint": "eslint . --max-warnings 0 --fix", - "lit-analyse": "lit-analyzer src" + "lit-analyse": "lit-analyzer src", + "prettier-check": "prettier --check .", + "prettier": "prettier --write ." }, "lingui": { "sourceLocale": "en", @@ -76,6 +78,7 @@ "lit-element": "^2.5.1", "lit-html": "^1.4.1", "moment": "^2.29.1", + "prettier": "^2.3.2", "rapidoc": "^9.0.0", "rollup": "^2.55.1", "rollup-plugin-commonjs": "^10.1.0", diff --git a/web/rollup.config.js b/web/rollup.config.js index 7e9a7650e..8d1d60e26 100644 --- a/web/rollup.config.js +++ b/web/rollup.config.js @@ -8,21 +8,37 @@ import copy from "rollup-plugin-copy"; import babel from "@rollup/plugin-babel"; import replace from "@rollup/plugin-replace"; -const extensions = [ - ".js", ".jsx", ".ts", ".tsx", -]; +const extensions = [".js", ".jsx", ".ts", ".tsx"]; const resources = [ { src: "node_modules/rapidoc/dist/rapidoc-min.js", dest: "dist/" }, - { src: "node_modules/@patternfly/patternfly/patternfly.min.css", dest: "dist/" }, - { src: "node_modules/@patternfly/patternfly/patternfly-base.css", dest: "dist/" }, - { src: "node_modules/@patternfly/patternfly/components/Page/page.css", dest: "dist/" }, - { src: "node_modules/@patternfly/patternfly/components/EmptyState/empty-state.css", dest: "dist/" }, - { src: "node_modules/@patternfly/patternfly/components/Spinner/spinner.css", dest: "dist/" }, + { + src: "node_modules/@patternfly/patternfly/patternfly.min.css", + dest: "dist/", + }, + { + src: "node_modules/@patternfly/patternfly/patternfly-base.css", + dest: "dist/", + }, + { + src: "node_modules/@patternfly/patternfly/components/Page/page.css", + dest: "dist/", + }, + { + src: "node_modules/@patternfly/patternfly/components/EmptyState/empty-state.css", + dest: "dist/", + }, + { + src: "node_modules/@patternfly/patternfly/components/Spinner/spinner.css", + dest: "dist/", + }, { src: "src/authentik.css", dest: "dist/" }, - { src: "node_modules/@patternfly/patternfly/assets/*", dest: "dist/assets/" }, + { + src: "node_modules/@patternfly/patternfly/assets/*", + dest: "dist/assets/", + }, { src: "src/assets/*", dest: "dist/assets" }, { src: "./icons/*", dest: "dist/assets/icons" }, ]; @@ -60,15 +76,15 @@ export default [ ], plugins: [ typescript({ - "declaration": true, - "outDir": "./api/dist/", + declaration: true, + outDir: "./api/dist/", }), isProdBuild && terser(), copy({ targets: [...resources], copyOnce: false, }), - ].filter(p => p), + ].filter((p) => p), watch: { clearScreen: false, }, @@ -81,14 +97,14 @@ export default [ format: "iife", file: "dist/poly.js", sourcemap: true, - } + }, ], plugins: [ cssimport(), resolve({ browser: true }), commonjs(), isProdBuild && terser(), - ].filter(p => p), + ].filter((p) => p), watch: { clearScreen: false, }, @@ -102,7 +118,7 @@ export default [ dir: "dist", sourcemap: true, manualChunks: manualChunks, - chunkFileNames: "admin-[name].js" + chunkFileNames: "admin-[name].js", }, ], plugins: [ @@ -116,11 +132,11 @@ export default [ }), replace({ "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"), - preventAssignment: true + "preventAssignment": true, }), sourcemaps(), isProdBuild && terser(), - ].filter(p => p), + ].filter((p) => p), watch: { clearScreen: false, }, @@ -134,7 +150,7 @@ export default [ dir: "dist", sourcemap: true, manualChunks: manualChunks, - chunkFileNames: "flow-[name].js" + chunkFileNames: "flow-[name].js", }, ], plugins: [ @@ -148,11 +164,11 @@ export default [ }), replace({ "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"), - preventAssignment: true + "preventAssignment": true, }), sourcemaps(), isProdBuild && terser(), - ].filter(p => p), + ].filter((p) => p), watch: { clearScreen: false, }, diff --git a/web/src/authentik.css b/web/src/authentik.css index dc1e637c0..6c5cb5a8a 100644 --- a/web/src/authentik.css +++ b/web/src/authentik.css @@ -169,7 +169,9 @@ body { color: var(--ak-dark-foreground) !important; } .pf-c-table__expandable-row.pf-m-expanded { - --pf-c-table__expandable-row--m-expanded--BorderBottomColor: var(--ak-dark-background-lighter); + --pf-c-table__expandable-row--m-expanded--BorderBottomColor: var( + --ak-dark-background-lighter + ); } /* tabs */ .pf-c-tabs { @@ -214,7 +216,8 @@ body { border-bottom: 0; } /* inputs */ - optgroup, option { + optgroup, + option { color: var(--ak-dark-foreground); } .pf-c-input-group { @@ -235,7 +238,10 @@ body { background-color: var(--ak-dark-background-light); } .pf-c-button.pf-m-control { - --pf-c-button--after--BorderColor: var(--ak-dark-background-lighter) var(--ak-dark-background-lighter) var(--pf-c-button--m-control--after--BorderBottomColor) var(--ak-dark-background-lighter); + --pf-c-button--after--BorderColor: var(--ak-dark-background-lighter) + var(--ak-dark-background-lighter) + var(--pf-c-button--m-control--after--BorderBottomColor) + var(--ak-dark-background-lighter); background-color: var(--ak-dark-background-light); color: var(--ak-dark-foreground); } diff --git a/web/src/common/ws.ts b/web/src/common/ws.ts index ffd1689a3..94c7bac58 100644 --- a/web/src/common/ws.ts +++ b/web/src/common/ws.ts @@ -8,7 +8,6 @@ export interface WSMessage { } export class WebsocketClient { - messageSocket?: WebSocket; retryDelay = 200; @@ -22,8 +21,9 @@ export class WebsocketClient { connect(): void { if (navigator.webdriver) return; - const wsUrl = `${window.location.protocol.replace("http", "ws")}//${window.location.host - }/ws/client/`; + const wsUrl = `${window.location.protocol.replace("http", "ws")}//${ + window.location.host + }/ws/client/`; this.messageSocket = new WebSocket(wsUrl); this.messageSocket.addEventListener("open", () => { console.debug(`authentik/ws: connected to ${wsUrl}`); @@ -34,7 +34,7 @@ export class WebsocketClient { if (this.retryDelay > 3000) { showMessage({ level: MessageLevel.error, - message: t`Connection error, reconnecting...` + message: t`Connection error, reconnecting...`, }); } setTimeout(() => { @@ -50,12 +50,11 @@ export class WebsocketClient { bubbles: true, composed: true, detail: data as WSMessage, - }) + }), ); }); this.messageSocket.addEventListener("error", () => { this.retryDelay = this.retryDelay * 2; }); } - } diff --git a/web/src/elements/CodeMirror.ts b/web/src/elements/CodeMirror.ts index 9b6a3acc0..657d50462 100644 --- a/web/src/elements/CodeMirror.ts +++ b/web/src/elements/CodeMirror.ts @@ -1,4 +1,12 @@ -import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; import CodeMirror from "codemirror"; import "codemirror/addon/display/autorefresh"; @@ -13,13 +21,13 @@ import "codemirror/mode/python/python.js"; import CodeMirrorStyle from "codemirror/lib/codemirror.css"; import CodeMirrorTheme from "codemirror/theme/monokai.css"; import CodeMirrorDialogStyle from "codemirror/addon/dialog/dialog.css"; -import CodeMirrorShowHintStyle from "codemirror/addon/hint/show-hint.css"; +import CodeMirrorShowHintStyle from "codemirror/addon/hint/show-hint.css"; import { ifDefined } from "lit-html/directives/if-defined"; import YAML from "yaml"; @customElement("ak-codemirror") export class CodeMirrorTextarea extends LitElement { - @property({type: Boolean}) + @property({ type: Boolean }) readOnly = false; @property() @@ -83,11 +91,17 @@ export class CodeMirrorTextarea extends LitElement { } static get styles(): CSSResult[] { - return [CodeMirrorStyle, CodeMirrorTheme, CodeMirrorDialogStyle, CodeMirrorShowHintStyle, css` - .CodeMirror-wrap pre { - word-break: break-word !important; - } - `]; + return [ + CodeMirrorStyle, + CodeMirrorTheme, + CodeMirrorDialogStyle, + CodeMirrorShowHintStyle, + css` + .CodeMirror-wrap pre { + word-break: break-word !important; + } + `, + ]; } firstUpdated(): void { @@ -102,7 +116,7 @@ export class CodeMirrorTextarea extends LitElement { readOnly: this.readOnly, autoRefresh: true, lineWrapping: true, - value: this._value + value: this._value, }); this.editor.on("blur", () => { this.editor?.save(); diff --git a/web/src/elements/Divider.ts b/web/src/elements/Divider.ts index d354139f2..a3451d82f 100644 --- a/web/src/elements/Divider.ts +++ b/web/src/elements/Divider.ts @@ -4,34 +4,36 @@ import AKGlobal from "../authentik.css"; @customElement("ak-divider") export class Divider extends LitElement { - static get styles(): CSSResult[] { - return [PFBase, AKGlobal, css` - .separator { - display: flex; - align-items: center; - text-align: center; - } + return [ + PFBase, + AKGlobal, + css` + .separator { + display: flex; + align-items: center; + text-align: center; + } - .separator::before, - .separator::after { - content: ''; - flex: 1; - border-bottom: 1px solid var(--pf-global--Color--100); - } + .separator::before, + .separator::after { + content: ""; + flex: 1; + border-bottom: 1px solid var(--pf-global--Color--100); + } - .separator:not(:empty)::before { - margin-right: .25em; - } + .separator:not(:empty)::before { + margin-right: 0.25em; + } - .separator:not(:empty)::after { - margin-left: .25em; - } - `]; + .separator:not(:empty)::after { + margin-left: 0.25em; + } + `, + ]; } render(): TemplateResult { return html`
`; } - } diff --git a/web/src/elements/EmptyState.ts b/web/src/elements/EmptyState.ts index 911befcbb..03eaf5f32 100644 --- a/web/src/elements/EmptyState.ts +++ b/web/src/elements/EmptyState.ts @@ -8,14 +8,13 @@ import { PFSize } from "./Spinner"; @customElement("ak-empty-state") export class EmptyState extends LitElement { - - @property({type: String}) + @property({ type: String }) icon = ""; - @property({type: Boolean}) + @property({ type: Boolean }) loading = false; - @property({type: Boolean}) + @property({ type: Boolean }) fullHeight = false; @property() @@ -28,14 +27,16 @@ export class EmptyState extends LitElement { render(): TemplateResult { return html`
- ${this.loading ? - html`
- -
`: - html``} -

- ${this.header} -

+ ${this.loading + ? html`
+ +
` + : html``} +

${this.header}

@@ -45,5 +46,4 @@ export class EmptyState extends LitElement {
`; } - } diff --git a/web/src/elements/Expand.ts b/web/src/elements/Expand.ts index 1343bd797..8d1c91af1 100644 --- a/web/src/elements/Expand.ts +++ b/web/src/elements/Expand.ts @@ -4,7 +4,6 @@ import PFExpandableSection from "../../node_modules/@patternfly/patternfly/compo @customElement("ak-expand") export class Expand extends LitElement { - @property({ type: Boolean }) expanded = false; @@ -20,16 +19,22 @@ export class Expand extends LitElement { render(): TemplateResult { return html`
-
`; } - } diff --git a/web/src/elements/Label.ts b/web/src/elements/Label.ts index b7783e884..87cc4c3bc 100644 --- a/web/src/elements/Label.ts +++ b/web/src/elements/Label.ts @@ -12,7 +12,6 @@ export enum PFColor { @customElement("ak-label") export class Label extends LitElement { - @property() color: PFColor = PFColor.Grey; @@ -45,11 +44,14 @@ export class Label extends LitElement { return html` - + ${this.text || ""} `; } - } diff --git a/web/src/elements/PageHeader.ts b/web/src/elements/PageHeader.ts index 4da4d8308..2a13c8936 100644 --- a/web/src/elements/PageHeader.ts +++ b/web/src/elements/PageHeader.ts @@ -1,4 +1,12 @@ -import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; import AKGlobal from "../authentik.css"; @@ -10,19 +18,18 @@ import { EventsApi } from "../../api/dist"; @customElement("ak-page-header") export class PageHeader extends LitElement { - @property() icon?: string; - @property({type: Boolean}) - iconImage = false + @property({ type: Boolean }) + iconImage = false; - @property({type: Boolean}) + @property({ type: Boolean }) hasNotifications = false; @property() set header(value: string) { - tenant().then(tenant => { + tenant().then((tenant) => { if (value !== "") { document.title = `${value} - ${tenant.brandingTitle}`; } else { @@ -42,33 +49,40 @@ export class PageHeader extends LitElement { _header = ""; static get styles(): CSSResult[] { - return [PFBase, PFButton, PFPage, PFContent, AKGlobal, css` - :host { - display: flex; - flex-direction: row; - min-height: 114px; - } - .pf-c-button.pf-m-plain { - background-color: var(--pf-c-page__main-section--m-light--BackgroundColor); - border-radius: 0px; - } - .pf-c-page__main-section { - flex-grow: 1; - display: flex; - flex-direction: column; - justify-content: center; - } - img.pf-icon { - max-height: 24px; - } - .sidebar-trigger, - .notification-trigger { - font-size: 24px; - } - .notification-trigger.has-notifications { - color: #2B9AF3; - } - `]; + return [ + PFBase, + PFButton, + PFPage, + PFContent, + AKGlobal, + css` + :host { + display: flex; + flex-direction: row; + min-height: 114px; + } + .pf-c-button.pf-m-plain { + background-color: var(--pf-c-page__main-section--m-light--BackgroundColor); + border-radius: 0px; + } + .pf-c-page__main-section { + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: center; + } + img.pf-icon { + max-height: 24px; + } + .sidebar-trigger, + .notification-trigger { + font-size: 24px; + } + .notification-trigger.has-notifications { + color: #2b9af3; + } + `, + ]; } renderIcon(): TemplateResult { @@ -82,50 +96,51 @@ export class PageHeader extends LitElement { } firstUpdated(): void { - new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({ - seen: false, - ordering: "-created", - pageSize: 1, - }).then(r => { - this.hasNotifications = r.pagination.count > 0; - }); + new EventsApi(DEFAULT_CONFIG) + .eventsNotificationsList({ + seen: false, + ordering: "-created", + pageSize: 1, + }) + .then((r) => { + this.hasNotifications = r.pagination.count > 0; + }); } render(): TemplateResult { return html` -
-
-

- ${this.renderIcon()} - ${this.header} -

- ${this.description ? - html`

${this.description}

` : html``} -
-
- `; + class="sidebar-trigger pf-c-button pf-m-plain" + @click=${() => { + this.dispatchEvent( + new CustomEvent(EVENT_SIDEBAR_TOGGLE, { + bubbles: true, + composed: true, + }), + ); + }} + > + + +
+
+

${this.renderIcon()} ${this.header}

+ ${this.description ? html`

${this.description}

` : html``} +
+
+ `; } - } diff --git a/web/src/elements/Spinner.ts b/web/src/elements/Spinner.ts index d4888c9bd..2443d2dcb 100644 --- a/web/src/elements/Spinner.ts +++ b/web/src/elements/Spinner.ts @@ -20,13 +20,13 @@ export class Spinner extends LitElement { render(): TemplateResult { return html` - - - - `; + class="pf-c-spinner ${this.size.toString()}" + role="progressbar" + aria-valuetext="${t`Loading...`}" + > + + + + `; } - } diff --git a/web/src/elements/Tabs.ts b/web/src/elements/Tabs.ts index bacec9690..56a0b57ad 100644 --- a/web/src/elements/Tabs.ts +++ b/web/src/elements/Tabs.ts @@ -1,4 +1,12 @@ -import { LitElement, html, customElement, property, CSSResult, TemplateResult, css } from "lit-element"; +import { + LitElement, + html, + customElement, + property, + CSSResult, + TemplateResult, + css, +} from "lit-element"; import { ifDefined } from "lit-html/directives/if-defined"; import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css"; import PFGlobal from "@patternfly/patternfly/patternfly-base.css"; @@ -11,24 +19,29 @@ export class Tabs extends LitElement { @property() currentPage?: string; - @property({type: Boolean}) + @property({ type: Boolean }) vertical = false; static get styles(): CSSResult[] { - return [PFGlobal, PFTabs, AKGlobal, css` - ::slotted(*) { - flex-grow: 2; - } - :host([vertical]) { - display: flex; - } - :host([vertical]) .pf-c-tabs { - width: auto !important; - } - :host([vertical]) .pf-c-tabs__list { - height: 100%; - } - `]; + return [ + PFGlobal, + PFTabs, + AKGlobal, + css` + ::slotted(*) { + flex-grow: 2; + } + :host([vertical]) { + display: flex; + } + :host([vertical]) .pf-c-tabs { + width: auto !important; + } + :host([vertical]) .pf-c-tabs__list { + height: 100%; + } + `, + ]; } observer: MutationObserver; @@ -42,7 +55,11 @@ export class Tabs extends LitElement { connectedCallback(): void { super.connectedCallback(); - this.observer.observe(this, { attributes: true, childList: true, subtree: true }); + this.observer.observe(this, { + attributes: true, + childList: true, + subtree: true, + }); } disconnectedCallback(): void { @@ -61,9 +78,7 @@ export class Tabs extends LitElement { const slot = page.attributes.getNamedItem("slot")?.value; return html`
  • `; } diff --git a/web/src/elements/buttons/ActionButton.ts b/web/src/elements/buttons/ActionButton.ts index 80d440ffd..c17d381a1 100644 --- a/web/src/elements/buttons/ActionButton.ts +++ b/web/src/elements/buttons/ActionButton.ts @@ -5,10 +5,11 @@ import { MessageLevel } from "../messages/Message"; @customElement("ak-action-button") export class ActionButton extends SpinnerButton { - - @property({attribute: false}) + @property({ attribute: false }) // eslint-disable-next-line @typescript-eslint/no-explicit-any - apiRequest: () => Promise = () => { throw new Error(); }; + apiRequest: () => Promise = () => { + throw new Error(); + }; callAction = (): Promise => { this.setLoading(); @@ -16,13 +17,13 @@ export class ActionButton extends SpinnerButton { if (e instanceof Error) { showMessage({ level: MessageLevel.error, - message: e.toString() + message: e.toString(), }); } else { - e.text().then(t => { + e.text().then((t) => { showMessage({ level: MessageLevel.error, - message: t + message: t, }); }); } diff --git a/web/src/elements/buttons/ModalButton.ts b/web/src/elements/buttons/ModalButton.ts index bd757bf94..bd69b8778 100644 --- a/web/src/elements/buttons/ModalButton.ts +++ b/web/src/elements/buttons/ModalButton.ts @@ -1,4 +1,12 @@ -import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFModalBox from "@patternfly/patternfly/components/ModalBox/modal-box.css"; @@ -35,11 +43,25 @@ export class ModalButton extends LitElement { @property() size: PFSize = PFSize.Large; - @property({type: Boolean}) + @property({ type: Boolean }) open = false; static get styles(): CSSResult[] { - return [PFBase, PFButton, PFModalBox, PFForm, PFTitle, PFFormControl, PFBullseye, PFBackdrop, PFPage, PFCard, PFContent, AKGlobal, MODAL_BUTTON_STYLES]; + return [ + PFBase, + PFButton, + PFModalBox, + PFForm, + PFTitle, + PFFormControl, + PFBullseye, + PFBackdrop, + PFPage, + PFCard, + PFContent, + AKGlobal, + MODAL_BUTTON_STYLES, + ]; } constructor() { @@ -53,7 +75,7 @@ export class ModalButton extends LitElement { } resetForms(): void { - this.querySelectorAll("[slot=form]").forEach(form => { + this.querySelectorAll("[slot=form]").forEach((form) => { if ("resetForm" in form) { form?.resetForm(); } @@ -62,7 +84,7 @@ export class ModalButton extends LitElement { onClick(): void { this.open = true; - this.querySelectorAll("*").forEach(child => { + this.querySelectorAll("*").forEach((child) => { if ("requestUpdate" in child) { (child as LitElement).requestUpdate(); } @@ -70,17 +92,13 @@ export class ModalButton extends LitElement { } renderModalInner(): TemplateResult { - return html``; + return html``; } renderModal(): TemplateResult { return html`
    - -
    - ${this.renderInner()} -
    +
    ${this.renderInner()}
    `; } - } diff --git a/web/src/elements/cards/AggregatePromiseCard.ts b/web/src/elements/cards/AggregatePromiseCard.ts index b36a189c9..4ac796b1c 100644 --- a/web/src/elements/cards/AggregatePromiseCard.ts +++ b/web/src/elements/cards/AggregatePromiseCard.ts @@ -6,14 +6,14 @@ import { PFSize } from "../Spinner"; @customElement("ak-aggregate-card-promise") export class AggregatePromiseCard extends AggregateCard { - @property({attribute: false}) + @property({ attribute: false }) promise?: Promise>; promiseProxy(): Promise { if (!this.promise) { return new Promise(() => html``); } - return this.promise.then(s => { + return this.promise.then((s) => { return html` ${s.toString()}`; }); } @@ -23,5 +23,4 @@ export class AggregatePromiseCard extends AggregateCard { ${until(this.promiseProxy(), html``)}

    `; } - } diff --git a/web/src/elements/charts/AdminLoginsChart.ts b/web/src/elements/charts/AdminLoginsChart.ts index 316cfc012..211471e15 100644 --- a/web/src/elements/charts/AdminLoginsChart.ts +++ b/web/src/elements/charts/AdminLoginsChart.ts @@ -6,7 +6,6 @@ import { DEFAULT_CONFIG } from "../../api/Config"; @customElement("ak-charts-admin-login") export class AdminLoginsChart extends AKChart { - apiRequest(): Promise { return new AdminApi(DEFAULT_CONFIG).adminMetricsRetrieve(); } @@ -18,26 +17,27 @@ export class AdminLoginsChart extends AKChart { label: "Failed Logins", backgroundColor: "rgba(201, 25, 11, .5)", spanGaps: true, - data: data.loginsFailedPer1h?.map((cord) => { - return { - x: cord.xCord || 0, - y: cord.yCord || 0, - }; - }) || [], + data: + data.loginsFailedPer1h?.map((cord) => { + return { + x: cord.xCord || 0, + y: cord.yCord || 0, + }; + }) || [], }, { label: "Successful Logins", backgroundColor: "rgba(189, 229, 184, .5)", spanGaps: true, - data: data.loginsPer1h?.map((cord) => { - return { - x: cord.xCord || 0, - y: cord.yCord || 0, - }; - }) || [], + data: + data.loginsPer1h?.map((cord) => { + return { + x: cord.xCord || 0, + y: cord.yCord || 0, + }; + }) || [], }, - ] + ], }; } - } diff --git a/web/src/elements/charts/ApplicationAuthorizeChart.ts b/web/src/elements/charts/ApplicationAuthorizeChart.ts index 861f51da7..3e39f5a05 100644 --- a/web/src/elements/charts/ApplicationAuthorizeChart.ts +++ b/web/src/elements/charts/ApplicationAuthorizeChart.ts @@ -6,12 +6,13 @@ import { ChartData } from "chart.js"; @customElement("ak-charts-application-authorize") export class ApplicationAuthorizeChart extends AKChart { - @property() applicationSlug!: string; apiRequest(): Promise { - return new CoreApi(DEFAULT_CONFIG).coreApplicationsMetricsList({ slug: this.applicationSlug }); + return new CoreApi(DEFAULT_CONFIG).coreApplicationsMetricsList({ + slug: this.applicationSlug, + }); } getChartData(data: Coordinate[]): ChartData { @@ -21,15 +22,15 @@ export class ApplicationAuthorizeChart extends AKChart { label: "Authorizations", backgroundColor: "rgba(189, 229, 184, .5)", spanGaps: true, - data: data.map((cord) => { - return { - x: cord.xCord || 0, - y: cord.yCord || 0, - }; - }) || [], + data: + data.map((cord) => { + return { + x: cord.xCord || 0, + y: cord.yCord || 0, + }; + }) || [], }, - ] + ], }; } - } diff --git a/web/src/elements/charts/Chart.ts b/web/src/elements/charts/Chart.ts index 926748b99..cd6c0635b 100644 --- a/web/src/elements/charts/Chart.ts +++ b/web/src/elements/charts/Chart.ts @@ -6,7 +6,7 @@ import { ArcElement, BarElement } from "chart.js"; import { TimeScale, LinearScale } from "chart.js"; import "chartjs-adapter-moment"; import { FONT_COLOUR_DARK_MODE, FONT_COLOUR_LIGHT_MODE } from "../../pages/flows/FlowDiagram"; -import {EVENT_REFRESH} from "../../constants"; +import { EVENT_REFRESH } from "../../constants"; Chart.register(Legend, Tooltip); Chart.register(LineController, BarController, DoughnutController); @@ -14,7 +14,6 @@ Chart.register(ArcElement, BarElement); Chart.register(TimeScale, LinearScale); export abstract class AKChart extends LitElement { - abstract apiRequest(): Promise; abstract getChartData(data: T): ChartData; @@ -26,15 +25,17 @@ export abstract class AKChart extends LitElement { fontColour = FONT_COLOUR_LIGHT_MODE; static get styles(): CSSResult[] { - return [css` - .container { - height: 100%; - } - canvas { - width: 100px; - height: 100px; - } - `]; + return [ + css` + .container { + height: 100%; + } + canvas { + width: 100px; + height: 100px; + } + `, + ]; } constructor() { @@ -99,12 +100,14 @@ export abstract class AKChart extends LitElement { chart.ctx.textBaseline = "middle"; chart.ctx.fillStyle = this.fontColour; - const textX = Math.round((width - chart.ctx.measureText(this.centerText).width) / 2); + const textX = Math.round( + (width - chart.ctx.measureText(this.centerText).width) / 2, + ); const textY = height / 2; chart.ctx.fillText(this.centerText, textX, textY); - } - } + }, + }, ]; } @@ -116,8 +119,12 @@ export abstract class AKChart extends LitElement { type: "time", display: true, ticks: { - callback: function (tickValue: string | number, index: number, ticks: Tick[]): string { - const valueStamp = (ticks[index]); + callback: function ( + tickValue: string | number, + index: number, + ticks: Tick[], + ): string { + const valueStamp = ticks[index]; const delta = Date.now() - valueStamp.value; const ago = Math.round(delta / 1000 / 3600); return `${ago} Hours ago`; @@ -129,7 +136,7 @@ export abstract class AKChart extends LitElement { grid: { color: "rgba(0, 0, 0, 0)", }, - offset: true + offset: true, }, y: { type: "linear", @@ -138,7 +145,7 @@ export abstract class AKChart extends LitElement { grid: { color: "rgba(0, 0, 0, 0)", }, - } + }, }, } as ChartOptions; } diff --git a/web/src/elements/charts/UserChart.ts b/web/src/elements/charts/UserChart.ts index 29dc8ef49..549eb3538 100644 --- a/web/src/elements/charts/UserChart.ts +++ b/web/src/elements/charts/UserChart.ts @@ -6,8 +6,7 @@ import { ChartData } from "chart.js"; @customElement("ak-charts-user") export class UserChart extends AKChart { - - @property({type: Number}) + @property({ type: Number }) userId?: number; apiRequest(): Promise { @@ -23,37 +22,39 @@ export class UserChart extends AKChart { label: "Failed Logins", backgroundColor: "rgba(201, 25, 11, .5)", spanGaps: true, - data: data.loginsFailedPer1h?.map((cord) => { - return { - x: cord.xCord || 0, - y: cord.yCord || 0, - }; - }) || [], + data: + data.loginsFailedPer1h?.map((cord) => { + return { + x: cord.xCord || 0, + y: cord.yCord || 0, + }; + }) || [], }, { label: "Successful Logins", backgroundColor: "rgba(189, 229, 184, .5)", spanGaps: true, - data: data.loginsPer1h?.map((cord) => { - return { - x: cord.xCord || 0, - y: cord.yCord || 0, - }; - }) || [], + data: + data.loginsPer1h?.map((cord) => { + return { + x: cord.xCord || 0, + y: cord.yCord || 0, + }; + }) || [], }, { label: "Application authorizations", backgroundColor: "rgba(43, 154, 243, .5)", spanGaps: true, - data: data.authorizationsPer1h?.map((cord) => { - return { - x: cord.xCord || 0, - y: cord.yCord || 0, - }; - }) || [], + data: + data.authorizationsPer1h?.map((cord) => { + return { + x: cord.xCord || 0, + y: cord.yCord || 0, + }; + }) || [], }, - ] + ], }; } - } diff --git a/web/src/elements/chips/Chip.ts b/web/src/elements/chips/Chip.ts index 58f544798..8a59418ee 100644 --- a/web/src/elements/chips/Chip.ts +++ b/web/src/elements/chips/Chip.ts @@ -7,11 +7,10 @@ import AKGlobal from "../../authentik.css"; @customElement("ak-chip") export class Chip extends LitElement { - @property() value?: number | string; - @property({type: Boolean}) + @property({ type: Boolean }) removable = false; static get styles(): CSSResult[] { @@ -24,16 +23,23 @@ export class Chip extends LitElement { - ${this.removable ? html`` : html``} + ${this.removable + ? html`` + : html``}
    `; } - } diff --git a/web/src/elements/chips/ChipGroup.ts b/web/src/elements/chips/ChipGroup.ts index dd6b131bd..a320458c1 100644 --- a/web/src/elements/chips/ChipGroup.ts +++ b/web/src/elements/chips/ChipGroup.ts @@ -9,7 +9,6 @@ import { Chip } from "./Chip"; @customElement("ak-chip-group") export class ChipGroup extends LitElement { - static get styles(): CSSResult[] { return [PFBase, PFChip, PFChipGroup, PFButton, AKGlobal]; } @@ -20,7 +19,7 @@ export class ChipGroup extends LitElement { get value(): (string | number | undefined)[] { const values: (string | number | undefined)[] = []; - this.querySelectorAll("ak-chip").forEach(chip => { + this.querySelectorAll("ak-chip").forEach((chip) => { values.push(chip.value); }); return values; @@ -28,12 +27,11 @@ export class ChipGroup extends LitElement { render(): TemplateResult { return html`
    -
    -
      - -
    -
    -
    `; +
    +
      + +
    +
    + `; } - } diff --git a/web/src/elements/events/ObjectChangelog.ts b/web/src/elements/events/ObjectChangelog.ts index be7bb6b8a..915133c84 100644 --- a/web/src/elements/events/ObjectChangelog.ts +++ b/web/src/elements/events/ObjectChangelog.ts @@ -55,32 +55,28 @@ export class ObjectChangelog extends Table { return [ html`${item.action}`, html`
    ${item.user?.username}
    - ${item.user.on_behalf_of ? html` - ${t`On behalf of ${item.user.on_behalf_of.username}`} - ` : html``}`, + ${item.user.on_behalf_of + ? html` ${t`On behalf of ${item.user.on_behalf_of.username}`} ` + : html``}`, html`${item.created?.toLocaleString()}`, html`${item.clientIp || "-"}`, ]; } renderExpanded(item: Event): TemplateResult { - return html` - -
    - -
    - - - - `; + return html` +
    + +
    + + + + `; } renderEmpty(): TemplateResult { return super.renderEmpty(html` -
    - ${t`No matching events could be found.`} -
    +
    ${t`No matching events could be found.`}
    `); } - } diff --git a/web/src/elements/events/UserEvents.ts b/web/src/elements/events/UserEvents.ts index 62d79ae00..de52de8cf 100644 --- a/web/src/elements/events/UserEvents.ts +++ b/web/src/elements/events/UserEvents.ts @@ -29,7 +29,7 @@ export class ObjectChangelog extends Table { page: page, ordering: this.order, pageSize: PAGE_SIZE / 2, - username: this.targetUser + username: this.targetUser, }); } @@ -46,32 +46,28 @@ export class ObjectChangelog extends Table { return [ html`${item.action}`, html`
    ${item.user?.username}
    - ${item.user.on_behalf_of ? html` - ${t`On behalf of ${item.user.on_behalf_of.username}`} - ` : html``}`, + ${item.user.on_behalf_of + ? html` ${t`On behalf of ${item.user.on_behalf_of.username}`} ` + : html``}`, html`${item.created?.toLocaleString()}`, html`${item.clientIp || "-"}`, ]; } renderExpanded(item: Event): TemplateResult { - return html` - -
    - -
    - - - - `; + return html` +
    + +
    + + + + `; } renderEmpty(): TemplateResult { return super.renderEmpty(html` -
    - ${t`No matching events could be found.`} -
    +
    ${t`No matching events could be found.`}
    `); } - } diff --git a/web/src/elements/forms/ConfirmationForm.ts b/web/src/elements/forms/ConfirmationForm.ts index dd44b7d8e..7bc4620ee 100644 --- a/web/src/elements/forms/ConfirmationForm.ts +++ b/web/src/elements/forms/ConfirmationForm.ts @@ -8,7 +8,6 @@ import { showMessage } from "../messages/MessageContainer"; @customElement("ak-forms-confirm") export class ConfirmationForm extends ModalButton { - @property() successMessage!: string; @property() @@ -17,23 +16,25 @@ export class ConfirmationForm extends ModalButton { @property() action!: string; - @property({attribute: false}) + @property({ attribute: false }) onConfirm!: () => Promise; confirm(): Promise { - return this.onConfirm().then(() => { - this.onSuccess(); - this.open = false; - this.dispatchEvent( - new CustomEvent(EVENT_REFRESH, { - bubbles: true, - composed: true, - }) - ); - }).catch((e) => { - this.onError(e); - throw e; - }); + return this.onConfirm() + .then(() => { + this.onSuccess(); + this.open = false; + this.dispatchEvent( + new CustomEvent(EVENT_REFRESH, { + bubbles: true, + composed: true, + }), + ); + }) + .catch((e) => { + this.onError(e); + throw e; + }); } onSuccess(): void { @@ -52,33 +53,34 @@ export class ConfirmationForm extends ModalButton { renderModalInner(): TemplateResult { return html`
    -
    -

    - -

    -
    -
    -
    -
    - -
    -
    -
    - { - return this.confirm(); - }} - class="pf-m-danger"> - ${this.action} -   - { - this.open = false; - }} - class="pf-m-secondary"> - ${t`Cancel`} - -
    `; +
    +

    + +

    +
    + +
    +
    + +
    +
    +
    + { + return this.confirm(); + }} + class="pf-m-danger" + > + ${this.action}   + { + this.open = false; + }} + class="pf-m-secondary" + > + ${t`Cancel`} + +
    `; } - } diff --git a/web/src/elements/forms/DeleteForm.ts b/web/src/elements/forms/DeleteForm.ts index 724070b1b..0e942525b 100644 --- a/web/src/elements/forms/DeleteForm.ts +++ b/web/src/elements/forms/DeleteForm.ts @@ -11,42 +11,43 @@ import { until } from "lit-html/directives/until"; @customElement("ak-forms-delete") export class DeleteForm extends ModalButton { - static get styles(): CSSResult[] { return super.styles.concat(PFList); } - @property({attribute: false}) + @property({ attribute: false }) obj?: Record; @property() objectLabel?: string; - @property({attribute: false}) + @property({ attribute: false }) usedBy?: () => Promise; - @property({attribute: false}) + @property({ attribute: false }) delete!: () => Promise; confirm(): Promise { - return this.delete().then(() => { - this.onSuccess(); - this.open = false; - this.dispatchEvent( - new CustomEvent(EVENT_REFRESH, { - bubbles: true, - composed: true, - }) - ); - }).catch((e) => { - this.onError(e); - throw e; - }); + return this.delete() + .then(() => { + this.onSuccess(); + this.open = false; + this.dispatchEvent( + new CustomEvent(EVENT_REFRESH, { + bubbles: true, + composed: true, + }), + ); + }) + .catch((e) => { + this.onError(e); + throw e; + }); } onSuccess(): void { showMessage({ - message: t`Successfully deleted ${this.objectLabel} ${ this.obj?.name }`, + message: t`Successfully deleted ${this.objectLabel} ${this.obj?.name}`, level: MessageLevel.success, }); } @@ -66,69 +67,70 @@ export class DeleteForm extends ModalButton { objName = ""; } return html`
    -
    -

    - ${t`Delete ${this.objectLabel}`} -

    -
    -
    -
    -
    -

    - ${t`Are you sure you want to delete ${this.objectLabel} ${objName} ?`} -

    -
    -
    - ${this.usedBy ? until(this.usedBy().then(usedBy => { - if (usedBy.length < 1) { - return html``; - } - return html` -
    -
    -

    - ${t`The following objects use ${objName} `} -

    -
      - ${usedBy.map(ub => { - let consequence = ""; - switch (ub.action) { - case UsedByActionEnum.Cascade: - consequence = t`object will be DELETED`; - break; - case UsedByActionEnum.CascadeMany: - consequence = t`connecting object will be deleted`; - break; - case UsedByActionEnum.SetDefault: - consequence = t`reference will be reset to default value`; - break; - case UsedByActionEnum.SetNull: - consequence = t`reference will be set to an empty value`; - break; - } - return html`
    • ${t`${ub.name} (${consequence})`}
    • `; - })} -
    -
    -
    - `; - })) : html``} -
    - { - return this.confirm(); - }} - class="pf-m-danger"> - ${t`Delete`} -   - { - this.open = false; - }} - class="pf-m-secondary"> - ${t`Cancel`} - -
    `; +
    +

    ${t`Delete ${this.objectLabel}`}

    +
    + +
    +
    +

    ${t`Are you sure you want to delete ${this.objectLabel} ${objName} ?`}

    +
    +
    + ${this.usedBy + ? until( + this.usedBy().then((usedBy) => { + if (usedBy.length < 1) { + return html``; + } + return html` +
    +
    +

    ${t`The following objects use ${objName} `}

    +
      + ${usedBy.map((ub) => { + let consequence = ""; + switch (ub.action) { + case UsedByActionEnum.Cascade: + consequence = t`object will be DELETED`; + break; + case UsedByActionEnum.CascadeMany: + consequence = t`connecting object will be deleted`; + break; + case UsedByActionEnum.SetDefault: + consequence = t`reference will be reset to default value`; + break; + case UsedByActionEnum.SetNull: + consequence = t`reference will be set to an empty value`; + break; + } + return html`
    • + ${t`${ub.name} (${consequence})`} +
    • `; + })} +
    +
    +
    + `; + }), + ) + : html``} +
    + { + return this.confirm(); + }} + class="pf-m-danger" + > + ${t`Delete`}   + { + this.open = false; + }} + class="pf-m-secondary" + > + ${t`Cancel`} + +
    `; } - } diff --git a/web/src/elements/forms/Form.ts b/web/src/elements/forms/Form.ts index 2d19c7cb8..4962b1227 100644 --- a/web/src/elements/forms/Form.ts +++ b/web/src/elements/forms/Form.ts @@ -2,7 +2,15 @@ import "@polymer/paper-input/paper-input"; import "@polymer/iron-form/iron-form"; import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { showMessage } from "../../elements/messages/MessageContainer"; -import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; @@ -18,31 +26,38 @@ import { ValidationError } from "authentik-api"; import { EVENT_REFRESH } from "../../constants"; export class APIError extends Error { - constructor(public response: ValidationError) { super(); } - } @customElement("ak-form") export class Form extends LitElement { - @property() successMessage = ""; @property() send!: (data: T) => Promise; - @property({attribute: false}) + @property({ attribute: false }) nonFieldErrors?: string[]; static get styles(): CSSResult[] { - return [PFBase, PFCard, PFButton, PFForm, PFAlert, PFInputGroup, PFFormControl, AKGlobal, css` - select[multiple] { - height: 15em; - } - `]; + return [ + PFBase, + PFCard, + PFButton, + PFForm, + PFAlert, + PFInputGroup, + PFFormControl, + AKGlobal, + css` + select[multiple] { + height: 15em; + } + `, + ]; } get isInViewport(): boolean { @@ -55,25 +70,27 @@ export class Form extends LitElement { } updated(): void { - this.shadowRoot?.querySelectorAll("input[name=name]").forEach(nameInput => { - const form = nameInput.closest("form"); - if (form === null) { - return; - } - const slugField = form.querySelector("input[name=slug]"); - if (!slugField) { - return; - } - // Only attach handler if the slug is already equal to the name - // if not, they are probably completely different and shouldn't update - // each other - if (convertToSlug(nameInput.value) !== slugField.value) { - return; - } - nameInput.addEventListener("input", () => { - slugField.value = convertToSlug(nameInput.value); + this.shadowRoot + ?.querySelectorAll("input[name=name]") + .forEach((nameInput) => { + const form = nameInput.closest("form"); + if (form === null) { + return; + } + const slugField = form.querySelector("input[name=slug]"); + if (!slugField) { + return; + } + // Only attach handler if the slug is already equal to the name + // if not, they are probably completely different and shouldn't update + // each other + if (convertToSlug(nameInput.value) !== slugField.value) { + return; + } + nameInput.addEventListener("input", () => { + slugField.value = convertToSlug(nameInput.value); + }); }); - }); } /** @@ -110,7 +127,7 @@ export class Form extends LitElement { serializeForm(form: IronFormElement): T { const elements: HTMLInputElement[] = form._getSubmittableElements(); const json: { [key: string]: unknown } = {}; - elements.forEach(element => { + elements.forEach((element) => { const values = form._serializeElementValues(element); if (element.hidden) { return; @@ -138,54 +155,58 @@ export class Form extends LitElement { return; } const data = this.serializeForm(ironForm); - return this.send(data).then((r) => { - showMessage({ - level: MessageLevel.success, - message: this.getSuccessMessage() - }); - this.dispatchEvent( - new CustomEvent(EVENT_REFRESH, { - bubbles: true, - composed: true, - }) - ); - return r; - }).catch((ex: Response | Error) => { - if (ex instanceof Error) { - throw ex; - } - if (ex.status > 399 && ex.status < 500) { - return ex.json().then((errorMessage: ValidationError) => { - if (!errorMessage) return errorMessage; - if (errorMessage instanceof Error) { - throw errorMessage; - } - // assign all input-related errors to their elements - const elements: PaperInputElement[] = ironForm._getSubmittableElements(); - elements.forEach((element) => { - const elementName = element.name; - if (!elementName) return; - if (camelToSnake(elementName) in errorMessage) { - element.errorMessage = errorMessage[camelToSnake(elementName)].join(", "); - element.invalid = true; - } - }); - if ("non_field_errors" in errorMessage) { - this.nonFieldErrors = errorMessage["non_field_errors"]; - } - throw new APIError(errorMessage); + return this.send(data) + .then((r) => { + showMessage({ + level: MessageLevel.success, + message: this.getSuccessMessage(), }); - } - throw ex; - }).catch((ex: Error) => { - // error is local or not from rest_framework - showMessage({ - message: ex.toString(), - level: MessageLevel.error, + this.dispatchEvent( + new CustomEvent(EVENT_REFRESH, { + bubbles: true, + composed: true, + }), + ); + return r; + }) + .catch((ex: Response | Error) => { + if (ex instanceof Error) { + throw ex; + } + if (ex.status > 399 && ex.status < 500) { + return ex.json().then((errorMessage: ValidationError) => { + if (!errorMessage) return errorMessage; + if (errorMessage instanceof Error) { + throw errorMessage; + } + // assign all input-related errors to their elements + const elements: PaperInputElement[] = ironForm._getSubmittableElements(); + elements.forEach((element) => { + const elementName = element.name; + if (!elementName) return; + if (camelToSnake(elementName) in errorMessage) { + element.errorMessage = + errorMessage[camelToSnake(elementName)].join(", "); + element.invalid = true; + } + }); + if ("non_field_errors" in errorMessage) { + this.nonFieldErrors = errorMessage["non_field_errors"]; + } + throw new APIError(errorMessage); + }); + } + throw ex; + }) + .catch((ex: Error) => { + // error is local or not from rest_framework + showMessage({ + message: ex.toString(), + level: MessageLevel.error, + }); + // rethrow the error so the form doesn't close + throw ex; }); - // rethrow the error so the form doesn't close - throw ex; - }); } renderForm(): TemplateResult { @@ -197,24 +218,24 @@ export class Form extends LitElement { return html``; } return html`
    - ${this.nonFieldErrors.map(err => { - return html`
    -
    - -
    -

    - ${err} -

    -
    `; - })} + ${this.nonFieldErrors.map((err) => { + return html`
    +
    + +
    +

    ${err}

    +
    `; + })}
    `; } renderVisible(): TemplateResult { return html` { this.submit(ev); }}> - ${this.renderNonFieldErrors()} - ${this.renderForm()} + @iron-form-presubmit=${(ev: Event) => { + this.submit(ev); + }} + > + ${this.renderNonFieldErrors()} ${this.renderForm()} `; } @@ -224,5 +245,4 @@ export class Form extends LitElement { } return this.renderVisible(); } - } diff --git a/web/src/elements/forms/FormElement.ts b/web/src/elements/forms/FormElement.ts index fbf805c81..00c48e1d4 100644 --- a/web/src/elements/forms/FormElement.ts +++ b/web/src/elements/forms/FormElement.ts @@ -6,16 +6,19 @@ import { ErrorDetail } from "authentik-api"; @customElement("ak-form-element") export class FormElement extends LitElement { - static get styles(): CSSResult[] { - return [PFForm, PFFormControl, css` - slot { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-around; - } - `]; + return [ + PFForm, + PFFormControl, + css` + slot { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-around; + } + `, + ]; } @property() @@ -28,22 +31,23 @@ export class FormElement extends LitElement { errors?: ErrorDetail[]; updated(): void { - this.querySelectorAll("input[autofocus]").forEach(input => { + this.querySelectorAll("input[autofocus]").forEach((input) => { input.focus(); }); } render(): TemplateResult { return html`
    - - - ${(this.errors || []).map((error) => { - return html`

    ${error.string}

    `; - })} -
    `; + + + ${(this.errors || []).map((error) => { + return html`

    ${error.string}

    `; + })} + `; } - } diff --git a/web/src/elements/forms/FormGroup.ts b/web/src/elements/forms/FormGroup.ts index 0109be284..c58f90ef8 100644 --- a/web/src/elements/forms/FormGroup.ts +++ b/web/src/elements/forms/FormGroup.ts @@ -1,4 +1,12 @@ -import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; @@ -7,25 +15,37 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css"; @customElement("ak-form-group") export class FormGroup extends LitElement { - @property({ type: Boolean }) expanded = false; static get styles(): CSSResult[] { - return [PFBase, PFForm, PFButton, PFFormControl, AKGlobal, css` - slot[name=body][hidden] { - display: none !important; - } - `]; + return [ + PFBase, + PFForm, + PFButton, + PFFormControl, + AKGlobal, + css` + slot[name="body"][hidden] { + display: none !important; + } + `, + ]; } render(): TemplateResult { return html`
    -
    `; } - } diff --git a/web/src/elements/forms/HorizontalFormElement.ts b/web/src/elements/forms/HorizontalFormElement.ts index d7242f540..476ad3f96 100644 --- a/web/src/elements/forms/HorizontalFormElement.ts +++ b/web/src/elements/forms/HorizontalFormElement.ts @@ -8,17 +8,24 @@ import { t } from "@lingui/macro"; @customElement("ak-form-element-horizontal") export class HorizontalFormElement extends LitElement { - static get styles(): CSSResult[] { - return [PFBase, PFForm, PFFormControl, AKGlobal, css` - .pf-c-form__group { - display: grid; - grid-template-columns: var(--pf-c-form--m-horizontal__group-label--md--GridColumnWidth) var(--pf-c-form--m-horizontal__group-control--md--GridColumnWidth); - } - .pf-c-form__group-label { - padding-top: var(--pf-c-form--m-horizontal__group-label--md--PaddingTop); - } - `]; + return [ + PFBase, + PFForm, + PFFormControl, + AKGlobal, + css` + .pf-c-form__group { + display: grid; + grid-template-columns: + var(--pf-c-form--m-horizontal__group-label--md--GridColumnWidth) + var(--pf-c-form--m-horizontal__group-control--md--GridColumnWidth); + } + .pf-c-form__group-label { + padding-top: var(--pf-c-form--m-horizontal__group-label--md--PaddingTop); + } + `, + ]; } @property() @@ -43,7 +50,7 @@ export class HorizontalFormElement extends LitElement { name = ""; updated(): void { - this.querySelectorAll("input[autofocus]").forEach(input => { + this.querySelectorAll("input[autofocus]").forEach((input) => { input.focus(); }); this.querySelectorAll("*").forEach((input) => { @@ -59,7 +66,7 @@ export class HorizontalFormElement extends LitElement { return; } if (this.writeOnly && !this.writeOnlyActivated) { - const i = (input as HTMLInputElement); + const i = input as HTMLInputElement; i.setAttribute("hidden", "true"); const handler = () => { i.removeAttribute("hidden"); @@ -76,24 +83,36 @@ export class HorizontalFormElement extends LitElement {
    - ${this.writeOnly && !this.writeOnlyActivated ? - html`
    - -
    ` : - html``} + ${this.writeOnly && !this.writeOnlyActivated + ? html`
    + +
    ` + : html``}
    - ${this.writeOnly ? html`

    ${ - t`Click to change value` - }

    ` : html``} - ${this.invalid ? html`

    ${this.errorMessage}

    ` : html``} + ${this.writeOnly + ? html`

    + ${t`Click to change value`} +

    ` + : html``} + ${this.invalid + ? html`

    + ${this.errorMessage} +

    ` + : html``}
    `; } - } diff --git a/web/src/elements/forms/ModalForm.ts b/web/src/elements/forms/ModalForm.ts index 6510e85f6..85443a8ff 100644 --- a/web/src/elements/forms/ModalForm.ts +++ b/web/src/elements/forms/ModalForm.ts @@ -7,11 +7,10 @@ import "../buttons/SpinnerButton"; @customElement("ak-forms-modal") export class ModalForm extends ModalButton { - @property({ type: Boolean }) closeAfterSuccessfulSubmit = true; - confirm(): Promise { + confirm(): Promise { const form = this.querySelector>("[slot=form]"); if (!form) { return Promise.reject(t`No form found`); @@ -29,39 +28,40 @@ export class ModalForm extends ModalButton { new CustomEvent(EVENT_REFRESH, { bubbles: true, composed: true, - }) + }), ); }); } renderModalInner(): TemplateResult { return html`
    -
    -

    - -

    -
    -
    -
    - -
    -
    - { - return this.confirm(); - }} - class="pf-m-primary"> - -   - { - this.resetForms(); - this.open = false; - }} - class="pf-m-secondary"> - ${t`Cancel`} - -
    `; +
    +

    + +

    +
    + +
    + +
    +
    + { + return this.confirm(); + }} + class="pf-m-primary" + > +   + { + this.resetForms(); + this.open = false; + }} + class="pf-m-secondary" + > + ${t`Cancel`} + +
    `; } - } diff --git a/web/src/elements/forms/ModelForm.ts b/web/src/elements/forms/ModelForm.ts index 59d06dd38..edb78d483 100644 --- a/web/src/elements/forms/ModelForm.ts +++ b/web/src/elements/forms/ModelForm.ts @@ -3,14 +3,13 @@ import { EVENT_REFRESH } from "../../constants"; import { Form } from "./Form"; export abstract class ModelForm extends Form { - abstract loadInstance(pk: PKT): Promise; - @property({attribute: false}) + @property({ attribute: false }) set instancePk(value: PKT) { this._instancePk = value; if (this.isInViewport) { - this.loadInstance(value).then(instance => { + this.loadInstance(value).then((instance) => { this.instance = instance; this.requestUpdate(); }); @@ -32,7 +31,7 @@ export abstract class ModelForm extends Form super(); this.addEventListener(EVENT_REFRESH, () => { if (!this._instancePk) return; - this.loadInstance(this._instancePk).then(instance => { + this.loadInstance(this._instancePk).then((instance) => { this.instance = instance; }); }); @@ -51,5 +50,4 @@ export abstract class ModelForm extends Form } return super.render(); } - } diff --git a/web/src/elements/forms/ProxyForm.ts b/web/src/elements/forms/ProxyForm.ts index 45f1f9d5f..32159572a 100644 --- a/web/src/elements/forms/ProxyForm.ts +++ b/web/src/elements/forms/ProxyForm.ts @@ -3,14 +3,13 @@ import { Form } from "./Form"; @customElement("ak-proxy-form") export class ProxyForm extends Form { - @property() type!: string; - @property({attribute: false}) + @property({ attribute: false }) args: Record = {}; - @property({attribute: false}) + @property({ attribute: false }) typeMap: Record = {}; submit(ev: Event): Promise | undefined { @@ -43,5 +42,4 @@ export class ProxyForm extends Form { } return html`${el}`; } - } diff --git a/web/src/elements/messages/Message.ts b/web/src/elements/messages/Message.ts index f4e0bf60e..8e191a2eb 100644 --- a/web/src/elements/messages/Message.ts +++ b/web/src/elements/messages/Message.ts @@ -9,7 +9,7 @@ export enum MessageLevel { error = "error", warning = "warning", success = "success", - info = "info" + info = "info", } export interface APIMessage { level: MessageLevel; @@ -27,14 +27,13 @@ const LEVEL_ICON_MAP: { [key: string]: string } = { @customElement("ak-message") export class Message extends LitElement { - - @property({attribute: false}) + @property({ attribute: false }) message?: APIMessage; - @property({type: Number}) + @property({ type: Number }) removeAfter = 8000; - @property({attribute: false}) + @property({ attribute: false }) onRemove?: (m: APIMessage) => void; static get styles(): CSSResult[] { @@ -51,27 +50,34 @@ export class Message extends LitElement { render(): TemplateResult { return html`
  • -
    +
    -

    - ${this.message?.message} -

    - ${this.message?.description && html`
    +

    ${this.message?.message}

    + ${this.message?.description && + html`

    ${this.message.description}

    `}
    -
  • `; } - } diff --git a/web/src/elements/messages/MessageContainer.ts b/web/src/elements/messages/MessageContainer.ts index a036d2f11..1f93d1510 100644 --- a/web/src/elements/messages/MessageContainer.ts +++ b/web/src/elements/messages/MessageContainer.ts @@ -1,4 +1,12 @@ -import { LitElement, html, customElement, TemplateResult, property, CSSResult, css } from "lit-element"; +import { + LitElement, + html, + customElement, + TemplateResult, + property, + CSSResult, + css, +} from "lit-element"; import "./Message"; import { APIMessage } from "./Message"; import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css"; @@ -17,17 +25,20 @@ export function showMessage(message: APIMessage): void { @customElement("ak-message-container") export class MessageContainer extends LitElement { - - @property({attribute: false}) + @property({ attribute: false }) messages: APIMessage[] = []; static get styles(): CSSResult[] { - return [PFBase, PFAlertGroup, css` - /* Fix spacing between messages */ - ak-message { - display: block; - } - `]; + return [ + PFBase, + PFAlertGroup, + css` + /* Fix spacing between messages */ + ak-message { + display: block; + } + `, + ]; } constructor() { @@ -40,7 +51,7 @@ export class MessageContainer extends LitElement { // add a new message, but only if the message isn't currently shown. addMessage(message: APIMessage): void { - const matchingMessages = this.messages.filter(m => m.message == message.message); + const matchingMessages = this.messages.filter((m) => m.message == message.message); if (matchingMessages.length < 1) { this.messages.push(message); } @@ -54,9 +65,10 @@ export class MessageContainer extends LitElement { .onRemove=${(m: APIMessage) => { this.messages = this.messages.filter((v) => v !== m); this.requestUpdate(); - }}> - `; - })} + }} + > + `; + })} `; } } diff --git a/web/src/elements/messages/Middleware.ts b/web/src/elements/messages/Middleware.ts index 88ec39cc5..3b3d8c102 100644 --- a/web/src/elements/messages/Middleware.ts +++ b/web/src/elements/messages/Middleware.ts @@ -4,13 +4,12 @@ import { MessageLevel } from "./Message"; import { showMessage } from "./MessageContainer"; export class MessageMiddleware implements Middleware { - post(context: ResponseContext): Promise { if (context.response.status >= 500) { showMessage({ level: MessageLevel.error, message: t`API request failed`, - description: `${context.init.method} ${context.url}: ${context.response.status}` + description: `${context.init.method} ${context.url}: ${context.response.status}`, }); } return Promise.resolve(context.response); diff --git a/web/src/elements/notifications/APIDrawer.ts b/web/src/elements/notifications/APIDrawer.ts index a14ff7030..27e9c7f00 100644 --- a/web/src/elements/notifications/APIDrawer.ts +++ b/web/src/elements/notifications/APIDrawer.ts @@ -32,7 +32,7 @@ export class APIMiddleware implements Middleware { new CustomEvent(EVENT_API_DRAWER_REFRESH, { bubbles: true, composed: true, - }) + }), ); return Promise.resolve(context.response); } @@ -43,7 +43,6 @@ export const API_DRAWER_MIDDLEWARE = new APIMiddleware(); @customElement("ak-api-drawer") export class APIDrawer extends LitElement { - static get styles(): CSSResult[] { return [PFBase, PFNotificationDrawer, PFContent, PFDropdown, AKGlobal]; } @@ -58,9 +57,7 @@ export class APIDrawer extends LitElement { renderItem(item: RequestInfo): TemplateResult { return html`
  • -

    - ${item.method} -

    +

    ${item.method}

    ${item.path}

  • `; @@ -70,17 +67,14 @@ export class APIDrawer extends LitElement { return html`
    -

    - ${t`API Requests`} -

    +

    ${t`API Requests`}

      - ${API_DRAWER_MIDDLEWARE.requests.map(n => this.renderItem(n))} + ${API_DRAWER_MIDDLEWARE.requests.map((n) => this.renderItem(n))}
    `; } - } diff --git a/web/src/elements/notifications/NotificationDrawer.ts b/web/src/elements/notifications/NotificationDrawer.ts index 08b3a7032..6eccdc95c 100644 --- a/web/src/elements/notifications/NotificationDrawer.ts +++ b/web/src/elements/notifications/NotificationDrawer.ts @@ -1,5 +1,13 @@ import { t } from "@lingui/macro"; -import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; import { EventsApi, Notification } from "authentik-api"; import { AKResponse } from "../../api/Client"; import { DEFAULT_CONFIG } from "../../api/Config"; @@ -14,11 +22,10 @@ import { ActionToLabel } from "../../pages/events/utils"; @customElement("ak-notification-drawer") export class NotificationDrawer extends LitElement { - - @property({attribute: false}) + @property({ attribute: false }) notifications?: AKResponse; - @property({type: Number}) + @property({ type: Number }) unread = 0; static get styles(): CSSResult[] { @@ -36,34 +43,36 @@ export class NotificationDrawer extends LitElement { .pf-c-notification-drawer__list-item-description { white-space: pre-wrap; } - ` + `, ); } firstUpdated(): void { - new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({ - seen: false, - ordering: "-created", - }).then(r => { - this.notifications = r; - this.unread = r.results.length; - }); + new EventsApi(DEFAULT_CONFIG) + .eventsNotificationsList({ + seen: false, + ordering: "-created", + }) + .then((r) => { + this.notifications = r; + this.unread = r.results.length; + }); } renderItem(item: Notification): TemplateResult { let level = ""; switch (item.severity) { - case "notice": - level = "pf-m-info"; - break; - case "warning": - level = "pf-m-warning"; - break; - case "alert": - level = "pf-m-danger"; - break; - default: - break; + case "notice": + level = "pf-m-info"; + break; + case "warning": + level = "pf-m-warning"; + break; + case "alert": + level = "pf-m-danger"; + break; + default: + break; } return html`
  • @@ -75,26 +84,38 @@ export class NotificationDrawer extends LitElement {
    - ${item.event && html` - + ${item.event && + html` + `} -

    ${item.body}

    - ${item.created?.toLocaleString()} + ${item.created?.toLocaleString()}
  • `; } @@ -106,12 +127,8 @@ export class NotificationDrawer extends LitElement {
    -

    - ${t`Notifications`} -

    - - ${t`${this.unread} unread`} - +

    ${t`Notifications`}

    + ${t`${this.unread} unread`}
    @@ -121,12 +138,13 @@ export class NotificationDrawer extends LitElement { new CustomEvent(EVENT_NOTIFICATION_TOGGLE, { bubbles: true, composed: true, - }) + }), ); }} class="pf-c-button pf-m-plain" type="button" - aria-label="Close"> + aria-label="Close" + >
    @@ -134,11 +152,10 @@ export class NotificationDrawer extends LitElement {
      - ${this.notifications.results.map(n => this.renderItem(n))} + ${this.notifications.results.map((n) => this.renderItem(n))}
    `; } - } diff --git a/web/src/elements/oauth/UserCodeList.ts b/web/src/elements/oauth/UserCodeList.ts index 89d82d358..dbe79ce1c 100644 --- a/web/src/elements/oauth/UserCodeList.ts +++ b/web/src/elements/oauth/UserCodeList.ts @@ -35,30 +35,27 @@ export class UserOAuthCodeList extends Table { row(item: ExpiringBaseGrantModel): TemplateResult[] { return [ - html` - ${item.provider?.name} - `, + html` ${item.provider?.name} `, html`${item.expires?.toLocaleString()}`, html`${item.scope.join(", ")}`, - html` - { return new Oauth2Api(DEFAULT_CONFIG).oauth2AuthorizationCodesUsedByList({ - id: item.pk + id: item.pk, }); }} .delete=${() => { return new Oauth2Api(DEFAULT_CONFIG).oauth2AuthorizationCodesDestroy({ id: item.pk, }); - }}> + }} + > `, ]; } - } diff --git a/web/src/elements/oauth/UserRefreshList.ts b/web/src/elements/oauth/UserRefreshList.ts index 7356eb6d3..1777e60fd 100644 --- a/web/src/elements/oauth/UserRefreshList.ts +++ b/web/src/elements/oauth/UserRefreshList.ts @@ -42,49 +42,45 @@ export class UserOAuthRefreshList extends Table { } renderExpanded(item: RefreshTokenModel): TemplateResult { - return html` - -
    -
    -
    -

    ${t`ID Token`}

    -
    ${item.idToken}
    + return html` +
    +
    +
    +

    ${t`ID Token`}

    +
    ${item.idToken}
    +
    -
    - - - - `; + + + + `; } row(item: RefreshTokenModel): TemplateResult[] { return [ - html` - ${item.provider?.name} - `, + html` ${item.provider?.name} `, html`${item.revoked ? t`Yes` : t`No`}`, html`${item.expires?.toLocaleString()}`, html`${item.scope.join(", ")}`, - html` - { return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensUsedByList({ - id: item.pk + id: item.pk, }); }} .delete=${() => { return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensDestroy({ id: item.pk, }); - }}> + }} + > `, ]; } - } diff --git a/web/src/elements/router/RouteMatch.ts b/web/src/elements/router/RouteMatch.ts index 7f4814672..046b3e014 100644 --- a/web/src/elements/router/RouteMatch.ts +++ b/web/src/elements/router/RouteMatch.ts @@ -3,7 +3,7 @@ import { Route } from "./Route"; export class RouteMatch { route: Route; - arguments: { [key: string]: string; }; + arguments: { [key: string]: string }; fullUrl?: string; constructor(route: Route) { @@ -16,6 +16,8 @@ export class RouteMatch { } toString(): string { - return ``; + return ``; } } diff --git a/web/src/elements/router/Router404.ts b/web/src/elements/router/Router404.ts index 70e618694..2c3e2200a 100644 --- a/web/src/elements/router/Router404.ts +++ b/web/src/elements/router/Router404.ts @@ -6,7 +6,6 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; @customElement("ak-router-404") export class Router404 extends LitElement { - @property() url = ""; @@ -19,9 +18,7 @@ export class Router404 extends LitElement {

    ${t`Not found`}

    -
    - ${t`The URL "${this.url}" was not found.`} -
    +
    ${t`The URL "${this.url}" was not found.`}
    ${t`Return home`}
    `; diff --git a/web/src/elements/router/RouterOutlet.ts b/web/src/elements/router/RouterOutlet.ts index ee92853e8..981d0d28a 100644 --- a/web/src/elements/router/RouterOutlet.ts +++ b/web/src/elements/router/RouterOutlet.ts @@ -1,4 +1,12 @@ -import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; import { Route } from "./Route"; import { ROUTES } from "../../routes"; import { RouteMatch } from "./RouteMatch"; @@ -10,26 +18,36 @@ import { ROUTE_SEPARATOR } from "../../constants"; // Poliyfill for hashchange.newURL, // https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onhashchange window.addEventListener("load", () => { - if (!window.HashChangeEvent) (function () { - let lastURL = document.URL; - window.addEventListener("hashchange", function (event) { - Object.defineProperty(event, "oldURL", { enumerable: true, configurable: true, value: lastURL }); - Object.defineProperty(event, "newURL", { enumerable: true, configurable: true, value: document.URL }); - lastURL = document.URL; - }); - }()); + if (!window.HashChangeEvent) + (function () { + let lastURL = document.URL; + window.addEventListener("hashchange", function (event) { + Object.defineProperty(event, "oldURL", { + enumerable: true, + configurable: true, + value: lastURL, + }); + Object.defineProperty(event, "newURL", { + enumerable: true, + configurable: true, + value: document.URL, + }); + lastURL = document.URL; + }); + })(); }); @customElement("ak-router-outlet") export class RouterOutlet extends LitElement { - @property({attribute: false}) + @property({ attribute: false }) current?: RouteMatch; @property() defaultUrl?: string; static get styles(): CSSResult[] { - return [AKGlobal, + return [ + AKGlobal, css` :host { height: 100vh; @@ -88,7 +106,7 @@ export class RouterOutlet extends LitElement { RegExp(""), html`
    -
    ` +
    `, ); matchedRoute = new RouteMatch(route); matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {}; diff --git a/web/src/elements/sidebar/Sidebar.ts b/web/src/elements/sidebar/Sidebar.ts index 5d2f6e076..a86ad4139 100644 --- a/web/src/elements/sidebar/Sidebar.ts +++ b/web/src/elements/sidebar/Sidebar.ts @@ -9,7 +9,6 @@ import "./SidebarUser"; @customElement("ak-sidebar") export class Sidebar extends LitElement { - static get styles(): CSSResult[] { return [ PFBase, diff --git a/web/src/elements/sidebar/SidebarBrand.ts b/web/src/elements/sidebar/SidebarBrand.ts index c4fd296d1..7a324ee3c 100644 --- a/web/src/elements/sidebar/SidebarBrand.ts +++ b/web/src/elements/sidebar/SidebarBrand.ts @@ -1,4 +1,12 @@ -import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGlobal from "@patternfly/patternfly/patternfly-base.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; @@ -25,7 +33,7 @@ export const DefaultTenant: CurrentTenant = { @customElement("ak-sidebar-brand") export class SidebarBrand extends LitElement { - @property({attribute: false}) + @property({ attribute: false }) tenant: CurrentTenant = DefaultTenant; static get styles(): CSSResult[] { @@ -45,7 +53,7 @@ export class SidebarBrand extends LitElement { } .pf-c-brand img { width: 100%; - padding: 0 .5rem; + padding: 0 0.5rem; height: 42px; } button.pf-c-button.sidebar-trigger { @@ -67,28 +75,34 @@ export class SidebarBrand extends LitElement { firstUpdated(): void { configureSentry(true); - tenant().then(tenant => this.tenant = tenant); + tenant().then((tenant) => (this.tenant = tenant)); } render(): TemplateResult { - return html` - ${window.innerWidth <= MIN_WIDTH ? html` - - ` : html``} + return html` ${window.innerWidth <= MIN_WIDTH + ? html` + + ` + : html``}
    - authentik icon + authentik icon
    `; } diff --git a/web/src/elements/sidebar/SidebarItem.ts b/web/src/elements/sidebar/SidebarItem.ts index 7ac55525d..3d1ebb184 100644 --- a/web/src/elements/sidebar/SidebarItem.ts +++ b/web/src/elements/sidebar/SidebarItem.ts @@ -9,7 +9,6 @@ import { ROUTE_SEPARATOR } from "../../constants"; @customElement("ak-sidebar-item") export class SidebarItem extends LitElement { - static get styles(): CSSResult[] { return [ PFBase, @@ -25,7 +24,7 @@ export class SidebarItem extends LitElement { background-color: var(--ak-accent); margin: 16px; } - :host([highlight]) .pf-c-nav__item .pf-c-nav__link { + :host([highlight]) .pf-c-nav__item .pf-c-nav__link { padding-left: 0.5rem; } .pf-c-nav__link.pf-m-current::after, @@ -92,13 +91,13 @@ export class SidebarItem extends LitElement { get childItems(): SidebarItem[] { const children = Array.from(this.querySelectorAll("ak-sidebar-item") || []); - children.forEach(child => child.parent = this); + children.forEach((child) => (child.parent = this)); return children; } - @property({attribute: false}) + @property({ attribute: false }) set activeWhen(regexp: string[]) { - regexp.forEach(r => { + regexp.forEach((r) => { this.activeMatchers.push(new RegExp(r)); }); } @@ -110,7 +109,7 @@ export class SidebarItem extends LitElement { onHashChange(): void { const activePath = window.location.hash.slice(1, Infinity).split(ROUTE_SEPARATOR)[0]; - this.childItems.forEach(item => { + this.childItems.forEach((item) => { this.expandParentRecursive(activePath, item); }); this.isActive = this.matchesPath(activePath); @@ -125,7 +124,7 @@ export class SidebarItem extends LitElement { return true; } } - return this.activeMatchers.some(v => { + return this.activeMatchers.some((v) => { const match = v.exec(path); if (match !== null) { return true; @@ -138,7 +137,7 @@ export class SidebarItem extends LitElement { item.parent.expanded = true; this.requestUpdate(); } - item.childItems.forEach(i => this.expandParentRecursive(activePath, i)); + item.childItems.forEach((i) => this.expandParentRecursive(activePath, i)); } render(): TemplateResult { @@ -153,10 +152,16 @@ export class SidebarItem extends LitElement { } } if (this.childItems.length > 0) { - return html`
  • -
  • `; } return html`
  • - ${this.path ? html` - - - - ` : html` - - - - `} + ${this.path + ? html` + + + + ` + : html` + + + + `}
  • `; } } diff --git a/web/src/elements/sidebar/SidebarUser.ts b/web/src/elements/sidebar/SidebarUser.ts index b053a91d4..90bc3161b 100644 --- a/web/src/elements/sidebar/SidebarUser.ts +++ b/web/src/elements/sidebar/SidebarUser.ts @@ -9,7 +9,6 @@ import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-sidebar-user") export class SidebarUser extends LitElement { - static get styles(): CSSResult[] { return [ PFBase, @@ -34,9 +33,16 @@ export class SidebarUser extends LitElement { render(): TemplateResult { return html` - ${until(me().then((u) => { - return html``; - }), html``)} + ${until( + me().then((u) => { + return html``; + }), + html``, + )} diff --git a/web/src/elements/table/Table.ts b/web/src/elements/table/Table.ts index d65b5e6a9..a73d39ad4 100644 --- a/web/src/elements/table/Table.ts +++ b/web/src/elements/table/Table.ts @@ -20,7 +20,6 @@ import { EVENT_REFRESH } from "../../constants"; import { ifDefined } from "lit-html/directives/if-defined"; export class TableColumn { - title: string; orderBy?: string; @@ -45,25 +44,27 @@ export class TableColumn { private getSortIndicator(table: Table): string { switch (table.order) { - case this.orderBy: - return "fa-long-arrow-alt-down"; - case `-${this.orderBy}`: - return "fa-long-arrow-alt-up"; - default: - return "fa-arrows-alt-v"; + case this.orderBy: + return "fa-long-arrow-alt-down"; + case `-${this.orderBy}`: + return "fa-long-arrow-alt-up"; + default: + return "fa-arrows-alt-v"; } } renderSortable(table: Table): TemplateResult { - return html` - `; + return html` `; } render(table: Table): TemplateResult { @@ -72,12 +73,14 @@ export class TableColumn { scope="col" class=" ${this.orderBy ? "pf-c-table__sort " : " "} - ${(table.order === this.orderBy || table.order === `-${this.orderBy}`) ? "pf-m-selected " : ""} - "> + ${table.order === this.orderBy || table.order === `-${this.orderBy}` + ? "pf-m-selected " + : ""} + " + > ${this.orderBy ? this.renderSortable(table) : html`${this.title}`} `; } - } export abstract class Table extends LitElement { @@ -99,32 +102,41 @@ export abstract class Table extends LitElement { return html``; } - @property({attribute: false}) + @property({ attribute: false }) data?: AKResponse; - @property({type: Number}) + @property({ type: Number }) page = 1; - @property({type: String}) + @property({ type: String }) order?: string; - @property({type: String}) + @property({ type: String }) search?: string; - @property({type: Boolean}) + @property({ type: Boolean }) checkbox = false; - @property({attribute: false}) + @property({ attribute: false }) selectedElements: T[] = []; - @property({type: Boolean}) + @property({ type: Boolean }) expandable = false; - @property({attribute: false}) + @property({ attribute: false }) expandedRows: boolean[] = []; static get styles(): CSSResult[] { - return [PFBase, PFTable, PFBullseye, PFButton, PFToolbar, PFDropdown, PFPagination, AKGlobal]; + return [ + PFBase, + PFTable, + PFBullseye, + PFButton, + PFToolbar, + PFDropdown, + PFPagination, + AKGlobal, + ]; } constructor() { @@ -139,24 +151,23 @@ export abstract class Table extends LitElement { return; } this.isLoading = true; - this.apiEndpoint(this.page).then((r) => { - this.data = r; - this.page = r.pagination.current; - this.expandedRows = []; - this.isLoading = false; - }).catch(() => { - this.isLoading = false; - }); + this.apiEndpoint(this.page) + .then((r) => { + this.data = r; + this.page = r.pagination.current; + this.expandedRows = []; + this.isLoading = false; + }) + .catch(() => { + this.isLoading = false; + }); } private renderLoading(): TemplateResult { return html`
    - - +
    `; @@ -167,7 +178,11 @@ export abstract class Table extends LitElement {
    - ${inner ? inner : html``} + ${inner + ? inner + : html``}
    @@ -182,40 +197,62 @@ export abstract class Table extends LitElement { return [this.renderEmpty()]; } return this.data.results.map((item: T, idx: number) => { - if ((this.expandedRows.length - 1) < idx) { + if (this.expandedRows.length - 1 < idx) { this.expandedRows[idx] = false; } - return html` + return html` - ${this.checkbox ? html` - = 0} - @input=${(ev: InputEvent) => { - if ((ev.target as HTMLInputElement).checked) { - // Add item to selected - this.selectedElements.push(item); - } else { - // Get index of item and remove if selected - const index = this.selectedElements.indexOf(item); - if (index <= -1) return; - this.selectedElements.splice(index, 1); - } - this.requestUpdate(); - }} /> - ` : html``} - ${this.expandable ? html` - - ` : html``} + ${this.checkbox + ? html` + = 0} + @input=${(ev: InputEvent) => { + if ((ev.target as HTMLInputElement).checked) { + // Add item to selected + this.selectedElements.push(item); + } else { + // Get index of item and remove if selected + const index = this.selectedElements.indexOf(item); + if (index <= -1) return; + this.selectedElements.splice(index, 1); + } + this.requestUpdate(); + }} + /> + ` + : html``} + ${this.expandable + ? html` + + ` + : html``} ${this.row(item).map((col) => { return html`${col}`; })} - + ${this.expandedRows[idx] ? this.renderExpanded(item) : html``} @@ -230,10 +267,11 @@ export abstract class Table extends LitElement { new CustomEvent(EVENT_REFRESH, { bubbles: true, composed: true, - }) + }), ); }} - class="pf-c-button pf-m-primary"> + class="pf-c-button pf-m-primary" + > ${t`Refresh`} `; } @@ -246,16 +284,20 @@ export abstract class Table extends LitElement { if (!this.searchEnabled()) { return html``; } - return html` { - this.search = value; - this.dispatchEvent( - new CustomEvent(EVENT_REFRESH, { - bubbles: true, - composed: true, - }) - ); - }}> -  `; + return html` { + this.search = value; + this.dispatchEvent( + new CustomEvent(EVENT_REFRESH, { + bubbles: true, + composed: true, + }), + ); + }} + > +  `; } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -268,20 +310,17 @@ export abstract class Table extends LitElement { } renderTable(): TemplateResult { - return html` - ${this.checkbox ? - html` - ${this.selectedElements.map(el => { - return html`${this.renderSelectedChip(el)}`; - })} - `: - html``} + return html` ${this.checkbox + ? html` + ${this.selectedElements.map((el) => { + return html`${this.renderSelectedChip(el)}`; + })} + ` + : html``}
    ${this.renderSearch()} -
    - ${this.renderToolbar()} -
    +
    ${this.renderToolbar()}
    ${this.renderToolbarAfter()} extends LitElement { new CustomEvent(EVENT_REFRESH, { bubbles: true, composed: true, - }) + }), ); - }}> + }} + >
    - ${this.checkbox ? html`` : html``} + ${this.checkbox + ? html`` + : html``} ${this.expandable ? html`` : html``} ${this.columns().map((col) => col.render(this))} - ${(this.isLoading || !this.data) ? this.renderLoading() : this.renderRows()} + ${this.isLoading || !this.data ? this.renderLoading() : this.renderRows()}
    - { - if ((ev.target as HTMLInputElement).checked) { - this.selectedElements = this.data?.results || []; - } else { - this.selectedElements = []; - } - }} /> - + { + if ((ev.target as HTMLInputElement).checked) { + this.selectedElements = this.data?.results || []; + } else { + this.selectedElements = []; + } + }} + /> +
    extends LitElement { new CustomEvent(EVENT_REFRESH, { bubbles: true, composed: true, - }) + }), ); - }}> + }} + >
    `; } diff --git a/web/src/elements/table/TableModal.ts b/web/src/elements/table/TableModal.ts index 6a7bd5bc3..4b50f7668 100644 --- a/web/src/elements/table/TableModal.ts +++ b/web/src/elements/table/TableModal.ts @@ -19,7 +19,16 @@ export abstract class TableModal extends Table { open = false; static get styles(): CSSResult[] { - return super.styles.concat(PFModalBox, PFBullseye, PFContent, PFBackdrop, PFPage, PFStack, AKGlobal, MODAL_BUTTON_STYLES); + return super.styles.concat( + PFModalBox, + PFBullseye, + PFContent, + PFBackdrop, + PFPage, + PFStack, + AKGlobal, + MODAL_BUTTON_STYLES, + ); } constructor() { @@ -33,7 +42,7 @@ export abstract class TableModal extends Table { } resetForms(): void { - this.querySelectorAll("[slot=form]").forEach(form => { + this.querySelectorAll("[slot=form]").forEach((form) => { if ("resetForm" in form) { form?.resetForm(); } @@ -42,7 +51,7 @@ export abstract class TableModal extends Table { onClick(): void { this.open = true; - this.querySelectorAll("*").forEach(child => { + this.querySelectorAll("*").forEach((child) => { if ("requestUpdate" in child) { (child as LitElement).requestUpdate(); } @@ -56,11 +65,7 @@ export abstract class TableModal extends Table { renderModal(): TemplateResult { return html`
    - `; + }} + /> + + +
    +
    `; } - } diff --git a/web/src/elements/user/SessionList.ts b/web/src/elements/user/SessionList.ts index 5b88b3cb6..ff77dea6d 100644 --- a/web/src/elements/user/SessionList.ts +++ b/web/src/elements/user/SessionList.ts @@ -10,7 +10,6 @@ import { DEFAULT_CONFIG } from "../../api/Config"; @customElement("ak-user-session-list") export class AuthenticatedSessionList extends Table { - @property() targetUser!: string; @@ -41,8 +40,7 @@ export class AuthenticatedSessionList extends Table { html`${item.userAgent.userAgent?.family}`, html`${item.userAgent.os?.family}`, html`${item.expires?.toLocaleString()}`, - html` - { @@ -54,12 +52,10 @@ export class AuthenticatedSessionList extends Table { return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsDestroy({ uuid: item.uuid || "", }); - }}> - + }} + > + `, ]; } - } diff --git a/web/src/elements/user/UserConsentList.ts b/web/src/elements/user/UserConsentList.ts index c6fd7cf4f..40f5ae84a 100644 --- a/web/src/elements/user/UserConsentList.ts +++ b/web/src/elements/user/UserConsentList.ts @@ -36,25 +36,22 @@ export class UserConsentList extends Table { return [ html`${item.application.name}`, html`${item.expires?.toLocaleString()}`, - html` - { return new CoreApi(DEFAULT_CONFIG).coreUserConsentUsedByList({ - id: item.pk + id: item.pk, }); }} .delete=${() => { return new CoreApi(DEFAULT_CONFIG).coreUserConsentDestroy({ id: item.pk, }); - }}> - + }} + > + `, ]; } - } diff --git a/web/src/flows/FlowExecutor.ts b/web/src/flows/FlowExecutor.ts index d1c523a69..71f094400 100644 --- a/web/src/flows/FlowExecutor.ts +++ b/web/src/flows/FlowExecutor.ts @@ -1,5 +1,13 @@ import { t } from "@lingui/macro"; -import { LitElement, html, customElement, property, TemplateResult, CSSResult, css } from "lit-element"; +import { + LitElement, + html, + customElement, + property, + TemplateResult, + CSSResult, + css, +} from "lit-element"; import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; @@ -26,7 +34,15 @@ import "./stages/password/PasswordStage"; import "./stages/prompt/PromptStage"; import "./sources/plex/PlexLoginInit"; import { StageHost } from "./stages/base"; -import { ChallengeChoices, CurrentTenant, ChallengeTypes, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api"; +import { + ChallengeChoices, + CurrentTenant, + ChallengeTypes, + FlowChallengeResponseRequest, + FlowsApi, + RedirectChallenge, + ShellChallenge, +} from "authentik-api"; import { DEFAULT_CONFIG, tenant } from "../api/Config"; import { ifDefined } from "lit-html/directives/if-defined"; import { until } from "lit-html/directives/until"; @@ -37,13 +53,12 @@ import { WebsocketClient } from "../common/ws"; @customElement("ak-flow-executor") export class FlowExecutor extends LitElement implements StageHost { - flowSlug: string; - @property({attribute: false}) + @property({ attribute: false }) challenge?: ChallengeTypes; - @property({type: Boolean}) + @property({ type: Boolean }) loading = false; @property({ attribute: false }) @@ -84,13 +99,15 @@ export class FlowExecutor extends LitElement implements StageHost { } setBackground(url: string): void { - this.shadowRoot?.querySelectorAll(".pf-c-background-image").forEach((bg) => { - bg.style.setProperty("--ak-flow-background", `url('${url}')`); - }); + this.shadowRoot + ?.querySelectorAll(".pf-c-background-image") + .forEach((bg) => { + bg.style.setProperty("--ak-flow-background", `url('${url}')`); + }); } private postUpdate(): void { - tenant().then(tenant => { + tenant().then((tenant) => { if (this.challenge?.flowInfo?.title) { document.title = `${this.challenge.flowInfo?.title} - ${tenant.brandingTitle}`; } else { @@ -105,40 +122,48 @@ export class FlowExecutor extends LitElement implements StageHost { // @ts-ignore payload.component = this.challenge.component; this.loading = true; - return new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({ - flowSlug: this.flowSlug, - query: window.location.search.substring(1), - flowChallengeResponseRequest: payload, - }).then((data) => { - this.challenge = data; - this.postUpdate(); - }).catch((e: Error | Response) => { - this.errorMessage(e); - }).finally(() => { - this.loading = false; - }); + return new FlowsApi(DEFAULT_CONFIG) + .flowsExecutorSolve({ + flowSlug: this.flowSlug, + query: window.location.search.substring(1), + flowChallengeResponseRequest: payload, + }) + .then((data) => { + this.challenge = data; + this.postUpdate(); + }) + .catch((e: Error | Response) => { + this.errorMessage(e); + }) + .finally(() => { + this.loading = false; + }); } firstUpdated(): void { configureSentry(); - tenant().then(tenant => this.tenant = tenant); + tenant().then((tenant) => (this.tenant = tenant)); this.loading = true; - new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({ - flowSlug: this.flowSlug, - query: window.location.search.substring(1), - }).then((challenge) => { - this.challenge = challenge; - // Only set background on first update, flow won't change throughout execution - if (this.challenge?.flowInfo?.background) { - this.setBackground(this.challenge.flowInfo.background); - } - this.postUpdate(); - }).catch((e: Error | Response) => { - // Catch JSON or Update errors - this.errorMessage(e); - }).finally(() => { - this.loading = false; - }); + new FlowsApi(DEFAULT_CONFIG) + .flowsExecutorGet({ + flowSlug: this.flowSlug, + query: window.location.search.substring(1), + }) + .then((challenge) => { + this.challenge = challenge; + // Only set background on first update, flow won't change throughout execution + if (this.challenge?.flowInfo?.background) { + this.setBackground(this.challenge.flowInfo.background); + } + this.postUpdate(); + }) + .catch((e: Error | Response) => { + // Catch JSON or Update errors + this.errorMessage(e); + }) + .finally(() => { + this.loading = false; + }); } async errorMessage(error: Error | Response): Promise { @@ -167,7 +192,7 @@ export class FlowExecutor extends LitElement implements StageHost {
    - ` + `, } as ChallengeTypes; } @@ -183,46 +208,92 @@ export class FlowExecutor extends LitElement implements StageHost { } switch (this.challenge.type) { case ChallengeChoices.Redirect: - console.debug("authentik/flows: redirecting to url from server", (this.challenge as RedirectChallenge).to); + console.debug( + "authentik/flows: redirecting to url from server", + (this.challenge as RedirectChallenge).to, + ); window.location.assign((this.challenge as RedirectChallenge).to); - return html` - `; + return html` + `; case ChallengeChoices.Shell: return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`; case ChallengeChoices.Native: switch (this.challenge.component) { case "ak-stage-access-denied": - return html``; + return html``; case "ak-stage-identification": - return html``; + return html``; case "ak-stage-password": - return html``; + return html``; case "ak-stage-captcha": - return html``; + return html``; case "ak-stage-consent": - return html``; + return html``; case "ak-stage-dummy": - return html``; + return html``; case "ak-stage-email": - return html``; + return html``; case "ak-stage-autosubmit": - return html``; + return html``; case "ak-stage-prompt": - return html``; + return html``; case "ak-stage-authenticator-totp": - return html``; + return html``; case "ak-stage-authenticator-duo": - return html``; + return html``; case "ak-stage-authenticator-static": - return html``; + return html``; case "ak-stage-authenticator-webauthn": - return html``; + return html``; case "ak-stage-authenticator-validate": - return html``; + return html``; case "ak-flow-sources-plex": - return html``; + return html``; default: break; } @@ -236,59 +307,85 @@ export class FlowExecutor extends LitElement implements StageHost { renderChallengeWrapper(): TemplateResult { if (!this.challenge) { - return html` - `; + return html` `; } - return html` - ${this.loading ? this.renderLoading() : html``} - ${this.renderChallenge()} - `; + return html` ${this.loading ? this.renderLoading() : html``} ${this.renderChallenge()} `; } render(): TemplateResult { return html`
    - - - - - - - - - - - -
    - `; + `; } - } diff --git a/web/src/flows/FormStatic.ts b/web/src/flows/FormStatic.ts index 9826fe953..b2aa38d34 100644 --- a/web/src/flows/FormStatic.ts +++ b/web/src/flows/FormStatic.ts @@ -1,11 +1,18 @@ import { t } from "@lingui/macro"; -import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-form-static") export class FormStatic extends LitElement { - @property() userAvatar?: string; @@ -13,39 +20,45 @@ export class FormStatic extends LitElement { user = ""; static get styles(): CSSResult[] { - return [PFAvatar, css` - /* Form with user */ - .form-control-static { - margin-top: var(--pf-global--spacer--sm); - display: flex; - align-items: center; - justify-content: space-between; - } - .form-control-static .avatar { - display: flex; - align-items: center; - } - .form-control-static img { - margin-right: var(--pf-global--spacer--xs); - } - .form-control-static a { - padding-top: var(--pf-global--spacer--xs); - padding-bottom: var(--pf-global--spacer--xs); - line-height: var(--pf-global--spacer--xl); - } - `]; + return [ + PFAvatar, + css` + /* Form with user */ + .form-control-static { + margin-top: var(--pf-global--spacer--sm); + display: flex; + align-items: center; + justify-content: space-between; + } + .form-control-static .avatar { + display: flex; + align-items: center; + } + .form-control-static img { + margin-right: var(--pf-global--spacer--xs); + } + .form-control-static a { + padding-top: var(--pf-global--spacer--xs); + padding-bottom: var(--pf-global--spacer--xs); + line-height: var(--pf-global--spacer--xl); + } + `, + ]; } render(): TemplateResult { return html`
    - ${t`User's avatar`} + ${t`User's avatar`} ${this.user}
    `; } - } diff --git a/web/src/flows/access_denied/FlowAccessDenied.ts b/web/src/flows/access_denied/FlowAccessDenied.ts index baf13771b..1f5fe54a8 100644 --- a/web/src/flows/access_denied/FlowAccessDenied.ts +++ b/web/src/flows/access_denied/FlowAccessDenied.ts @@ -13,23 +13,20 @@ import { t } from "@lingui/macro"; import "../../elements/EmptyState"; @customElement("ak-stage-access-denied") -export class FlowAccessDenied extends BaseStage { - +export class FlowAccessDenied extends BaseStage< + AccessDeniedChallenge, + FlowChallengeResponseRequest +> { static get styles(): CSSResult[] { return [PFBase, PFLogin, PFForm, PFList, PFFormControl, PFTitle, AKGlobal]; } render(): TemplateResult { if (!this.challenge) { - return html` - `; + return html` `; } return html`
    - +
    `; } - } diff --git a/web/src/flows/sources/plex/API.ts b/web/src/flows/sources/plex/API.ts index 7f0fd3731..1fdc20160 100644 --- a/web/src/flows/sources/plex/API.ts +++ b/web/src/flows/sources/plex/API.ts @@ -23,40 +23,54 @@ export const DEFAULT_HEADERS = { }; export function popupCenterScreen(url: string, title: string, w: number, h: number): Window | null { - const top = (screen.height - h) / 4, left = (screen.width - w) / 2; - const popup = window.open(url, title, `scrollbars=yes,width=${w},height=${h},top=${top},left=${left}`); + const top = (screen.height - h) / 4, + left = (screen.width - w) / 2; + const popup = window.open( + url, + title, + `scrollbars=yes,width=${w},height=${h},top=${top},left=${left}`, + ); return popup; } export class PlexAPIClient { - token: string; constructor(token: string) { this.token = token; } - static async getPin(clientIdentifier: string): Promise<{ authUrl: string, pin: PlexPinResponse }> { - const headers = { ...DEFAULT_HEADERS, ...{ - "X-Plex-Client-Identifier": clientIdentifier - }}; + static async getPin( + clientIdentifier: string, + ): Promise<{ authUrl: string; pin: PlexPinResponse }> { + const headers = { + ...DEFAULT_HEADERS, + ...{ + "X-Plex-Client-Identifier": clientIdentifier, + }, + }; const pinResponse = await fetch("https://plex.tv/api/v2/pins.json?strong=true", { method: "POST", - headers: headers + headers: headers, }); const pin: PlexPinResponse = await pinResponse.json(); return { - authUrl: `https://app.plex.tv/auth#!?clientID=${encodeURIComponent(clientIdentifier)}&code=${pin.code}`, - pin: pin + authUrl: `https://app.plex.tv/auth#!?clientID=${encodeURIComponent( + clientIdentifier, + )}&code=${pin.code}`, + pin: pin, }; } static async pinStatus(clientIdentifier: string, id: number): Promise { - const headers = { ...DEFAULT_HEADERS, ...{ - "X-Plex-Client-Identifier": clientIdentifier - }}; + const headers = { + ...DEFAULT_HEADERS, + ...{ + "X-Plex-Client-Identifier": clientIdentifier, + }, + }; const pinResponse = await fetch(`https://plex.tv/api/v2/pins/${id}`, { - headers: headers + headers: headers, }); const pin: PlexPinResponse = await pinResponse.json(); return pin.authToken || ""; @@ -65,7 +79,7 @@ export class PlexAPIClient { static async pinPoll(clientIdentifier: string, id: number): Promise { const executePoll = async ( resolve: (authToken: string) => void, - reject: (e: Error) => void + reject: (e: Error) => void, ) => { try { const response = await PlexAPIClient.pinStatus(clientIdentifier, id); @@ -84,13 +98,15 @@ export class PlexAPIClient { } async getServers(): Promise { - const resourcesResponse = await fetch(`https://plex.tv/api/v2/resources?X-Plex-Token=${this.token}&X-Plex-Client-Identifier=authentik`, { - headers: DEFAULT_HEADERS - }); + const resourcesResponse = await fetch( + `https://plex.tv/api/v2/resources?X-Plex-Token=${this.token}&X-Plex-Client-Identifier=authentik`, + { + headers: DEFAULT_HEADERS, + }, + ); const resources: PlexResource[] = await resourcesResponse.json(); - return resources.filter(r => { + return resources.filter((r) => { return r.provides.toLowerCase().includes("server") && r.owned; }); } - } diff --git a/web/src/flows/sources/plex/PlexLoginInit.ts b/web/src/flows/sources/plex/PlexLoginInit.ts index dbf6d5fe6..02214dee4 100644 --- a/web/src/flows/sources/plex/PlexLoginInit.ts +++ b/web/src/flows/sources/plex/PlexLoginInit.ts @@ -1,5 +1,8 @@ import { t } from "@lingui/macro"; -import { PlexAuthenticationChallenge, PlexAuthenticationChallengeResponseRequest } from "authentik-api"; +import { + PlexAuthenticationChallenge, + PlexAuthenticationChallengeResponseRequest, +} from "authentik-api"; import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; @@ -16,10 +19,11 @@ import { SourcesApi } from "authentik-api"; import { showMessage } from "../../../elements/messages/MessageContainer"; import { MessageLevel } from "../../../elements/messages/Message"; - @customElement("ak-flow-sources-plex") -export class PlexLoginInit extends BaseStage { - +export class PlexLoginInit extends BaseStage< + PlexAuthenticationChallenge, + PlexAuthenticationChallengeResponseRequest +> { static get styles(): CSSResult[] { return [PFBase, PFLogin, PFForm, PFFormControl, PFButton, PFTitle, AKGlobal]; } @@ -27,46 +31,43 @@ export class PlexLoginInit extends BaseStage { const authInfo = await PlexAPIClient.getPin(this.challenge?.clientId || ""); const authWindow = popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700); - PlexAPIClient.pinPoll(this.challenge?.clientId || "", authInfo.pin.id).then(token => { + PlexAPIClient.pinPoll(this.challenge?.clientId || "", authInfo.pin.id).then((token) => { authWindow?.close(); - new SourcesApi(DEFAULT_CONFIG).sourcesPlexRedeemTokenCreate({ - plexTokenRedeemRequest: { - plexToken: token, - }, - slug: this.challenge?.slug || "", - }).then((r) => { - window.location.assign(r.to); - }).catch((r: Response) => { - r.json().then((body: {detail: string}) => { - showMessage({ - level: MessageLevel.error, - message: body.detail + new SourcesApi(DEFAULT_CONFIG) + .sourcesPlexRedeemTokenCreate({ + plexTokenRedeemRequest: { + plexToken: token, + }, + slug: this.challenge?.slug || "", + }) + .then((r) => { + window.location.assign(r.to); + }) + .catch((r: Response) => { + r.json().then((body: { detail: string }) => { + showMessage({ + level: MessageLevel.error, + message: body.detail, + }); + setTimeout(() => { + window.location.assign("/"); + }, 5000); }); - setTimeout(() => { - window.location.assign("/"); - }, 5000); }); - }); }); } render(): TemplateResult { return html`
    - +
    `; } - } diff --git a/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts b/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts index 0dd9720bd..ec3b6f94b 100644 --- a/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts +++ b/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts @@ -11,13 +11,19 @@ import { BaseStage } from "../base"; import "../../../elements/forms/FormElement"; import "../../../elements/EmptyState"; import "../../FormStatic"; -import { AuthenticatorDuoChallenge, AuthenticatorDuoChallengeResponseRequest, StagesApi } from "authentik-api"; +import { + AuthenticatorDuoChallenge, + AuthenticatorDuoChallengeResponseRequest, + StagesApi, +} from "authentik-api"; import { DEFAULT_CONFIG } from "../../../api/Config"; import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-stage-authenticator-duo") -export class AuthenticatorDuoStage extends BaseStage { - +export class AuthenticatorDuoStage extends BaseStage< + AuthenticatorDuoChallenge, + AuthenticatorDuoChallengeResponseRequest +> { static get styles(): CSSResult[] { return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal]; } @@ -31,35 +37,41 @@ export class AuthenticatorDuoStage extends BaseStage { - return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoEnrollmentStatusCreate({ - stageUuid: this.challenge?.stageUuid || "", - }).then(() => { - this.host?.submit({}); - }).catch(() => { - console.debug("authentik/flows/duo: Waiting for auth status"); - }); + return new StagesApi(DEFAULT_CONFIG) + .stagesAuthenticatorDuoEnrollmentStatusCreate({ + stageUuid: this.challenge?.stageUuid || "", + }) + .then(() => { + this.host?.submit({}); + }) + .catch(() => { + console.debug("authentik/flows/duo: Waiting for auth status"); + }); } render(): TemplateResult { if (!this.challenge) { - return html` - `; + return html` `; } return html`
    - +
    `; } - } diff --git a/web/src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts b/web/src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts index ef0527b34..cfa379a0c 100644 --- a/web/src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts +++ b/web/src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts @@ -11,58 +11,75 @@ import { BaseStage } from "../base"; import "../../../elements/forms/FormElement"; import "../../../elements/EmptyState"; import "../../FormStatic"; -import { AuthenticatorStaticChallenge, AuthenticatorStaticChallengeResponseRequest } from "authentik-api"; +import { + AuthenticatorStaticChallenge, + AuthenticatorStaticChallengeResponseRequest, +} from "authentik-api"; import { ifDefined } from "lit-html/directives/if-defined"; export const STATIC_TOKEN_STYLE = css` -/* Static OTP Tokens */ -.ak-otp-tokens { - list-style: circle; - columns: 2; - -webkit-columns: 2; - -moz-columns: 2; - margin-left: var(--pf-global--spacer--xs); -} -.ak-otp-tokens li { - font-size: var(--pf-global--FontSize--2xl); - font-family: monospace; -} + /* Static OTP Tokens */ + .ak-otp-tokens { + list-style: circle; + columns: 2; + -webkit-columns: 2; + -moz-columns: 2; + margin-left: var(--pf-global--spacer--xs); + } + .ak-otp-tokens li { + font-size: var(--pf-global--FontSize--2xl); + font-family: monospace; + } `; - @customElement("ak-stage-authenticator-static") -export class AuthenticatorStaticStage extends BaseStage { - +export class AuthenticatorStaticStage extends BaseStage< + AuthenticatorStaticChallenge, + AuthenticatorStaticChallengeResponseRequest +> { static get styles(): CSSResult[] { - return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal, STATIC_TOKEN_STYLE]; + return [ + PFBase, + PFLogin, + PFForm, + PFFormControl, + PFTitle, + PFButton, + AKGlobal, + STATIC_TOKEN_STYLE, + ]; } render(): TemplateResult { if (!this.challenge) { - return html` - `; + return html` `; } return html`
    - +
    `; } - } diff --git a/web/src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts b/web/src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts index 3a9eb17b4..9d46b4894 100644 --- a/web/src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts +++ b/web/src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts @@ -14,53 +14,66 @@ import { showMessage } from "../../../elements/messages/MessageContainer"; import "../../../elements/EmptyState"; import "../../FormStatic"; import { MessageLevel } from "../../../elements/messages/Message"; -import { AuthenticatorTOTPChallenge, AuthenticatorTOTPChallengeResponseRequest } from "authentik-api"; +import { + AuthenticatorTOTPChallenge, + AuthenticatorTOTPChallengeResponseRequest, +} from "authentik-api"; import { ifDefined } from "lit-html/directives/if-defined"; - @customElement("ak-stage-authenticator-totp") -export class AuthenticatorTOTPStage extends BaseStage { - +export class AuthenticatorTOTPStage extends BaseStage< + AuthenticatorTOTPChallenge, + AuthenticatorTOTPChallengeResponseRequest +> { static get styles(): CSSResult[] { return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal]; } render(): TemplateResult { if (!this.challenge) { - return html` - `; + return html` `; } return html` + + +
    + +
    `; } - } diff --git a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts index fdfc4c178..93d950e97 100644 --- a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts +++ b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts @@ -12,12 +12,18 @@ import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage"; import "../../../elements/forms/FormElement"; import "../../../elements/EmptyState"; import "../../FormStatic"; -import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; +import { + AuthenticatorValidationChallenge, + AuthenticatorValidationChallengeResponseRequest, + DeviceChallenge, +} from "authentik-api"; import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-stage-authenticator-validate-duo") -export class AuthenticatorValidateStageWebDuo extends BaseStage { - +export class AuthenticatorValidateStageWebDuo extends BaseStage< + AuthenticatorValidationChallenge, + AuthenticatorValidationChallengeResponseRequest +> { @property({ attribute: false }) deviceChallenge?: DeviceChallenge; @@ -30,49 +36,58 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage - `; + return html` `; } return html` -
    - -
    `; + + + +
    + +
    `; } - } diff --git a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts index 41b11c731..a7bbb41ca 100644 --- a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts +++ b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts @@ -8,15 +8,24 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import AKGlobal from "../../../authentik.css"; import { PFSize } from "../../../elements/Spinner"; -import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils"; +import { + transformAssertionForServer, + transformCredentialRequestOptions, +} from "../authenticator_webauthn/utils"; import { BaseStage } from "../base"; import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage"; -import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; +import { + AuthenticatorValidationChallenge, + AuthenticatorValidationChallengeResponseRequest, + DeviceChallenge, +} from "authentik-api"; @customElement("ak-stage-authenticator-validate-webauthn") -export class AuthenticatorValidateStageWebAuthn extends BaseStage { - - @property({attribute: false}) +export class AuthenticatorValidateStageWebAuthn extends BaseStage< + AuthenticatorValidationChallenge, + AuthenticatorValidationChallengeResponseRequest +> { + @property({ attribute: false }) deviceChallenge?: DeviceChallenge; @property({ type: Boolean }) @@ -25,7 +34,7 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage { // convert certain members of the PublicKeyCredentialRequestOptions into // byte arrays as expected by the spec. - const credentialRequestOptions = this.deviceChallenge?.challenge; - const transformedCredentialRequestOptions = transformCredentialRequestOptions(credentialRequestOptions); + const credentialRequestOptions = ( + this.deviceChallenge?.challenge + ); + const transformedCredentialRequestOptions = + transformCredentialRequestOptions(credentialRequestOptions); // request the authenticator to create an assertion signature using the // credential private key @@ -54,12 +66,14 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStageassertion); + const transformedAssertionForServer = transformAssertionForServer( + assertion, + ); // post the assertion to the server for verification. try { await this.host?.submit({ - webauthn: transformedAssertionForServer + webauthn: transformedAssertionForServer, }); } catch (err) { throw new Error(t`Error when validating assertion on server: ${err}`); @@ -75,48 +89,56 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage { - console.error(e); - this.authenticateMessage = e.toString(); - }).finally(() => { - this.authenticateRunning = false; - }); + this.authenticate() + .catch((e) => { + console.error(e); + this.authenticateMessage = e.toString(); + }) + .finally(() => { + this.authenticateRunning = false; + }); } render(): TemplateResult { return html` -
    - -
    `; + ${this.authenticateRunning + ? html`
    +
    +
    + +
    +
    +
    ` + : html`
    +

    ${this.authenticateMessage}

    + +
    `} + +
    + +
    `; } - } diff --git a/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts b/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts index 709a20690..b28aa4eda 100644 --- a/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts +++ b/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts @@ -9,17 +9,26 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; import AKGlobal from "../../../authentik.css"; import { PFSize } from "../../../elements/Spinner"; import { BaseStage } from "../base"; -import { Assertion, transformCredentialCreateOptions, transformNewAssertionForServer } from "./utils"; -import { AuthenticatorWebAuthnChallenge, AuthenticatorWebAuthnChallengeResponseRequest } from "authentik-api"; +import { + Assertion, + transformCredentialCreateOptions, + transformNewAssertionForServer, +} from "./utils"; +import { + AuthenticatorWebAuthnChallenge, + AuthenticatorWebAuthnChallengeResponseRequest, +} from "authentik-api"; export interface WebAuthnAuthenticatorRegisterChallengeResponse { response: Assertion; } @customElement("ak-stage-authenticator-webauthn") -export class WebAuthnAuthenticatorRegisterStage extends BaseStage { - - @property({type: Boolean}) +export class WebAuthnAuthenticatorRegisterStage extends BaseStage< + AuthenticatorWebAuthnChallenge, + AuthenticatorWebAuthnChallengeResponseRequest +> { + @property({ type: Boolean }) registerRunning = false; @property() @@ -35,13 +44,15 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage await navigator.credentials.create({ - publicKey: publicKeyCredentialCreateOptions + credential = await navigator.credentials.create({ + publicKey: publicKeyCredentialCreateOptions, }); if (!credential) { throw new Error("Credential is empty"); @@ -58,7 +69,7 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage { - console.error(e); - this.registerMessage = e.toString(); - }).finally(() => { - this.registerRunning = false; - }); + this.register() + .catch((e) => { + console.error(e); + this.registerMessage = e.toString(); + }) + .finally(() => { + this.registerRunning = false; + }); } firstUpdated(): void { @@ -89,26 +102,32 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage
    @@ -116,5 +135,4 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage
    `; } - } diff --git a/web/src/flows/stages/authenticator_webauthn/utils.ts b/web/src/flows/stages/authenticator_webauthn/utils.ts index 7c4fad31b..86aa60b4e 100644 --- a/web/src/flows/stages/authenticator_webauthn/utils.ts +++ b/web/src/flows/stages/authenticator_webauthn/utils.ts @@ -2,30 +2,28 @@ import * as base64js from "base64-js"; import { hexEncode } from "../../../utils"; export function b64enc(buf: Uint8Array): string { - return base64js.fromByteArray(buf) - .replace(/\+/g, "-") - .replace(/\//g, "_") - .replace(/=/g, ""); + return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } export function b64RawEnc(buf: Uint8Array): string { - return base64js.fromByteArray(buf) - .replace(/\+/g, "-") - .replace(/\//g, "_"); + return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_"); } /** * Transforms items in the credentialCreateOptions generated on the server * into byte arrays expected by the navigator.credentials.create() call */ -export function transformCredentialCreateOptions(credentialCreateOptions: PublicKeyCredentialCreationOptions): PublicKeyCredentialCreationOptions { +export function transformCredentialCreateOptions( + credentialCreateOptions: PublicKeyCredentialCreationOptions, +): PublicKeyCredentialCreationOptions { const user = credentialCreateOptions.user; user.id = u8arr(b64enc(credentialCreateOptions.user.id as Uint8Array)); const challenge = u8arr(credentialCreateOptions.challenge.toString()); - const transformedCredentialCreateOptions = Object.assign( - {}, credentialCreateOptions, - { challenge, user }); + const transformedCredentialCreateOptions = Object.assign({}, credentialCreateOptions, { + challenge, + user, + }); return transformedCredentialCreateOptions; } @@ -46,11 +44,10 @@ export interface Assertion { */ export function transformNewAssertionForServer(newAssertion: PublicKeyCredential): Assertion { const attObj = new Uint8Array( - (newAssertion.response).attestationObject); - const clientDataJSON = new Uint8Array( - newAssertion.response.clientDataJSON); - const rawId = new Uint8Array( - newAssertion.rawId); + (newAssertion.response).attestationObject, + ); + const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON); + const rawId = new Uint8Array(newAssertion.rawId); const registrationClientExtensions = newAssertion.getClientExtensionResults(); return { @@ -59,26 +56,32 @@ export function transformNewAssertionForServer(newAssertion: PublicKeyCredential type: newAssertion.type, attObj: b64enc(attObj), clientData: b64enc(clientDataJSON), - registrationClientExtensions: JSON.stringify(registrationClientExtensions) + registrationClientExtensions: JSON.stringify(registrationClientExtensions), }; } function u8arr(input: string): Uint8Array { - return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), c => c.charCodeAt(0)); + return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) => + c.charCodeAt(0), + ); } -export function transformCredentialRequestOptions(credentialRequestOptions: PublicKeyCredentialRequestOptions): PublicKeyCredentialRequestOptions { +export function transformCredentialRequestOptions( + credentialRequestOptions: PublicKeyCredentialRequestOptions, +): PublicKeyCredentialRequestOptions { const challenge = u8arr(credentialRequestOptions.challenge.toString()); - const allowCredentials = (credentialRequestOptions.allowCredentials || []).map(credentialDescriptor => { - const id = u8arr(credentialDescriptor.id.toString()); - return Object.assign({}, credentialDescriptor, { id }); - }); + const allowCredentials = (credentialRequestOptions.allowCredentials || []).map( + (credentialDescriptor) => { + const id = u8arr(credentialDescriptor.id.toString()); + return Object.assign({}, credentialDescriptor, { id }); + }, + ); - const transformedCredentialRequestOptions = Object.assign( - {}, - credentialRequestOptions, - { challenge, allowCredentials }); + const transformedCredentialRequestOptions = Object.assign({}, credentialRequestOptions, { + challenge, + allowCredentials, + }); return transformedCredentialRequestOptions; } @@ -97,8 +100,8 @@ export interface AuthAssertion { * Encodes the binary data in the assertion into strings for posting to the server. * @param {PublicKeyCredential} newAssertion */ -export function transformAssertionForServer(newAssertion: PublicKeyCredential): AuthAssertion{ - const response = newAssertion.response; +export function transformAssertionForServer(newAssertion: PublicKeyCredential): AuthAssertion { + const response = newAssertion.response; const authData = new Uint8Array(response.authenticatorData); const clientDataJSON = new Uint8Array(response.clientDataJSON); const rawId = new Uint8Array(newAssertion.rawId); @@ -112,6 +115,6 @@ export function transformAssertionForServer(newAssertion: PublicKeyCredential): authData: b64RawEnc(authData), clientData: b64RawEnc(clientDataJSON), signature: hexEncode(sig), - assertionClientExtensions: JSON.stringify(assertionClientExtensions) + assertionClientExtensions: JSON.stringify(assertionClientExtensions), }; } diff --git a/web/src/flows/stages/autosubmit/AutosubmitStage.ts b/web/src/flows/stages/autosubmit/AutosubmitStage.ts index 87578eeef..44d3deb57 100644 --- a/web/src/flows/stages/autosubmit/AutosubmitStage.ts +++ b/web/src/flows/stages/autosubmit/AutosubmitStage.ts @@ -12,36 +12,37 @@ import "../../../elements/EmptyState"; import { AutosubmitChallenge, AutoSubmitChallengeResponseRequest } from "authentik-api"; @customElement("ak-stage-autosubmit") -export class AutosubmitStage extends BaseStage { - +export class AutosubmitStage extends BaseStage< + AutosubmitChallenge, + AutoSubmitChallengeResponseRequest +> { static get styles(): CSSResult[] { return [PFBase, PFLogin, PFForm, PFFormControl, PFButton, PFTitle, AKGlobal]; } updated(): void { - this.shadowRoot?.querySelectorAll("form").forEach((form) => {form.submit();}); + this.shadowRoot?.querySelectorAll("form").forEach((form) => { + form.submit(); + }); } render(): TemplateResult { if (!this.challenge) { - return html` - `; + return html` `; } return html`